Make Map use Array<[K, V]> instead of Array<K | V> (#1538)

This commit is contained in:
Erik Arvidsson
2016-05-18 15:16:08 -07:00
parent 4d47b9cf18
commit d67d1fb924
12 changed files with 98 additions and 105 deletions

View File

@@ -1,6 +1,6 @@
{
"name": "@attic/noms",
"version": "31.0.0",
"version": "32.0.0",
"description": "Noms JS SDK",
"repository": "https://github.com/attic-labs/noms",
"main": "dist/commonjs/noms.js",

View File

@@ -90,7 +90,7 @@ suite('validate type', () => {
test('map', async () => {
const mapOfNumberToStringType = makeMapType(numberType, stringType);
const m = await newMap([0, 'a', 2, 'b']);
const m = await newMap([[0, 'a'], [2, 'b']]);
assertSubtype(mapOfNumberToStringType, m);
assertAll(mapOfNumberToStringType, m);
@@ -174,7 +174,7 @@ suite('validate type', () => {
assertSubtype(mt, await newMap([]));
// Map<> not a subtype of Map<Number, Number>
assertInvalid(makeMapType(makeUnionType([]), makeUnionType([])), await newMap([1, 2]));
assertInvalid(makeMapType(makeUnionType([]), makeUnionType([])), await newMap([[1, 2]]));
});
test('struct subtype by name', () => {

View File

@@ -69,7 +69,7 @@ suite('compare.js', () => {
// The order of these are done by the hash.
ds.writeValue(10),
await newSet([0, 1, 2, 3]),
await newMap([0, 1, 2, 3]),
await newMap([[0, 1], [2, 3]]),
boolType,
await newBlob(new Uint8Array([0, 1, 2, 3])),
await newList([0, 1, 2, 3]),

View File

@@ -155,7 +155,7 @@ suite('Database', () => {
const commit = await newCommit('foo', []);
const commitRef = ds.writeValue(commit);
const datasets = await newMap(['foo', commitRef]);
const datasets = await newMap([['foo', commitRef]]);
const rootRef = ds.writeValue(datasets).targetRef;
assert.isTrue(await bs.updateRoot(rootRef, emptyRef));
ds = new Database(bs); // refresh the datasets

View File

@@ -213,7 +213,7 @@ suite('Decode', () => {
const v: Map<number, number> = r.readValue();
invariant(v instanceof Map);
const m = new Map(newMapLeafSequence(ds, [{key: 0, value: 1}, {key: 2, value: 3}]));
const m = new Map(newMapLeafSequence(ds, [[0, 1], [2, 3]]));
assert.isTrue(equals(v, m));
});
@@ -231,7 +231,7 @@ suite('Decode', () => {
const v: Map<RefValue<Value>, number> = r.readValue();
invariant(v instanceof Map);
const m = new Map(newMapLeafSequence(ds, [{key: rv1, value: 2}, {key: rv2, value: 4}]));
const m = new Map(newMapLeafSequence(ds, [[rv1, 2], [rv2, 4]]));
assert.isTrue(equals(v, m));
});
@@ -243,7 +243,7 @@ suite('Decode', () => {
const v: Map<number, number> = r.readValue();
invariant(v instanceof Map);
const m = new Map(newMapLeafSequence(ds, [{key: 0, value: 1}, {key: 2, value: 3}]));
const m = new Map(newMapLeafSequence(ds, [[0, 1], [2, 3]]));
assert.isTrue(equals(v, m));
});

View File

@@ -176,7 +176,7 @@ export class JsonArrayReader {
while (!this.atEnd()) {
const k = this.readValue();
const v = this.readValue();
entries.push({key: k, value: v});
entries.push([k, v]);
}
return new MapLeafSequence(this._ds, t, entries);

View File

@@ -152,8 +152,7 @@ suite('Encode', () => {
const ds = new Database(makeTestingBatchStore());
const w = new JsonArrayWriter(ds);
const v = new Map(newMapLeafSequence(ds, [{key: 'a', value: false},
{key:'b', value:true}]));
const v = new Map(newMapLeafSequence(ds, [['a', false], ['b', true]]));
w.writeValue(v);
assert.deepEqual([Kind.Map, Kind.String, Kind.Bool, false,
[Kind.String, 'a', Kind.Bool, false, Kind.String, 'b', Kind.Bool, true]], w.array);
@@ -165,8 +164,8 @@ suite('Encode', () => {
// Map<Map<String, Number>, Set<Bool>>({{'a': 0}: {true}})
const s = new Set(newSetLeafSequence(ds, [true]));
const m1 = new Map(newMapLeafSequence(ds, [{key: 'a', value: 0}]));
const v = new Map(newMapLeafSequence(ds, [{key: m1, value: s}]));
const m1 = new Map(newMapLeafSequence(ds, [['a', 0]]));
const v = new Map(newMapLeafSequence(ds, [[m1, s]]));
w.writeValue(v);
assert.deepEqual([Kind.Map,
Kind.Map, Kind.String, Kind.Number,

View File

@@ -173,8 +173,8 @@ export class JsonArrayWriter {
invariant(sequence instanceof MapLeafSequence);
const w2 = new JsonArrayWriter(this._vw);
sequence.items.forEach(entry => {
w2.writeValue(entry.key);
w2.writeValue(entry.value);
w2.writeValue(entry[0]);
w2.writeValue(entry[1]);
});
this.write(w2.array);
break;

View File

@@ -41,10 +41,11 @@ suite('BuildMap', () => {
test('unique keys - strings', async () => {
const kvs = [
'hello', 'world',
'foo', 'bar',
'bar', 'foo',
'hello', 'foo'];
['hello', 'world'],
['foo', 'bar'],
['bar', 'foo'],
['hello', 'foo'],
];
const m = await newMap(kvs);
assert.strictEqual(3, m.size);
assert.strictEqual('foo', await m.get('hello'));
@@ -52,11 +53,12 @@ suite('BuildMap', () => {
test('unique keys - number', async () => {
const kvs = [
4, 1,
0, 2,
1, 2,
3, 4,
1, 5];
[4, 1],
[0, 2],
[1, 2],
[3, 4],
[1, 5],
];
const m = await newMap(kvs);
assert.strictEqual(4, m.size);
assert.strictEqual(5, await m.get(1));
@@ -65,7 +67,7 @@ suite('BuildMap', () => {
test('LONG: set of n numbers', async () => {
const kvs = [];
for (let i = 0; i < testMapSize; i++) {
kvs.push(i, i + 1);
kvs.push([i, i + 1]);
}
const m = await newMap(kvs);
@@ -91,10 +93,10 @@ suite('BuildMap', () => {
test('LONG: map of ref to ref, set of n numbers', async () => {
const kvs = [];
for (let i = 0; i < testMapSize; i++) {
kvs.push(i, i + 1);
kvs.push([i, i + 1]);
}
const kvRefs = kvs.map(n => new RefValue(newStruct('num', {n})));
const kvRefs = kvs.map(entry => entry.map(n => new RefValue(newStruct('num', {n}))));
const m = await newMap(kvRefs);
assert.strictEqual(m.ref.toString(), 'sha1-5c9a17f6da0ebfebc1f82f498ac46992fad85250');
const height = deriveCollectionHeight(m);
@@ -106,7 +108,7 @@ suite('BuildMap', () => {
test('LONG: set', async () => {
const kvs = [];
for (let i = 0; i < testMapSize - 10; i++) {
kvs.push(i, i + 1);
kvs.push([i, i + 1]);
}
let m = await newMap(kvs);
@@ -121,7 +123,7 @@ suite('BuildMap', () => {
test('LONG: set existing', async () => {
const kvs = [];
for (let i = 0; i < testMapSize; i++) {
kvs.push(i, i + 1);
kvs.push([i, i + 1]);
}
let m = await newMap(kvs);
@@ -136,7 +138,7 @@ suite('BuildMap', () => {
test('LONG: remove', async () => {
const kvs = [];
for (let i = 0; i < testMapSize + 10; i++) {
kvs.push(i, i + 1);
kvs.push([i, i + 1]);
}
let m = await newMap(kvs);
@@ -153,7 +155,7 @@ suite('BuildMap', () => {
const kvs = [];
for (let i = 0; i < testMapSize; i++) {
kvs.push(i, i + 1);
kvs.push([i, i + 1]);
}
const m = await newMap(kvs);
@@ -161,15 +163,15 @@ suite('BuildMap', () => {
const r = ds.writeValue(m).targetRef;
const m2 = await ds.readValue(r);
const outKvs = [];
await m2.forEach((v, k) => outKvs.push(k, v));
await m2.forEach((v, k) => outKvs.push([k, v]));
assert.deepEqual(kvs, outKvs);
assert.strictEqual(testMapSize, m2.size);
invariant(m2 instanceof Map);
const m3 = await m2.remove(testMapSize - 1);
const outKvs2 = [];
await m3.forEach((v, k) => outKvs2.push(k, v));
kvs.splice(testMapSize * 2 - 2, 2);
await m3.forEach((v, k) => outKvs2.push([k, v]));
kvs.splice(testMapSize * 1 - 1, 1);
assert.deepEqual(kvs, outKvs2);
assert.strictEqual(testMapSize - 1, m3.size);
});
@@ -193,7 +195,7 @@ suite('BuildMap', () => {
} else {
numbers.push(v);
}
kvs.push(v, i);
kvs.push([v, i]);
keys.push(v);
}
@@ -267,7 +269,7 @@ suite('MapLeaf', () => {
let m = newMap([]);
assert.isTrue(m.isEmpty());
assert.strictEqual(0, m.size);
m = newMap([{key: 'a', value: false}, {key: 'k', value: true}]);
m = newMap([['a', false], ['k', true]]);
assert.isFalse(m.isEmpty());
assert.strictEqual(2, m.size);
});
@@ -275,7 +277,7 @@ suite('MapLeaf', () => {
test('has', async () => {
const ds = new Database(makeTestingBatchStore());
const m = new Map(
newMapLeafSequence(ds, [{key: 'a', value: false}, {key: 'k', value: true}]));
newMapLeafSequence(ds, [['a', false], ['k', true]]));
assert.isTrue(await m.has('a'));
assert.isFalse(await m.has('b'));
assert.isTrue(await m.has('k'));
@@ -285,7 +287,7 @@ suite('MapLeaf', () => {
test('first/last/get', async () => {
const ds = new Database(makeTestingBatchStore());
const m = new Map(
newMapLeafSequence(ds, [{key: 'a', value: 4}, {key: 'k', value: 8}]));
newMapLeafSequence(ds, [['a', 4], ['k', 8]]));
assert.deepEqual(['a', 4], await m.first());
assert.deepEqual(['k', 8], await m.last());
@@ -299,7 +301,7 @@ suite('MapLeaf', () => {
test('forEach', async () => {
const ds = new Database(makeTestingBatchStore());
const m = new Map(
newMapLeafSequence(ds, [{key: 'a', value: 4}, {key: 'k', value: 8}]));
newMapLeafSequence(ds, [['a', 4], ['k', 8]]));
const kv = [];
await m.forEach((v, k) => { kv.push(k, v); });
@@ -316,8 +318,8 @@ suite('MapLeaf', () => {
};
await test([]);
await test([{key: 'a', value: 4}]);
await test([{key: 'a', value: 4}, {key: 'k', value: 8}]);
await test([['a', 4]]);
await test([['a', 4], ['k', 8]]);
});
test('LONG: iteratorAt', async () => {
@@ -327,14 +329,14 @@ suite('MapLeaf', () => {
assert.deepEqual([], await flatten(build([]).iteratorAt('a')));
{
const kv = [{key: 'b', value: 5}];
const kv = [['b', 5]];
assert.deepEqual(kv, await flatten(build(kv).iteratorAt('a')));
assert.deepEqual(kv, await flatten(build(kv).iteratorAt('b')));
assert.deepEqual([], await flatten(build(kv).iteratorAt('c')));
}
{
const kv = [{key: 'b', value: 5}, {key: 'd', value: 10}];
const kv = [['b', 5], ['d', 10]];
assert.deepEqual(kv, await flatten(build(kv).iteratorAt('a')));
assert.deepEqual(kv, await flatten(build(kv).iteratorAt('b')));
assert.deepEqual(kv.slice(1), await flatten(build(kv).iteratorAt('c')));
@@ -350,7 +352,7 @@ suite('MapLeaf', () => {
const r3 = ds.writeValue('b');
const r4 = ds.writeValue(false);
const m = new Map(
newMapLeafSequence(ds, [{key: r1, value: r2}, {key: r3, value: r4}]));
newMapLeafSequence(ds, [[r1, r2], [r3, r4]]));
assert.strictEqual(4, m.chunks.length);
assert.isTrue(equals(r1, m.chunks[0]));
assert.isTrue(equals(r2, m.chunks[1]));
@@ -362,17 +364,13 @@ suite('MapLeaf', () => {
suite('CompoundMap', () => {
function build(vwr: ValueReadWriter): Array<Map> {
const l1 = new Map(newMapLeafSequence(vwr, [{key: 'a', value: false},
{key:'b', value:false}]));
const l1 = new Map(newMapLeafSequence(vwr, [['a', false], ['b', false]]));
const r1 = vwr.writeValue(l1);
const l2 = new Map(newMapLeafSequence(vwr, [{key: 'e', value: true},
{key:'f', value:true}]));
const l2 = new Map(newMapLeafSequence(vwr, [['e', true], ['f', true]]));
const r2 = vwr.writeValue(l2);
const l3 = new Map(newMapLeafSequence(vwr, [{key: 'h', value: false},
{key:'i', value:true}]));
const l3 = new Map(newMapLeafSequence(vwr, [['h', false], ['i', true]]));
const r3 = vwr.writeValue(l3);
const l4 = new Map(newMapLeafSequence(vwr, [{key: 'm', value: true},
{key:'n', value:false}]));
const l4 = new Map(newMapLeafSequence(vwr, [['m', true], ['n', false]]));
const r4 = vwr.writeValue(l4);
const m1 = new Map(newMapMetaSequence(vwr, [new MetaTuple(r1, 'b', 2),
@@ -454,9 +452,8 @@ suite('CompoundMap', () => {
test('iterator', async () => {
const ds = new Database(makeTestingBatchStore());
const [c] = build(ds);
const expected = [{key: 'a', value: false}, {key: 'b', value: false}, {key: 'e', value: true},
{key: 'f', value: true}, {key: 'h', value: false}, {key: 'i', value: true},
{key: 'm', value: true}, {key: 'n', value: false}];
const expected = [['a', false], ['b', false], ['e', true], ['f', true], ['h', false],
['i', true], ['m', true], ['n', false]];
assert.deepEqual(expected, await flatten(c.iterator()));
assert.deepEqual(expected, await flattenParallel(c.iterator(), expected.length));
});
@@ -464,9 +461,8 @@ suite('CompoundMap', () => {
test('LONG: iteratorAt', async () => {
const ds = new Database(makeTestingBatchStore());
const [c] = build(ds);
const entries = [{key: 'a', value: false}, {key: 'b', value: false}, {key: 'e', value: true},
{key: 'f', value: true}, {key: 'h', value: false}, {key: 'i', value: true},
{key: 'm', value: true}, {key: 'n', value: false}];
const entries = [['a', false], ['b', false], ['e', true], ['f', true], ['h', false],
['i', true], ['m', true], ['n', false]];
const offsets = {
_: 0, a: 0,
b: 1,
@@ -496,8 +492,7 @@ suite('CompoundMap', () => {
await iter.return();
}
}
assert.deepEqual([{key: 'a', value: false}, {key: 'b', value: false}, {key: 'e', value: true},
{key: 'f', value: true}, {key: 'h', value: false}],
assert.deepEqual([['a', false], ['b', false], ['e', true], ['f', true], ['h', false]],
values);
});
@@ -506,8 +501,8 @@ suite('CompoundMap', () => {
const [c] = build(ds);
const iter = c.iterator();
const values = await Promise.all([iter.next(), iter.next(), iter.return(), iter.next()]);
assert.deepEqual([{done: false, value: {key: 'a', value: false}},
{done: false, value: {key: 'b', value: false}},
assert.deepEqual([{done: false, value: ['a', false]},
{done: false, value: ['b', false]},
{done: true}, {done: true}],
values);
});
@@ -528,18 +523,18 @@ suite('CompoundMap', () => {
for (let i = 0; i < mapSize; i++) {
const r = Math.random();
if (r <= inM1) {
kv1.push(i, i + '');
kv1.push([i, i + '']);
removed.push(i);
} else if (r <= inM1 + inM2) {
kv2.push(i, i + '');
kv2.push([i, i + '']);
added.push(i);
} else if (r <= inM1 + inM2 + inBoth) {
kv1.push(i, i + '');
kv2.push(i, i + '_');
kv1.push([i, i + '']);
kv2.push([i, i + '_']);
modified.push(i);
} else {
kv1.push(i, i + '');
kv2.push(i, i + '');
kv1.push([i, i + '']);
kv2.push([i, i + '']);
}
}

View File

@@ -20,10 +20,10 @@ import diff from './ordered-sequence-diff.js';
import {Value} from './value.js';
import {Kind} from './noms-kind.js';
export type MapEntry<K: valueOrPrimitive, V: valueOrPrimitive> = {
key: K,
value: V,
};
export type MapEntry<K: valueOrPrimitive, V: valueOrPrimitive> = [K, V];
const KEY = 0;
const VALUE = 1;
const mapWindowSize = 1;
const mapPattern = ((1 << 6) | 0) - 1;
@@ -33,7 +33,7 @@ function newMapLeafChunkFn<K: valueOrPrimitive, V: valueOrPrimitive>(vr: ?ValueR
return (items: Array<MapEntry<K, V>>) => {
let indexValue: ?valueOrPrimitive = null;
if (items.length > 0) {
indexValue = items[items.length - 1].key;
indexValue = items[items.length - 1][KEY];
if (indexValue instanceof Value) {
indexValue = new RefValue(indexValue);
}
@@ -48,7 +48,7 @@ function newMapLeafChunkFn<K: valueOrPrimitive, V: valueOrPrimitive>(vr: ?ValueR
function newMapLeafBoundaryChecker<K: valueOrPrimitive, V: valueOrPrimitive>():
BoundaryChecker<MapEntry<K, V>> {
return new BuzHashBoundaryChecker(mapWindowSize, sha1Size, mapPattern,
(entry: MapEntry<K, V>) => getRefOfValue(entry.key).digest);
(entry: MapEntry<K, V>) => getRefOfValue(entry[KEY]).digest);
}
export function removeDuplicateFromOrdered<T>(elems: Array<T>,
@@ -69,24 +69,22 @@ export function removeDuplicateFromOrdered<T>(elems: Array<T>,
}
function compareKeys(v1, v2) {
return compare(v1.key, v2.key);
return compare(v1[KEY], v2[KEY]);
}
function buildMapData<K: valueOrPrimitive, V: valueOrPrimitive>(kvs: Array<K | V>):
Array<MapEntry<K, V>> {
function buildMapData<K: valueOrPrimitive, V: valueOrPrimitive>(
kvs: Array<MapEntry<K, V>>): Array<MapEntry<K, V>> {
// TODO: Assert k & v are of correct type
const entries = [];
for (let i = 0; i < kvs.length; i += 2) {
// $FlowIssue: gets confused about the K | V type.
const key: K = kvs[i], value: V = kvs[i + 1];
entries.push({key, value});
for (let i = 0; i < kvs.length; i++) {
entries.push([kvs[i][KEY], kvs[i][VALUE]]);
}
entries.sort(compareKeys);
return removeDuplicateFromOrdered(entries, compareKeys);
}
export function newMap<K: valueOrPrimitive, V: valueOrPrimitive>(kvs: Array<K | V>):
Promise<Map<K, V>> {
export function newMap<K: valueOrPrimitive, V: valueOrPrimitive>(
kvs: Array<MapEntry<K, V>>): Promise<Map<K, V>> {
return chunkSequence(null, buildMapData(kvs), 0, newMapLeafChunkFn(null),
newOrderedMetaSequenceChunkFn(Kind.Map, null),
newMapLeafBoundaryChecker(),
@@ -100,21 +98,20 @@ export default class Map<K: valueOrPrimitive, V: valueOrPrimitive> extends
return cursor.valid && equals(cursor.getCurrentKey(), key);
}
async _firstOrLast(last: boolean): Promise<?[K, V]> {
async _firstOrLast(last: boolean): Promise<?MapEntry<K, V>> {
const cursor = await this.sequence.newCursorAt(null, false, last);
if (!cursor.valid) {
return undefined;
}
const entry = cursor.getCurrent();
return [entry.key, entry.value];
return cursor.getCurrent();
}
first(): Promise<?[K, V]> {
first(): Promise<?MapEntry<K, V>> {
return this._firstOrLast(false);
}
last(): Promise<?[K, V]> {
last(): Promise<?MapEntry<K, V>> {
return this._firstOrLast(true);
}
@@ -125,13 +122,13 @@ export default class Map<K: valueOrPrimitive, V: valueOrPrimitive> extends
}
const entry = cursor.getCurrent();
return equals(entry.key, key) ? entry.value : undefined;
return equals(entry[KEY], key) ? entry[VALUE] : undefined;
}
async forEach(cb: (v: V, k: K) => void): Promise<void> {
const cursor = await this.sequence.newCursorAt(null);
return cursor.iter(entry => {
cb(entry.value, entry.key);
cb(entry[VALUE], entry[KEY]);
return false;
});
}
@@ -158,14 +155,14 @@ export default class Map<K: valueOrPrimitive, V: valueOrPrimitive> extends
const cursor = await this.sequence.newCursorAt(key, true);
if (cursor.valid && equals(cursor.getCurrentKey(), key)) {
const entry = cursor.getCurrent();
if (equals(entry.value, value)) {
if (equals(entry[VALUE], value)) {
return this;
}
remove = 1;
}
return this._splice(cursor, [{key: key, value: value}], remove);
return this._splice(cursor, [[key, value]], remove);
}
async remove(key: K): Promise<Map<K, V>> {
@@ -192,22 +189,22 @@ export default class Map<K: valueOrPrimitive, V: valueOrPrimitive> extends
export class MapLeafSequence<K: valueOrPrimitive, V: valueOrPrimitive> extends
OrderedSequence<K, MapEntry<K, V>> {
getKey(idx: number): K {
return this.items[idx].key;
return this.items[idx][KEY];
}
equalsAt(idx: number, other: MapEntry<K, V>): boolean {
const entry = this.items[idx];
return equals(entry.key, other.key) && equals(entry.value, other.value);
return equals(entry[KEY], other[KEY]) && equals(entry[VALUE], other[VALUE]);
}
get chunks(): Array<RefValue> {
const chunks = [];
for (const entry of this.items) {
if (entry.key instanceof Value) {
chunks.push(...entry.key.chunks);
if (entry[KEY] instanceof Value) {
chunks.push(...entry[KEY].chunks);
}
if (entry.value instanceof Value) {
chunks.push(...entry.value.chunks);
if (entry[VALUE] instanceof Value) {
chunks.push(...entry[VALUE].chunks);
}
}
return chunks;
@@ -219,8 +216,8 @@ export function newMapLeafSequence<K: valueOrPrimitive, V: valueOrPrimitive>(vr:
const kt = [];
const vt = [];
for (let i = 0; i < items.length; i++) {
kt.push(getTypeOfValue(items[i].key));
vt.push(getTypeOfValue(items[i].value));
kt.push(getTypeOfValue(items[i][KEY]));
vt.push(getTypeOfValue(items[i][VALUE]));
}
const t = makeMapType(makeUnionType(kt), makeUnionType(vt));
return new MapLeafSequence(vr, t, items);

View File

@@ -59,7 +59,7 @@ suite('Path', () => {
});
test('map', async () => {
const v = await newMap([1, 'foo', 'two', 'bar', false, 23, 2.3, 4.5]);
const v = await newMap([[1, 'foo'], ['two', 'bar'], [false, 23], [2.3, 4.5]]);
await assertPathEqual('foo', v, new Path().addIndex(1));
await assertPathEqual('bar', v, new Path().addIndex('two'));
@@ -69,8 +69,8 @@ suite('Path', () => {
});
test('struct.list.map', async () => {
const m1 = await newMap(['a', 'foo', 'b','bar', 'c', 'car']);
const m2 = await newMap(['d', 'dar', false, 'earth']);
const m1 = await newMap([['a', 'foo'], ['b','bar'], ['c', 'car']]);
const m2 = await newMap([['d', 'dar'], [false, 'earth']]);
const l = await newList([m1, m2]);
const s = newStruct('', {
foo: l,

View File

@@ -78,11 +78,13 @@ suite('walk', () => {
test('map', async () => {
const expected = [];
const entries = [];
for (let i = 0; i < 1000; i++) {
expected.push(i);
expected.push(String('value' + i));
expected.push('value' + i);
entries.push([i, 'value' + i]);
}
const map = await newMap(Array.from(expected));
const map = await newMap(entries);
expected.push(map);
await callbackHappensOnce(map, ds);