@cloudbitmaps/core 0.15.0 → 0.16.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/dist/{chunk-B7IGS3W6.js → chunk-LFUNPMXZ.js} +1 -1
- package/dist/{chunk-B7IGS3W6.js.map → chunk-LFUNPMXZ.js.map} +2 -2
- package/dist/core/codec.d.ts +10 -0
- package/dist/core/cost.d.ts +1 -1
- package/dist/core/crbm-storage-source.d.ts +3 -3
- package/dist/core/engine.d.ts +36 -6
- package/dist/core/load-input.d.ts +9 -0
- package/dist/core/load.d.ts +20 -1
- package/dist/core/ports.d.ts +29 -4
- package/dist/driver-kit.js +43 -10
- package/dist/driver-kit.js.map +2 -2
- package/dist/drivers/_shared/object-registry.d.ts +16 -3
- package/dist/drivers/localfs/registry.d.ts +4 -0
- package/dist/drivers/memory.d.ts +4 -0
- package/dist/index.d.ts +2 -2
- package/dist/index.js +228 -47
- package/dist/index.js.map +2 -2
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -52,7 +52,7 @@ import {
|
|
|
52
52
|
validateUserNamespace,
|
|
53
53
|
validateUserRef,
|
|
54
54
|
webCryptoEntropy
|
|
55
|
-
} from "./chunk-
|
|
55
|
+
} from "./chunk-LFUNPMXZ.js";
|
|
56
56
|
|
|
57
57
|
// src/core/bit-route.ts
|
|
58
58
|
var U32_MAX = 4294967295;
|
|
@@ -494,6 +494,8 @@ var SegmentEngine = class {
|
|
|
494
494
|
* they deliver, as a read whose entry an invalidation dropped does not (streams hold no `openReads` entry).
|
|
495
495
|
*/
|
|
496
496
|
openStreams = /* @__PURE__ */ new Set();
|
|
497
|
+
/** How many times {@link invalidate} has been called: a read compares it with the count it began under. */
|
|
498
|
+
invalidations = 0;
|
|
497
499
|
constructor(deps) {
|
|
498
500
|
this.storage = deps.storage;
|
|
499
501
|
this.cache = deps.cache;
|
|
@@ -578,8 +580,15 @@ var SegmentEngine = class {
|
|
|
578
580
|
* coalesced ranges when the source has `getChunks`, opening 1, 2, 4 … ranges wide up to {@link DEFAULT_INTERSECT_CONCURRENCY},
|
|
579
581
|
* else the chunk-by-chunk window of that many reads. `close` stops what is left of it.
|
|
580
582
|
*/
|
|
581
|
-
chunkSequence(seg, chunkKeys, gen) {
|
|
582
|
-
const streamed = this.openStreamed(
|
|
583
|
+
chunkSequence(seg, chunkKeys, gen, epoch) {
|
|
584
|
+
const streamed = this.openStreamed(
|
|
585
|
+
seg,
|
|
586
|
+
chunkKeys,
|
|
587
|
+
gen,
|
|
588
|
+
epoch,
|
|
589
|
+
DEFAULT_INTERSECT_CONCURRENCY,
|
|
590
|
+
1
|
|
591
|
+
);
|
|
583
592
|
if (streamed !== void 0) {
|
|
584
593
|
return {
|
|
585
594
|
take: (chunkKey) => this.streamedChunk(streamed, chunkKey),
|
|
@@ -604,10 +613,11 @@ var SegmentEngine = class {
|
|
|
604
613
|
return range === void 0 ? this.iterateAll(seg) : this.iterateRange(seg, range);
|
|
605
614
|
}
|
|
606
615
|
async *iterateAll(seg) {
|
|
616
|
+
const epoch = this.invalidations;
|
|
607
617
|
const chunkKeys = await this.chunkKeys(seg);
|
|
608
618
|
checkBudget(this.budget, chunkKeys.length, "iterate");
|
|
609
619
|
const gen = await this.cacheVersion(seg);
|
|
610
|
-
const chunks = this.chunkSequence(seg, chunkKeys, gen);
|
|
620
|
+
const chunks = this.chunkSequence(seg, chunkKeys, gen, epoch);
|
|
611
621
|
try {
|
|
612
622
|
for (const chunkKey of chunkKeys) {
|
|
613
623
|
const chunk = await chunks.take(chunkKey);
|
|
@@ -619,12 +629,13 @@ var SegmentEngine = class {
|
|
|
619
629
|
}
|
|
620
630
|
}
|
|
621
631
|
async *iterateRange(seg, range) {
|
|
632
|
+
const epoch = this.invalidations;
|
|
622
633
|
const w = windowOf(range) ?? WHOLE_ID_SPACE;
|
|
623
634
|
if (w === "empty") return;
|
|
624
635
|
const chunkKeys = keysWithin(await this.chunkKeys(seg), w);
|
|
625
636
|
checkBudget(this.budget, chunkKeys.length, "iterate");
|
|
626
637
|
const gen = await this.cacheVersion(seg);
|
|
627
|
-
const chunks = this.chunkSequence(seg, chunkKeys, gen);
|
|
638
|
+
const chunks = this.chunkSequence(seg, chunkKeys, gen, epoch);
|
|
628
639
|
try {
|
|
629
640
|
for (const chunkKey of chunkKeys) {
|
|
630
641
|
const chunk = await chunks.take(chunkKey);
|
|
@@ -646,12 +657,13 @@ var SegmentEngine = class {
|
|
|
646
657
|
* the caller's to keep.
|
|
647
658
|
*/
|
|
648
659
|
async *iterateBatches(seg, range) {
|
|
660
|
+
const epoch = this.invalidations;
|
|
649
661
|
const w = windowOf(range);
|
|
650
662
|
if (w === "empty") return;
|
|
651
663
|
const chunkKeys = w === null ? await this.chunkKeys(seg) : keysWithin(await this.chunkKeys(seg), w);
|
|
652
664
|
checkBudget(this.budget, chunkKeys.length, "iterate");
|
|
653
665
|
const gen = await this.cacheVersion(seg);
|
|
654
|
-
const chunks = this.chunkSequence(seg, chunkKeys, gen);
|
|
666
|
+
const chunks = this.chunkSequence(seg, chunkKeys, gen, epoch);
|
|
655
667
|
try {
|
|
656
668
|
for (const chunkKey of chunkKeys) {
|
|
657
669
|
const chunk = await chunks.take(chunkKey);
|
|
@@ -731,6 +743,26 @@ var SegmentEngine = class {
|
|
|
731
743
|
andNotBatches(seg, excludes, options) {
|
|
732
744
|
return this.combineBatches([seg], excludes, "all", "andNot", options);
|
|
733
745
|
}
|
|
746
|
+
/**
|
|
747
|
+
* {@link intersect}, as the chunks the result is made of: `{ chunkKey, bitmap }` in ascending key order, each a
|
|
748
|
+
* non-empty bitmap of the chunk's 16-bit remainders, cut to the read's range. What a materialisation writes
|
|
749
|
+
* straight into its new generation, with no id built on the way.
|
|
750
|
+
*
|
|
751
|
+
* **Each bitmap belongs to the caller, who may change it:** it is the combine's own accumulator or a bitmap built
|
|
752
|
+
* for the range's edge, never a chunk the cache holds or another operand's. The reads, the budget, the metric and the
|
|
753
|
+
* errors are the id reads' own.
|
|
754
|
+
*/
|
|
755
|
+
intersectChunks(segs, options) {
|
|
756
|
+
return this.combineChunks(segs, options?.exclude ?? [], "all", "intersect", options);
|
|
757
|
+
}
|
|
758
|
+
/** {@link union}, as chunks: see {@link intersectChunks}. */
|
|
759
|
+
unionChunks(segs, options) {
|
|
760
|
+
return this.combineChunks(segs, options?.exclude ?? [], "any", "union", options);
|
|
761
|
+
}
|
|
762
|
+
/** {@link andNot}, as chunks: see {@link intersectChunks}. */
|
|
763
|
+
andNotChunks(seg, excludes, options) {
|
|
764
|
+
return this.combineChunks([seg], excludes, "all", "andNot", options);
|
|
765
|
+
}
|
|
734
766
|
/**
|
|
735
767
|
* Everything a combine decides before it reads a chunk: validation, each operand's keys and generation, the
|
|
736
768
|
* absent-operand refusal, key alignment, the budget and the metric. `null` when the range is empty. The per-id
|
|
@@ -753,10 +785,11 @@ var SegmentEngine = class {
|
|
|
753
785
|
const w = windowOf(options);
|
|
754
786
|
if (w === "empty") return null;
|
|
755
787
|
const extract = async (seg) => {
|
|
788
|
+
const epoch = this.invalidations;
|
|
756
789
|
const all = await this.chunkKeys(seg);
|
|
757
790
|
const gen = await this.cacheVersion(seg);
|
|
758
791
|
const keys = w === null ? all : keysWithin(all, w);
|
|
759
|
-
return { seg, keys: new Set(keys), chunkless: all.length === 0, gen };
|
|
792
|
+
return { seg, keys: new Set(keys), chunkless: all.length === 0, gen, epoch };
|
|
760
793
|
};
|
|
761
794
|
const [operands, excludes] = await Promise.all([
|
|
762
795
|
Promise.all(segs.map(extract)),
|
|
@@ -801,14 +834,15 @@ var SegmentEngine = class {
|
|
|
801
834
|
const open4 = (o, wanted) => {
|
|
802
835
|
o.streamed = this.openStreamed(
|
|
803
836
|
o.seg,
|
|
804
|
-
common.filter(wanted),
|
|
837
|
+
wanted === void 0 ? common : common.filter(wanted),
|
|
805
838
|
o.gen,
|
|
839
|
+
o.epoch,
|
|
806
840
|
limit,
|
|
807
841
|
COMBINE_RANGE_START
|
|
808
842
|
);
|
|
809
|
-
if (o.streamed
|
|
843
|
+
if (o.streamed) streams.push(o.streamed);
|
|
810
844
|
};
|
|
811
|
-
for (const o of operands) open4(o, mode === "all" ?
|
|
845
|
+
for (const o of operands) open4(o, mode === "all" ? void 0 : (k) => o.keys.has(k));
|
|
812
846
|
if (streamExcludes) for (const e of excludes) open4(e, (k) => e.keys.has(k));
|
|
813
847
|
return {
|
|
814
848
|
w,
|
|
@@ -853,6 +887,28 @@ var SegmentEngine = class {
|
|
|
853
887
|
for (const stream of plan.streams) this.closeStreamed(stream);
|
|
854
888
|
}
|
|
855
889
|
}
|
|
890
|
+
/** The chunk read: the plan every combine shares, then each key's result, cut to the range at its edges. */
|
|
891
|
+
async *combineChunks(segs, excludeSegs, mode, op, options) {
|
|
892
|
+
const plan = await this.combinePlan(segs, excludeSegs, mode, op, options);
|
|
893
|
+
if (plan === null) return;
|
|
894
|
+
const { w, window } = plan;
|
|
895
|
+
try {
|
|
896
|
+
for (let slot = await window.next(); slot !== void 0; slot = await window.next()) {
|
|
897
|
+
if (!slot.result || slot.result.isEmpty) continue;
|
|
898
|
+
const bitmap = w !== null && isEdge(slot.key, w) ? this.cutToWindow(slot.result, slot.key, w) : slot.result;
|
|
899
|
+
if (!bitmap.isEmpty) yield { chunkKey: slot.key, bitmap };
|
|
900
|
+
}
|
|
901
|
+
} finally {
|
|
902
|
+
for (const stream of plan.streams) this.closeStreamed(stream);
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
/** An edge chunk's bitmap cut to the window, as a new bitmap: the remainders `edgeIds` would yield. */
|
|
906
|
+
cutToWindow(chunk, chunkKey, w) {
|
|
907
|
+
const from = chunkKey === w.loKey ? w.loRem : 0;
|
|
908
|
+
const to = chunkKey === w.hiKey ? w.hiRem : MAX_REMAINDER;
|
|
909
|
+
const rem = chunk.toUint32Array ? chunk.toUint32Array() : Uint32Array.from(chunk);
|
|
910
|
+
return this.codec.fromValues(rem.subarray(firstAbove(rem, from - 1), firstAbove(rem, to)));
|
|
911
|
+
}
|
|
856
912
|
/**
|
|
857
913
|
* The AND (or OR) of one chunk key across all operands, minus the excludes that hold it, or `null` if empty.
|
|
858
914
|
* Operand chunks are fetched **in parallel** (the spec's parallel byte-range reads).
|
|
@@ -902,25 +958,43 @@ var SegmentEngine = class {
|
|
|
902
958
|
return op.streamed === void 0 ? this.storageChunk({ ...op.seg, chunkKey }, op.gen) : this.streamedChunk(op.streamed, chunkKey);
|
|
903
959
|
}
|
|
904
960
|
/**
|
|
905
|
-
*
|
|
906
|
-
* `getChunks`, a segment with no generation, or nothing to read.
|
|
907
|
-
*
|
|
908
|
-
|
|
961
|
+
* `keys` of `seg` as a stream of coalesced ranges, or `undefined` when the read is per key: a source with no
|
|
962
|
+
* `getChunks`, a segment with no generation, or nothing to read. Nothing is requested, and no cache lookup made, until
|
|
963
|
+
* the first chunk is asked for (see {@link StreamedChunks}).
|
|
964
|
+
*/
|
|
965
|
+
openStreamed(seg, keys, gen, epoch, concurrency, rampStart) {
|
|
966
|
+
if (this.storage.getChunks === void 0 || gen === null || keys.length === 0) return void 0;
|
|
967
|
+
return {
|
|
968
|
+
seg,
|
|
969
|
+
gen,
|
|
970
|
+
epoch,
|
|
971
|
+
keys,
|
|
972
|
+
concurrency,
|
|
973
|
+
rampStart,
|
|
974
|
+
opened: false,
|
|
975
|
+
inStream: void 0,
|
|
976
|
+
stream: void 0,
|
|
977
|
+
invalidated: false
|
|
978
|
+
};
|
|
979
|
+
}
|
|
980
|
+
/**
|
|
981
|
+
* Open the stream of `streamed`, at its first uncached chunk `from`: over that chunk and the later keys the cache does
|
|
982
|
+
* not hold. Each later key is counted as a cache hit or miss here, so a key is counted once whichever way it is served.
|
|
909
983
|
*/
|
|
910
|
-
|
|
984
|
+
startStream(streamed, from) {
|
|
985
|
+
const { seg, gen } = streamed;
|
|
911
986
|
const getChunks = this.storage.getChunks;
|
|
912
|
-
|
|
913
|
-
let wanted = keys;
|
|
914
|
-
let inStream;
|
|
987
|
+
let wanted = streamed.keys.slice(streamed.keys.indexOf(from));
|
|
915
988
|
if (this.cache) {
|
|
916
|
-
const misses = [];
|
|
917
|
-
for (
|
|
989
|
+
const misses = [from];
|
|
990
|
+
for (let i = 1; i < wanted.length; i++) {
|
|
991
|
+
const chunkKey = wanted[i];
|
|
918
992
|
const hit = this.cache.get(this.chunkCacheKey({ ...seg, chunkKey }, gen));
|
|
919
993
|
if (!hit) misses.push(chunkKey);
|
|
920
994
|
if (this.metricsOn) this.metrics.onEvent({ kind: "cache", hit: hit !== void 0 });
|
|
921
995
|
}
|
|
996
|
+
if (misses.length < wanted.length) streamed.inStream = new Set(misses);
|
|
922
997
|
wanted = misses;
|
|
923
|
-
if (misses.length < keys.length) inStream = new Set(misses);
|
|
924
998
|
}
|
|
925
999
|
const onRequest = this.metricsOn ? (request) => {
|
|
926
1000
|
this.metrics.onEvent({
|
|
@@ -931,27 +1005,36 @@ var SegmentEngine = class {
|
|
|
931
1005
|
ms: request.ms
|
|
932
1006
|
});
|
|
933
1007
|
} : void 0;
|
|
934
|
-
|
|
1008
|
+
streamed.stream = new ChunkStream(
|
|
935
1009
|
getChunks.call(this.storage, seg, wanted, {
|
|
936
|
-
concurrency,
|
|
937
|
-
ramp: rampStart,
|
|
1010
|
+
concurrency: streamed.concurrency,
|
|
1011
|
+
ramp: streamed.rampStart,
|
|
938
1012
|
...onRequest === void 0 ? {} : { onRequest }
|
|
939
1013
|
})
|
|
940
1014
|
);
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
1015
|
+
streamed.opened = true;
|
|
1016
|
+
streamed.invalidated = this.storage.currentVersion === void 0 && this.invalidations !== streamed.epoch;
|
|
1017
|
+
this.openStreams.add(streamed);
|
|
944
1018
|
}
|
|
945
1019
|
closeStreamed(streamed) {
|
|
946
1020
|
streamed.stream?.close();
|
|
947
1021
|
this.openStreams.delete(streamed);
|
|
948
1022
|
}
|
|
949
1023
|
/**
|
|
950
|
-
* The decoded chunk at `chunkKey` of a streamed read: the
|
|
951
|
-
*
|
|
1024
|
+
* The decoded chunk at `chunkKey` of a streamed read: from the cache while the stream is unopened and the cache holds
|
|
1025
|
+
* it; else the next chunk of its stream, or, for a chunk the cache held when the stream opened, the cached instance
|
|
1026
|
+
* while it is still there, and a read of that chunk alone once it is not.
|
|
952
1027
|
*/
|
|
953
1028
|
streamedChunk(streamed, chunkKey) {
|
|
954
|
-
if (
|
|
1029
|
+
if (!streamed.opened) {
|
|
1030
|
+
const cache = this.cache;
|
|
1031
|
+
if (cache) {
|
|
1032
|
+
const hit = cache.get(this.chunkCacheKey({ ...streamed.seg, chunkKey }, streamed.gen));
|
|
1033
|
+
if (this.metricsOn) this.metrics.onEvent({ kind: "cache", hit: hit !== void 0 });
|
|
1034
|
+
if (hit) return Promise.resolve(hit);
|
|
1035
|
+
}
|
|
1036
|
+
this.startStream(streamed, chunkKey);
|
|
1037
|
+
} else if (streamed.inStream !== void 0 && !streamed.inStream.has(chunkKey)) {
|
|
955
1038
|
const ref = { ...streamed.seg, chunkKey };
|
|
956
1039
|
const hit = this.cache?.get(this.chunkCacheKey(ref, streamed.gen));
|
|
957
1040
|
return hit ? Promise.resolve(hit) : this.storageChunk(ref, streamed.gen);
|
|
@@ -1074,6 +1157,7 @@ var SegmentEngine = class {
|
|
|
1074
1157
|
*/
|
|
1075
1158
|
invalidate(ref) {
|
|
1076
1159
|
const prefix = segmentPrefix(ref);
|
|
1160
|
+
this.invalidations += 1;
|
|
1077
1161
|
for (const streamed of this.openStreams) {
|
|
1078
1162
|
if (segmentPrefix(streamed.seg) === prefix) streamed.invalidated = true;
|
|
1079
1163
|
}
|
|
@@ -1491,7 +1575,7 @@ var HOURS_PER_MONTH = 730;
|
|
|
1491
1575
|
var SECONDS_PER_MONTH = HOURS_PER_MONTH * 3600;
|
|
1492
1576
|
var GIB = 1024 ** 3;
|
|
1493
1577
|
var STORE_LOAD_PUT_CLASS = 1 + 1 / LIST_COLLECTION_CADENCE;
|
|
1494
|
-
var STORE_LOAD_POINTER_READS =
|
|
1578
|
+
var STORE_LOAD_POINTER_READS = 2 + 2 / LIST_COLLECTION_CADENCE;
|
|
1495
1579
|
var STORE_LOAD_EXISTENCE_CHECKS = 2 - 1 / LIST_COLLECTION_CADENCE;
|
|
1496
1580
|
var RETENTION_SWEEP_REQUESTS = deepFreeze({
|
|
1497
1581
|
conditionalDelete: {
|
|
@@ -2108,6 +2192,10 @@ var MemoryRegistryDriver = class {
|
|
|
2108
2192
|
this.rows.set(key, structuredClone(recordFromNew(ref, checked, this.now(), token)));
|
|
2109
2193
|
return { token };
|
|
2110
2194
|
}
|
|
2195
|
+
/**
|
|
2196
|
+
* A caller's `held` row (see `RegistryWriteOptions`) has no read to spare here: the token is compared with the stored row in the same step
|
|
2197
|
+
* as the write, in memory, so a stale one fails exactly as a lost race does.
|
|
2198
|
+
*/
|
|
2111
2199
|
async compareAndSwap(ref, expected, patch) {
|
|
2112
2200
|
validateSegmentRef(ref);
|
|
2113
2201
|
const checked = validateRegistryPatch(patch);
|
|
@@ -2463,6 +2551,10 @@ var LocalFsRegistryDriver = class {
|
|
|
2463
2551
|
return { token };
|
|
2464
2552
|
});
|
|
2465
2553
|
}
|
|
2554
|
+
/**
|
|
2555
|
+
* A caller's `held` row (see `RegistryWriteOptions`) has no read to spare here: the row's file is read under its lock and its token compared
|
|
2556
|
+
* there, a local read and no request, so a stale one fails exactly as a lost race does.
|
|
2557
|
+
*/
|
|
2466
2558
|
async compareAndSwap(ref, expected, patch) {
|
|
2467
2559
|
const checked = validateRegistryPatch(patch);
|
|
2468
2560
|
const path = registryRowPath(this.root, ref);
|
|
@@ -3717,6 +3809,12 @@ var DecodedLoadInput = class {
|
|
|
3717
3809
|
}
|
|
3718
3810
|
bitmap;
|
|
3719
3811
|
};
|
|
3812
|
+
var ChunkLoadInput = class {
|
|
3813
|
+
constructor(chunks) {
|
|
3814
|
+
this.chunks = chunks;
|
|
3815
|
+
}
|
|
3816
|
+
chunks;
|
|
3817
|
+
};
|
|
3720
3818
|
function tagOf(value) {
|
|
3721
3819
|
return Object.prototype.toString.call(value).slice(8, -1);
|
|
3722
3820
|
}
|
|
@@ -5057,7 +5155,9 @@ async function publishGeneration(registry, key, options = {}) {
|
|
|
5057
5155
|
let lastRecord;
|
|
5058
5156
|
for (let attempt = 0; attempt < 5; attempt++) {
|
|
5059
5157
|
const reused = attempt === 0 && options.row !== void 0;
|
|
5158
|
+
const holdsRow = fresh === void 0;
|
|
5060
5159
|
const record = fresh !== void 0 ? fresh : reused ? options.row ?? null : await registry.get(key);
|
|
5160
|
+
const write = holdsRow ? { held: record } : void 0;
|
|
5061
5161
|
fresh = void 0;
|
|
5062
5162
|
unanswered = void 0;
|
|
5063
5163
|
lastRecord = record;
|
|
@@ -5089,22 +5189,31 @@ async function publishGeneration(registry, key, options = {}) {
|
|
|
5089
5189
|
return false;
|
|
5090
5190
|
}
|
|
5091
5191
|
if (record === null) {
|
|
5092
|
-
await registry.create(
|
|
5093
|
-
|
|
5094
|
-
|
|
5095
|
-
|
|
5096
|
-
|
|
5192
|
+
await registry.create(
|
|
5193
|
+
key,
|
|
5194
|
+
{
|
|
5195
|
+
currentGen: key.generation,
|
|
5196
|
+
wrappedDeks: options.wrappedDeks,
|
|
5197
|
+
...options.summary === void 0 ? {} : { summary: options.summary }
|
|
5198
|
+
},
|
|
5199
|
+
write
|
|
5200
|
+
);
|
|
5097
5201
|
} else if (record.status === "destroyed") {
|
|
5098
5202
|
throw new ValidationError(
|
|
5099
5203
|
`segment "${key.segment}" was destroyed (crypto-shredded) while generation ${key.generation} was being written \u2014 refusing to publish it; the written object is unreadable. Use a new segment.`
|
|
5100
5204
|
);
|
|
5101
5205
|
} else if (record.currentGen === null) {
|
|
5102
5206
|
refuseCleartextOntoKey(key, record, options.cleartext);
|
|
5103
|
-
await registry.compareAndSwap(
|
|
5104
|
-
|
|
5105
|
-
|
|
5106
|
-
|
|
5107
|
-
|
|
5207
|
+
await registry.compareAndSwap(
|
|
5208
|
+
key,
|
|
5209
|
+
record.token,
|
|
5210
|
+
{
|
|
5211
|
+
currentGen: key.generation,
|
|
5212
|
+
...options.wrappedDeks === void 0 ? {} : { wrappedDeks: options.wrappedDeks },
|
|
5213
|
+
...options.summary === void 0 ? {} : { summary: options.summary }
|
|
5214
|
+
},
|
|
5215
|
+
write
|
|
5216
|
+
);
|
|
5108
5217
|
} else if (record.currentGen > key.generation) {
|
|
5109
5218
|
return false;
|
|
5110
5219
|
} else if (record.currentGen === key.generation) {
|
|
@@ -5117,10 +5226,15 @@ async function publishGeneration(registry, key, options = {}) {
|
|
|
5117
5226
|
);
|
|
5118
5227
|
}
|
|
5119
5228
|
refuseCleartextOntoKey(key, record, options.cleartext);
|
|
5120
|
-
await registry.compareAndSwap(
|
|
5121
|
-
|
|
5122
|
-
|
|
5123
|
-
|
|
5229
|
+
await registry.compareAndSwap(
|
|
5230
|
+
key,
|
|
5231
|
+
record.token,
|
|
5232
|
+
{
|
|
5233
|
+
currentGen: key.generation,
|
|
5234
|
+
...options.summary === void 0 ? {} : { summary: options.summary }
|
|
5235
|
+
},
|
|
5236
|
+
write
|
|
5237
|
+
);
|
|
5124
5238
|
}
|
|
5125
5239
|
return true;
|
|
5126
5240
|
} catch (err) {
|
|
@@ -5206,6 +5320,8 @@ async function bulkLoadAhead(driver, ref, ahead, ids, options = {}) {
|
|
|
5206
5320
|
await pause();
|
|
5207
5321
|
chunks = ids.bitmap.encodeChunks();
|
|
5208
5322
|
await pause();
|
|
5323
|
+
} else if (ids instanceof ChunkLoadInput) {
|
|
5324
|
+
chunks = encodeEach(rangeCheckedAtWrite(await collectChunks(ids, codec, options.clock)));
|
|
5209
5325
|
} else {
|
|
5210
5326
|
chunks = encodeEach(
|
|
5211
5327
|
await bucketIds(ids instanceof DecodedLoadInput ? ids.bitmap : ids, codec, options.clock)
|
|
@@ -5298,6 +5414,52 @@ async function bulkLoadAhead(driver, ref, ahead, ids, options = {}) {
|
|
|
5298
5414
|
fingerprint
|
|
5299
5415
|
};
|
|
5300
5416
|
}
|
|
5417
|
+
function* rangeCheckedAtWrite(chunks) {
|
|
5418
|
+
for (const chunk of chunks) {
|
|
5419
|
+
const max = chunk.bitmap.maximum?.();
|
|
5420
|
+
if (max !== void 0 && max > MAX_REMAINDER) {
|
|
5421
|
+
throw new ValidationError(
|
|
5422
|
+
`chunk ${chunk.chunkKey} holds ${max}, outside the 16-bit range [0, ${MAX_REMAINDER}]`
|
|
5423
|
+
);
|
|
5424
|
+
}
|
|
5425
|
+
yield chunk;
|
|
5426
|
+
}
|
|
5427
|
+
}
|
|
5428
|
+
async function collectChunks(input, codec, clock) {
|
|
5429
|
+
const chunks = [];
|
|
5430
|
+
const tick = yieldEvery(clock);
|
|
5431
|
+
let last = -1;
|
|
5432
|
+
for await (const chunk of input.chunks) {
|
|
5433
|
+
if (typeof chunk !== "object" || chunk === null) {
|
|
5434
|
+
throw new ValidationError(
|
|
5435
|
+
`a chunk must be an object with a chunkKey and a bitmap; got ${String(chunk)}`
|
|
5436
|
+
);
|
|
5437
|
+
}
|
|
5438
|
+
const { chunkKey, bitmap } = chunk;
|
|
5439
|
+
if (codec.owns?.(bitmap) !== true) {
|
|
5440
|
+
throw new ValidationError(
|
|
5441
|
+
`chunk ${String(chunkKey)}: its bitmap is not one this load's codec made`
|
|
5442
|
+
);
|
|
5443
|
+
}
|
|
5444
|
+
if (!Number.isInteger(chunkKey) || chunkKey < 0 || chunkKey > MAX_REMAINDER) {
|
|
5445
|
+
throw new ValidationError(`a chunk's key must be an integer in [0, 65535]; got ${chunkKey}`);
|
|
5446
|
+
}
|
|
5447
|
+
if (chunkKey <= last) {
|
|
5448
|
+
throw new ValidationError(`chunk ${chunkKey} does not ascend: the last was ${last}`);
|
|
5449
|
+
}
|
|
5450
|
+
last = chunkKey;
|
|
5451
|
+
const max = bitmap.maximum?.();
|
|
5452
|
+
if (max !== void 0 && max > MAX_REMAINDER) {
|
|
5453
|
+
throw new ValidationError(
|
|
5454
|
+
`chunk ${chunkKey} holds ${max}, outside the 16-bit range [0, ${MAX_REMAINDER}]`
|
|
5455
|
+
);
|
|
5456
|
+
}
|
|
5457
|
+
chunks.push({ chunkKey, bitmap });
|
|
5458
|
+
const pause = tick();
|
|
5459
|
+
if (pause !== null) await pause;
|
|
5460
|
+
}
|
|
5461
|
+
return chunks;
|
|
5462
|
+
}
|
|
5301
5463
|
async function bucketIds(ids, codec, clock) {
|
|
5302
5464
|
const byChunk = /* @__PURE__ */ new Map();
|
|
5303
5465
|
const pendingByChunk = /* @__PURE__ */ new Map();
|
|
@@ -5575,7 +5737,25 @@ async function currentCardinality(ref, deps, record, unwrapped) {
|
|
|
5575
5737
|
for (const n of reader.cardinalities().values()) total += n;
|
|
5576
5738
|
return { cardinality: total, fromSummary: false };
|
|
5577
5739
|
}
|
|
5578
|
-
|
|
5740
|
+
function loadSegment(ref, input, deps, options = {}) {
|
|
5741
|
+
return runLoad(ref, (codec) => prepareLoadInput(input, codec), deps, options);
|
|
5742
|
+
}
|
|
5743
|
+
function loadSegmentChunks(ref, chunks, deps, options = {}) {
|
|
5744
|
+
return runLoad(
|
|
5745
|
+
ref,
|
|
5746
|
+
() => {
|
|
5747
|
+
if (typeof chunks !== "object" || chunks === null || !(Symbol.asyncIterator in chunks)) {
|
|
5748
|
+
throw new ValidationError(
|
|
5749
|
+
"loadSegmentChunks takes an async iterable of { chunkKey, bitmap }"
|
|
5750
|
+
);
|
|
5751
|
+
}
|
|
5752
|
+
return new ChunkLoadInput(chunks);
|
|
5753
|
+
},
|
|
5754
|
+
deps,
|
|
5755
|
+
options
|
|
5756
|
+
);
|
|
5757
|
+
}
|
|
5758
|
+
async function runLoad(ref, prepare, deps, options) {
|
|
5579
5759
|
validateUserRef(ref);
|
|
5580
5760
|
const codec = requireCodec(deps.codec, "loadSegment");
|
|
5581
5761
|
const keep = options.keep ?? 1;
|
|
@@ -5601,7 +5781,7 @@ async function loadSegment(ref, input, deps, options = {}) {
|
|
|
5601
5781
|
throw new ValidationError(message);
|
|
5602
5782
|
});
|
|
5603
5783
|
const audit = safeAudit(options.audit ?? NOOP_AUDIT);
|
|
5604
|
-
const ids =
|
|
5784
|
+
const ids = prepare(codec);
|
|
5605
5785
|
assertRegistryCanWrite(deps.registry, "load");
|
|
5606
5786
|
const row = await deps.registry.get(ref);
|
|
5607
5787
|
const fromToken = row?.token;
|
|
@@ -7195,6 +7375,7 @@ export {
|
|
|
7195
7375
|
listGenerations,
|
|
7196
7376
|
listSegments,
|
|
7197
7377
|
loadSegment,
|
|
7378
|
+
loadSegmentChunks,
|
|
7198
7379
|
mapWithConcurrency,
|
|
7199
7380
|
readRetentionPolicy,
|
|
7200
7381
|
resolveBudget,
|