New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

@agoric/store

Package Overview
Dependencies
Maintainers
5
Versions
2700
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@agoric/store - npm Package Compare versions

Comparing version 0.6.9-dev-caa75cd.0 to 0.6.9-dev-cc79ff6.0

src/keys/copyBag.js

25

package.json
{
"name": "@agoric/store",
"version": "0.6.9-dev-caa75cd.0+caa75cd",
"version": "0.6.9-dev-cc79ff6.0+cc79ff6",
"description": "Wrapper for JavaScript map",

@@ -14,6 +14,5 @@ "type": "module",

"test:xs": "exit 0",
"lint-fix": "yarn lint:eslint --fix && yarn lint:types",
"lint-check": "yarn lint",
"lint": "yarn lint:types && yarn lint:eslint",
"lint:types": "tsc -p jsconfig.json",
"lint-fix": "yarn lint:eslint --fix",
"lint": "run-s --continue-on-error lint:*",
"lint:types": "tsc --maxNodeModuleJsDepth 3 -p jsconfig.json",
"lint:eslint": "eslint '**/*.js'"

@@ -35,9 +34,9 @@ },

"dependencies": {
"@agoric/assert": "0.3.16-dev-caa75cd.0+caa75cd",
"@agoric/eventual-send": "0.14.1-dev-caa75cd.0+caa75cd",
"@agoric/marshal": "0.5.1-dev-caa75cd.0+caa75cd",
"@agoric/promise-kit": "0.2.30-dev-caa75cd.0+caa75cd"
"@agoric/assert": "0.3.16-dev-cc79ff6.0+cc79ff6",
"@agoric/eventual-send": "0.14.1-dev-cc79ff6.0+cc79ff6",
"@agoric/promise-kit": "0.2.30-dev-cc79ff6.0+cc79ff6",
"@endo/marshal": "^0.5.4"
},
"devDependencies": {
"@agoric/swingset-vat": "0.24.2-dev-caa75cd.0+caa75cd",
"@agoric/swingset-vat": "0.24.2-dev-cc79ff6.0+cc79ff6",
"ava": "^3.12.1"

@@ -58,6 +57,2 @@ },

],
"prettier": {
"trailingComma": "all",
"singleQuote": true
},
"publishConfig": {

@@ -72,3 +67,3 @@ "access": "public"

},
"gitHead": "caa75cdf58ec0e280898c97fc94e1317cd4a3b64"
"gitHead": "cc79ff61263edbced108c8d9ed0a249d2f8e1786"
}

49

src/index.js
// @ts-check
export { isKey, assertKey } from './keys/checkKey.js';
export { keyLT, keyLTE, keyEQ, keyGTE, keyGT } from './keys/compareKeys.js';
export {
isKey,
assertKey,
makeCopySet,
getCopySetKeys,
makeCopyBag,
makeCopyBagFromElements,
getCopyBagEntries,
makeCopyMap,
getCopyMapEntries,
} from './keys/checkKey.js';
export { coerceToElements } from './keys/copySet.js';
export { coerceToBagEntries } from './keys/copyBag.js';
export {
compareKeys,
keyLT,
keyLTE,
keyEQ,
keyGTE,
keyGT,
} from './keys/compareKeys.js';
export {
elementsIsSuperset,
elementsIsDisjoint,
elementsCompare,
elementsUnion,
elementsDisjointUnion,
elementsIntersection,
elementsDisjointSubtract,
setIsSuperset,
setIsDisjoint,
setCompare,
setUnion,
setDisjointUnion,
setIntersection,
setDisjointSubtract,
} from './keys/merge-set-operators.js';
export {
bagIsSuperbag,
bagCompare,
bagUnion,
bagIntersection,
bagDisjointSubtract,
} from './keys/merge-bag-operators.js';
export {
M,

@@ -13,3 +56,3 @@ isPattern,

} from './patterns/patternMatchers.js';
// export { compareRank } from './patterns/rankOrder.js';
export { compareRank, isRankSorted, sortByRank } from './patterns/rankOrder.js';

@@ -16,0 +59,0 @@ export { makeScalarWeakSetStore } from './stores/scalarWeakSetStore.js';

// @ts-check
// eslint-disable-next-line spaced-comment
/// <reference types="ses"/>

@@ -9,10 +8,18 @@

assertPassable,
Far,
getTag,
isObject,
makeTagged,
passStyleOf,
} from '@agoric/marshal';
import { checkCopySet, everyCopySetKey } from './copySet.js';
import { checkCopyMap, everyCopyMapKey, everyCopyMapValue } from './copyMap.js';
} from '@endo/marshal';
import { checkElements, makeSetOfElements } from './copySet.js';
import { checkBagEntries, makeBagOfEntries } from './copyBag.js';
import {
compareAntiRank,
makeFullOrderComparatorKit,
sortByRank,
} from '../patterns/rankOrder.js';
const { details: X, quote: q } = assert;
const { ownKeys } = Reflect;

@@ -84,4 +91,7 @@ // ////////////////// Primitive and Scalar keys ////////////////////////////////

// ///////////////////////////// Keys //////////////////////////////////////////
// ////////////////////////////// Keys /////////////////////////////////////////
/** @type {WeakSet<Key>} */
const keyMemo = new WeakSet();
/**

@@ -92,4 +102,424 @@ * @param {Passable} val

*/
export const checkKey = (val, check = x => x) => {
if (isPrimitiveKey(val)) {
return true;
}
if (!isObject(val)) {
// TODO There is not yet a checkPassable, but perhaps there should be.
// If that happens, we should call it here instead.
assertPassable(val);
return true;
}
if (keyMemo.has(val)) {
return true;
}
// eslint-disable-next-line no-use-before-define
const result = checkKeyInternal(val, check);
if (result) {
// Don't cache the undefined cases, so that if it is tried again
// with `assertChecker` it'll throw a diagnostic again
keyMemo.add(val);
}
// Note that we do not memoize a negative judgement, so that if it is tried
// again with a checker, it will still produce a useful diagnostic.
return result;
};
harden(checkKey);
/**
* @param {Passable} val
* @returns {boolean}
*/
export const isKey = val => checkKey(val);
harden(isKey);
/**
* @param {Key} val
*/
export const assertKey = val => {
checkKey(val, assertChecker);
};
harden(assertKey);
// //////////////////////////// CopySet ////////////////////////////////////////
// Moved to here so they can check that the copySet contains only keys
// without creating an import cycle.
/** @type WeakSet<CopySet<Key>> */
const copySetMemo = new WeakSet();
/**
* @param {Passable} s
* @param {Checker=} check
* @returns {boolean}
*/
export const checkCopySet = (s, check = x => x) => {
if (copySetMemo.has(s)) {
return true;
}
const result =
check(
passStyleOf(s) === 'tagged' && getTag(s) === 'copySet',
X`Not a copySet: ${s}`,
) &&
checkElements(s.payload, check) &&
checkKey(s.payload);
if (result) {
copySetMemo.add(s);
}
return result;
};
harden(checkCopySet);
/**
* @callback IsCopySet
* @param {Passable} s
* @returns {s is CopySet<Key>}
*/
/** @type {IsCopySet} */
export const isCopySet = s => checkCopySet(s);
harden(isCopySet);
/**
* @callback AssertCopySet
* @param {Passable} s
* @returns {asserts s is CopySet<Key>}
*/
/** @type {AssertCopySet} */
export const assertCopySet = s => {
checkCopySet(s, assertChecker);
};
harden(assertCopySet);
/**
* @template K
* @param {CopySet<K>} s
* @returns {K[]}
*/
export const getCopySetKeys = s => {
assertCopySet(s);
return s.payload;
};
harden(getCopySetKeys);
/**
* @template K
* @param {CopySet<K>} s
* @param {(key: K, index: number) => boolean} fn
* @returns {boolean}
*/
export const everyCopySetKey = (s, fn) =>
getCopySetKeys(s).every((key, index) => fn(key, index));
harden(everyCopySetKey);
/**
* @template K
* @param {Iterable<K>} elementIter
* @returns {CopySet<K>}
*/
export const makeCopySet = elementIter => {
const result = makeSetOfElements(elementIter);
assertCopySet(result);
return result;
};
harden(makeCopySet);
// //////////////////////////// CopyBag ////////////////////////////////////////
// Moved to here so they can check that the copyBag contains only keys
// without creating an import cycle.
/** @type WeakSet<CopyBag<Key>> */
const copyBagMemo = new WeakSet();
/**
* @param {Passable} b
* @param {Checker=} check
* @returns {boolean}
*/
export const checkCopyBag = (b, check = x => x) => {
if (copyBagMemo.has(b)) {
return true;
}
const result =
check(
passStyleOf(b) === 'tagged' && getTag(b) === 'copyBag',
X`Not a copyBag: ${b}`,
) &&
checkBagEntries(b.payload, check) &&
checkKey(b.payload);
if (result) {
copyBagMemo.add(b);
}
return result;
};
harden(checkCopyBag);
/**
* @callback IsCopyBag
* @param {Passable} b
* @returns {b is CopyBag<Key>}
*/
/** @type {IsCopyBag} */
export const isCopyBag = b => checkCopyBag(b);
harden(isCopyBag);
/**
* @callback AssertCopyBag
* @param {Passable} b
* @returns {asserts b is CopyBag<Key>}
*/
/** @type {AssertCopyBag} */
export const assertCopyBag = b => {
checkCopyBag(b, assertChecker);
};
harden(assertCopyBag);
/**
* @template K
* @param {CopyBag<K>} b
* @returns {K[]}
*/
export const getCopyBagEntries = b => {
assertCopyBag(b);
return b.payload;
};
harden(getCopyBagEntries);
/**
* @template K
* @param {CopyBag<K>} b
* @param {(entry: [K, bigint], index: number) => boolean} fn
* @returns {boolean}
*/
export const everyCopyBagEntry = (b, fn) =>
getCopyBagEntries(b).every((entry, index) => fn(entry, index));
harden(everyCopyBagEntry);
/**
* @template K
* @param {Iterable<[K,bigint]>} bagEntryIter
* @returns {CopyBag<K>}
*/
export const makeCopyBag = bagEntryIter => {
const result = makeBagOfEntries(bagEntryIter);
assertCopyBag(result);
return result;
};
harden(makeCopyBag);
/**
* @template K
* @param {Iterable<K>} elementIter
* @returns {CopySet<K>}
*/
export const makeCopyBagFromElements = elementIter => {
// This fullOrder contains history dependent state. It is specific
// to this one call and does not survive it.
const fullCompare = makeFullOrderComparatorKit().antiComparator;
const sorted = sortByRank(elementIter, fullCompare);
/** @type {[K,bigint][]} */
const entries = [];
for (let i = 0; i < sorted.length; ) {
const k = sorted[i];
let j = i + 1;
while (j < sorted.length && fullCompare(k, sorted[j]) === 0) {
j += 1;
}
entries.push([k, BigInt(j - i)]);
i = j;
}
return makeCopyBag(entries);
};
harden(makeCopyBagFromElements);
// //////////////////////////// CopyMap ////////////////////////////////////////
// Moved to here so they can check that the copyMap's keys contains only keys
// without creating an import cycle.
/** @type WeakSet<CopyMap<any,any>> */
const copyMapMemo = new WeakSet();
/**
* @param {Passable} m
* @param {Checker=} check
* @returns {boolean}
*/
export const checkCopyMap = (m, check = x => x) => {
if (copyMapMemo.has(m)) {
return true;
}
if (!(passStyleOf(m) === 'tagged' && getTag(m) === 'copyMap')) {
return check(false, X`Not a copyMap: ${m}`);
}
const { payload } = m;
if (passStyleOf(payload) !== 'copyRecord') {
return check(false, X`A copyMap's payload must be a record: ${m}`);
}
const { keys, values, ...rest } = payload;
const result =
check(
ownKeys(rest).length === 0,
X`A copyMap's payload must only have .keys and .values: ${m}`,
) &&
checkElements(keys, check) &&
check(
passStyleOf(values) === 'copyArray',
X`A copyMap's .values must be a copyArray: ${m}`,
) &&
check(
keys.length === values.length,
X`A copyMap must have the same number of keys and values: ${m}`,
);
if (result) {
copyMapMemo.add(m);
}
return result;
};
harden(checkCopyMap);
/**
* @callback IsCopyMap
* @param {Passable} m
* @returns {m is CopyMap<Key, Passable>}
*/
/** @type {IsCopyMap} */
export const isCopyMap = m => checkCopyMap(m);
harden(isCopyMap);
/**
* @callback AssertCopyMap
* @param {Passable} m
* @returns {asserts m is CopyMap<Key, Passable>}
*/
/** @type {AssertCopyMap} */
export const assertCopyMap = m => {
checkCopyMap(m, assertChecker);
};
harden(assertCopyMap);
/**
* @template K,V
* @param {CopyMap<K,V>} m
* @returns {K[]}
*/
export const getCopyMapKeys = m => {
assertCopyMap(m);
return m.payload.keys;
};
harden(getCopyMapKeys);
/**
* @template K,V
* @param {CopyMap<K,V>} m
* @returns {V[]}
*/
export const getCopyMapValues = m => {
assertCopyMap(m);
return m.payload.values;
};
harden(getCopyMapValues);
/**
* @template K,V
* @param {CopyMap<K,V>} m
* @returns {Iterable<[K,V]>}
*/
export const getCopyMapEntries = m => {
assertCopyMap(m);
const {
payload: { keys, values },
} = m;
const { length } = keys;
return Far('CopyMap entries iterable', {
[Symbol.iterator]: () => {
let i = 0;
return Far('CopyMap entries iterator', {
next: () => {
/** @type {IteratorResult<[K,V],void>} */
let result;
if (i < length) {
result = harden({ done: false, value: [keys[i], values[i]] });
i += 1;
return result;
} else {
result = harden({ done: true, value: undefined });
}
return result;
},
});
},
});
};
harden(getCopyMapEntries);
/**
* @template K,V
* @param {CopyMap<K,V>} m
* @param {(key: K, index: number) => boolean} fn
* @returns {boolean}
*/
export const everyCopyMapKey = (m, fn) =>
getCopyMapKeys(m).every((key, index) => fn(key, index));
harden(everyCopyMapKey);
/**
* @template K,V
* @param {CopyMap<K,V>} m
* @param {(value: V, index: number) => boolean} fn
* @returns {boolean}
*/
export const everyCopyMapValue = (m, fn) =>
getCopyMapValues(m).every((value, index) => fn(value, index));
harden(everyCopyMapValue);
/**
* @template K,V
* @param {CopyMap<K,V>} m
* @returns {CopySet<K>}
*/
export const copyMapKeySet = m =>
// A copyMap's keys are already in the internal form used by copySets.
makeTagged('copySet', m.payload.keys);
harden(copyMapKeySet);
/**
* @template K,V
* @param {Iterable<[K, V]>} entries
* @returns {CopyMap<K,V>}
*/
export const makeCopyMap = entries => {
// This is weird, but reverse rank sorting the entries is a good first step
// for getting the rank sorted keys together with the values
// organized by those keys. Also, among values associated with
// keys in the same equivalence class, those are rank sorted.
// TODO This
// could solve the copyMap cover issue explained in patternMatchers.js.
// But only if we include this criteria in our validation of copyMaps,
// which we currently do not.
const sortedEntries = sortByRank(entries, compareAntiRank);
const keys = sortedEntries.map(([k, _v]) => k);
const values = sortedEntries.map(([_k, v]) => v);
const result = makeTagged('copyMap', { keys, values });
assertCopyMap(result);
return result;
};
harden(makeCopyMap);
// //////////////////////// Keys Recur /////////////////////////////////////////
/**
* @param {Passable} val
* @param {Checker=} check
* @returns {boolean}
*/
const checkKeyInternal = (val, check = x => x) => {
// eslint-disable-next-line no-use-before-define
const checkIt = child => checkKey(child, check);

@@ -111,8 +541,7 @@

case 'copySet': {
return (
checkCopySet(val, check) &&
// For a copySet to be a key, all its elements must be keys
everyCopySetKey(val, checkIt)
);
return checkCopySet(val, check);
}
case 'copyBag': {
return checkCopyBag(val, check);
}
case 'copyMap': {

@@ -122,4 +551,4 @@ return (

// For a copyMap to be a key, all its keys and values must
// be keys.
everyCopyMapKey(val, checkIt) &&
// be keys. Keys already checked by `checkCopyMap` since
// that's a copyMap requirement in general.
everyCopyMapValue(val, checkIt)

@@ -151,49 +580,1 @@ );

};
/** @type {WeakSet<Key>} */
const keyMemo = new WeakSet();
/**
* @param {Passable} val
* @param {Checker=} check
* @returns {boolean}
*/
export const checkKey = (val, check = x => x) => {
if (isPrimitiveKey(val)) {
return true;
}
if (!isObject(val)) {
// TODO There is not yet a checkPassable, but perhaps there should be.
// If that happens, we should call it here instead.
assertPassable(val);
return true;
}
if (keyMemo.has(val)) {
return true;
}
const result = checkKeyInternal(val, check);
if (result) {
// Don't cache the undefined cases, so that if it is tried again
// with `assertChecker` it'll throw a diagnostic again
keyMemo.add(val);
}
// Note that we do not memoize a negative judgement, so that if it is tried
// again with a checker, it will still produce a useful diagnostic.
return result;
};
harden(checkKey);
/**
* @param {Passable} val
* @returns {boolean}
*/
export const isKey = val => checkKey(val);
harden(isKey);
/**
* @param {Key} val
*/
export const assertKey = val => {
checkKey(val, assertChecker);
};
harden(assertKey);
// @ts-check
// eslint-disable-next-line spaced-comment
/// <reference types="ses"/>
import { passStyleOf, getTag } from '@agoric/marshal';
import { passStyleOf, getTag } from '@endo/marshal';
import { compareRank } from '../patterns/rankOrder.js';
import { assertKey } from './checkKey.js';
import { bagCompare } from './merge-bag-operators.js';
import { setCompare } from './merge-set-operators.js';

@@ -13,51 +14,4 @@ const { details: X, quote: q } = assert;

/**
* `compareKeys` implements a partial order over keys. As with the
* rank ordering produced by `compareRank`, -1, 0, and 1 mean
* "less than", "equivalent to", and "greater than" respectively.
* NaN means "incomparable" --- the first key is not less, equivalent,
* or greater than the second. For example, subsets over sets is
* a partial order.
*
* By using NaN for "incomparable", the normal equivalence for using
* the return value in a comparison is preserved.
* `compareKeys(left, right) >= 0` iff `left` is greater than or
* equivalent to `right` in the partial ordering.
* `compareKeys` is currently not exported directly, so its
* bizarre but convenient return type is not exposed.
*
* Key order (a partial order) and rank order (a full order) are
* co-designed so that we store passables in rank order and index into them
* with keys for key-based queries. To keep these distinct, when speaking
* informally about rank, we talk about "earlier" and "later". When speaking
* informally about keys, we talk about "smaller" and "bigger".
*
* In both orders, the return-0 case defines
* an equivalence class, i.e., those that are tied for the same place in the
* order. The global invariant that we need between the two orders is that the
* key order equivalence class is always at least as precise as the
* rank order equivalence class. IOW, if `compareKeys(X,Y) === 0` then
* `compareRank(X,Y) === 0`. But not vice versa. For example, two different
* remotables are the same rank but incommensurate as keys.
*
* A further invariant is if `compareKeys(X,Y) < 0` then
* `compareRank(X,Y) <= 0`, i.e., if X is smaller than Y in key order, then X
* must be at least as early as Y in rank order. But not vice versa.
* X can be earlier than Y in rank order and still be incommensurate with Y in
* key order. For example, the records `{b: 3, a: 5}` is earlier than but
* incommensurate with the record `{b: 5, a: 3}`.
*
* This lets us translate a range search over the
* partial key order into a range search over rank order followed by filtering
* out those that don't match. To get this effect, we store the elements of
* a set in an array sorted in reverse rank order, from later to earlier.
* Combined with our lexicographic comparison of arrays, if set X is a subset
* of set Y then the array representing set X will be an earlier rank that the
* array representing set Y.
*
* @param {Key} left
* @param {Key} right
* @returns {-1 | 0 | 1 | NaN}
*/
const compareKeys = (left, right) => {
/** @type {KeyCompare} */
export const compareKeys = (left, right) => {
assertKey(left);

@@ -132,3 +86,5 @@ assertKey(right);

}
let result = 0; // start with hypothesis they are keyEQ
// Presume that both copyRecords have the same key order
// until encountering a property disproving that hypothesis.
let result = 0;
for (const name of leftNames) {

@@ -151,7 +107,8 @@ const comp = compareKeys(left[name], right[name]);

// If copyRecord X is smaller than copyRecord Y, then they must have the
// same property names, and every value is X must be smaller or equal to
// every corresponding value in Y. The rank order of X and Y is based
// on lexicographic rank order of their values, as organized by the
// reverse order of their property names. Thus
// if compareKeys(X,Y) < 0 then compareRank(X,Y) <= 0.
// same property names and every value in X must be smaller or equal to
// the corresponding value in Y (with at least one value smaller).
// The rank order of X and Y is based on lexicographic rank order of
// their values, as organized by reverse lexicographic order of their
// property names.
// Thus if compareKeys(X,Y) < 0 then compareRank(X,Y) < 0.
return result;

@@ -169,68 +126,13 @@ }

// copySet X is smaller than copySet Y when every element of X
// is keyEQ to some element of Y.
// The following algorithm is good when there are few elements tied
// for the same rank. But it is O(N*M) in the size of these ties.
// Sets of remotables are a particularly bad case. For these, some
// kind of hash table (scalar set or map) should be used instead.
// TODO to get something working, I am currently implementing
// only the special case where there are no rank-order ties.
let result = 0; // start with the hypothesis they are keyEQ.
const xs = left.payload;
const ys = right.payload;
const xlen = xs.length;
const ylen = ys.length;
let xi = 0;
let yi = 0;
while (xi < xlen && yi < ylen) {
const x = xs[xi];
const y = ys[yi];
if (xi + 1 < xlen && compareRank(x, xs[xi + 1]) === 0) {
assert.fail(X`sets with rank ties not yet implemented: ${left}`);
}
if (yi + 1 < ylen && compareRank(y, ys[yi + 1]) === 0) {
assert.fail(X`sets with rank ties not yet implemented: ${right}`);
}
const comp = compareKeys(x, y);
if (Number.isNaN(comp)) {
// If they are incommensurate, then each element is not in the
// other set, so the sets are incommensurate.
return NaN;
} else if (comp === 0) {
//
xi += 1;
yi += 1;
} else {
if (result !== comp) {
if (result === 0) {
result = comp;
} else {
assert(
(result === -1 && comp === 1) ||
(result === 1 && comp === -1),
);
return NaN;
}
}
if (comp === 1) {
xi += 1;
} else {
assert(comp === -1);
yi += 1;
}
}
}
const comp = compareKeys(xlen, ylen);
if (comp === 0) {
return result;
} else if (result === 0 || result === comp) {
return comp;
} else {
assert(
(result === -1 && comp === 1) || (result === 1 && comp === -1),
);
return NaN;
}
// is keyEQ to some element of Y and some element of Y is
// not keyEQ to any element of X.
return setCompare(left, right);
}
case 'copyBag': {
// copyBag X is smaller than copyBag Y when every element of X
// occurs no more than the keyEQ element of Y, and some element
// of Y occurs more than some element of X, where being absent
// from X counts as occuring zero times.
return bagCompare(left, right);
}
case 'copyMap': {

@@ -237,0 +139,0 @@ // Two copyMaps that have different keys (according to keyEQ) are

// @ts-check
import { assertChecker, makeTagged, passStyleOf } from '@endo/marshal';
import {
assertChecker,
getTag,
makeTagged,
passStyleOf,
} from '@agoric/marshal';
import {
compareAntiRank,
isRankSorted,
makeFullOrderComparatorKit,
sortByRank,
} from '../patterns/rankOrder.js';
// eslint-disable-next-line spaced-comment
/// <reference types="ses"/>

@@ -21,81 +16,88 @@

/**
* @param {Passable[]} keys
* @template T
* @param {T[]} elements
* @param {FullCompare=} fullCompare If provided and `elements` is already known
* to be sorted by this `fullCompare`, then we should get a memo hit rather
* than a resorting. However, currently, we still enumerate the entire array
* each time.
*
* TODO: If doing this reduntantly turns out to be expensive, we
* could memoize this no-duplicate finding as well, independent
* of the `fullOrder` use to reach this finding.
* @param {Checker=} check
* @returns {boolean}
*/
export const checkCopySetKeys = (keys, check = x => x) => {
if (passStyleOf(keys) !== 'copyArray') {
return check(
false,
X`The keys of a copySet or copyMap must be a copyArray: ${keys}`,
);
const checkNoDuplicates = (
elements,
fullCompare = undefined,
check = x => x,
) => {
// This fullOrder contains history dependent state. It is specific
// to this one call and does not survive it.
// TODO Once all our tooling is ready for `&&=`, the following
// line should be rewritten using it.
fullCompare = fullCompare || makeFullOrderComparatorKit().antiComparator;
elements = sortByRank(elements, fullCompare);
const { length } = elements;
for (let i = 1; i < length; i += 1) {
const k0 = elements[i - 1];
const k1 = elements[i];
if (fullCompare(k0, k1) === 0) {
return check(false, X`value has duplicates: ${k0}`);
}
}
if (!isRankSorted(keys, compareAntiRank)) {
return check(
false,
X`The keys of a copySet or copyMap must be sorted in reverse rank order: ${keys}`,
);
}
return true;
};
harden(checkCopySetKeys);
/** @type WeakSet<CopySet<any>> */
const copySetMemo = new WeakSet();
/**
* @template T
* @param {T[]} elements
* @param {FullCompare=} fullCompare
* @returns {void}
*/
export const assertNoDuplicates = (elements, fullCompare = undefined) => {
checkNoDuplicates(elements, fullCompare, assertChecker);
};
/**
* @param {Passable} s
* @param {Passable[]} elements
* @param {Checker=} check
* @returns {boolean}
*/
export const checkCopySet = (s, check = x => x) => {
if (copySetMemo.has(s)) {
return true;
export const checkElements = (elements, check = x => x) => {
if (passStyleOf(elements) !== 'copyArray') {
return check(
false,
X`The keys of a copySet or copyMap must be a copyArray: ${elements}`,
);
}
const result =
check(
passStyleOf(s) === 'tagged' && getTag(s) === 'copySet',
X`Not a copySet: ${s}`,
) && checkCopySetKeys(s.payload, check);
if (result) {
copySetMemo.add(s);
if (!isRankSorted(elements, compareAntiRank)) {
return check(
false,
X`The keys of a copySet or copyMap must be sorted in reverse rank order: ${elements}`,
);
}
return result;
return checkNoDuplicates(elements, undefined, check);
};
harden(checkCopySet);
harden(checkElements);
export const isCopySet = s => checkCopySet(s);
harden(isCopySet);
export const assertElements = elements =>
checkElements(elements, assertChecker);
harden(assertElements);
export const assertCopySet = s => checkCopySet(s, assertChecker);
harden(assertCopySet);
/**
* @template K
* @param {CopySet<K>} s
* @returns {K[]}
*/
export const getCopySetKeys = s => {
assertCopySet(s);
return s.payload;
export const coerceToElements = elementsList => {
const elements = sortByRank(elementsList, compareAntiRank);
assertElements(elements);
return elements;
};
harden(getCopySetKeys);
harden(coerceToElements);
/**
* @template K
* @param {CopySet<K>} s
* @param {(key: K, index: number) => boolean} fn
* @returns {boolean}
*/
export const everyCopySetKey = (s, fn) =>
getCopySetKeys(s).every((key, index) => fn(key, index));
harden(everyCopySetKey);
/**
* @template K
* @param {Iterable<K>} elements
* @param {Iterable<K>} elementIter
* @returns {CopySet<K>}
*/
export const makeCopySet = elements =>
makeTagged('copySet', sortByRank(elements, compareAntiRank));
harden(makeCopySet);
export const makeSetOfElements = elementIter =>
makeTagged('copySet', coerceToElements(elementIter));
harden(makeSetOfElements);

@@ -22,3 +22,3 @@ // @ts-check

* comment explaining the problem inhibiting conversion to the new
* system. Some of these problems as if this writing:
* system. Some of these problems as of this writing:
* * A promiseKit used as a value, even though a promiseKit is not

@@ -33,3 +33,5 @@ * a passable. Solutions are to make it a passable, or to convert

* @deprecated switch to ScalarMap if possible, Map otherwise
* @template K,V
* @param {string} [keyName='key'] - the column name for the key
* @returns {LegacyMap<K,V>}
*/

@@ -42,3 +44,3 @@ export const makeLegacyMap = (keyName = 'key') => {

assert(m.has(key), X`${q(keyName)} not found: ${key}`);
const legacyMap = harden({
return harden({
has: key => {

@@ -66,8 +68,9 @@ // Check if a key exists. The key can be any JavaScript value,

},
keys: () => [...m.keys()],
values: () => [...m.values()],
entries: () => [...m.entries()],
keys: () => m.keys(),
values: () => m.values(),
entries: () => m.entries(),
getSize: () => m.size,
clear: () => m.clear(),
});
return legacyMap;
};
harden(makeLegacyMap);

@@ -10,5 +10,8 @@ // @ts-check

* @deprecated switch to ScalarWeakMap if possible, WeakMap otherwise
* @template K,V
* @param {string} [keyName='key'] - the column name for the key
* @returns {LegacyWeakMap<K,V>}
*/
export const makeLegacyWeakMap = (keyName = 'key') => {
/** @type {WeakMap<K & Object,V>} */
const wm = new WeakMap();

@@ -19,3 +22,3 @@ const assertKeyDoesNotExist = key =>

assert(wm.has(key), X`${q(keyName)} not found: ${key}`);
const legacyWeakMap = harden({
return harden({
has: key => {

@@ -33,3 +36,4 @@ // Check if a key exists. The key can be any JavaScript value,

assertKeyExists(key);
return wm.get(key);
// How to tell typescript I believe the `get` will succeed.
return /** @type {V} */ (wm.get(key));
},

@@ -45,4 +49,3 @@ set: (key, value) => {

});
return legacyWeakMap;
};
harden(makeLegacyWeakMap);
// @ts-check
// eslint-disable-next-line spaced-comment
/// <reference types="ses"/>

@@ -5,0 +4,0 @@

@@ -10,3 +10,3 @@ // @ts-check

hasOwnPropertyOf,
} from '@agoric/marshal';
} from '@endo/marshal';
import {

@@ -26,7 +26,8 @@ compareAntiRank,

isScalarKey,
checkCopySet,
checkCopyBag,
checkCopyMap,
copyMapKeySet,
} from '../keys/checkKey.js';
import { checkCopySet /* , makeCopySet XXX TEMP */ } from '../keys/copySet.js';
import { checkCopyMap, copyMapKeySet } from '../keys/copyMap.js';
// eslint-disable-next-line spaced-comment
/// <reference types="ses"/>

@@ -123,2 +124,16 @@

}
case 'copyBag': {
if (!checkCopyBag(patt, check)) {
return false;
}
// If it is a CopyBag, then it must also be a key and we
// should never get here.
if (isKey(patt)) {
assert.fail(
X`internal: The key case should have been dealt with earlier: ${patt}`,
);
} else {
assert.fail(X`A CopyMap must be a Key but was not: ${patt}`);
}
}
case 'copyMap': {

@@ -141,3 +156,3 @@ return (

false,
X`A passable tagged ${q(tag)} is not a key: ${patt}`,
X`A passable tagged ${q(tag)} is not a pattern: ${patt}`,
);

@@ -811,2 +826,19 @@ }

/** @type {MatchHelper} */
const matchBagOfHelper = Far('match:bagOf helper', {
checkMatches: (specimen, keyPatt, check = x => x) =>
check(
passStyleOf(specimen) === 'tagged' && getTag(specimen) === 'copyBag',
X`${specimen} - Must be a a CopyBag`,
) &&
specimen.payload.every(([key, _count]) => checkMatches(key, keyPatt)),
checkIsMatcherPayload: checkPattern,
getRankCover: () => getPassStyleCover('tagged'),
checkKeyPattern: (_, check = x => x) =>
check(false, X`CopySets not yet supported as keys`),
});
/** @type {MatchHelper} */
const matchMapOfHelper = Far('match:mapOf helper', {

@@ -868,3 +900,7 @@ checkMatches: (specimen, [keyPatt, valuePatt], check = x => x) =>

(checkMatches(specB, base, check) &&
(rest === undefined || checkMatches(specR, rest, check)))
(rest === undefined ||
check(
matches(specR, rest),
X`Remainder ${specR} - Must match ${rest}`,
)))
);

@@ -944,3 +980,7 @@ },

(checkMatches(specB, newBase, check) &&
(rest === undefined || checkMatches(specR, rest, check)))
(rest === undefined ||
check(
matches(specR, rest),
X`Remainder ${specR} - Must match ${rest}`,
)))
);

@@ -976,2 +1016,3 @@ },

'match:setOf': matchSetOfHelper,
'match:bagOf': matchBagOfHelper,
'match:mapOf': matchMapOfHelper,

@@ -1003,2 +1044,3 @@ 'match:split': matchSplitHelper,

const SetShape = makeKindMatcher('copySet');
const BagShape = makeKindMatcher('copyBag');
const MapShape = makeKindMatcher('copyMap');

@@ -1030,2 +1072,3 @@ const RemotableShape = makeKindMatcher('remotable');

set: () => SetShape,
bag: () => BagShape,
map: () => MapShape,

@@ -1052,2 +1095,3 @@ remotable: () => RemotableShape,

setOf: (keyPatt = M.any()) => makeMatcher('match:setOf', keyPatt),
bagOf: (keyPatt = M.any()) => makeMatcher('match:bagOf', keyPatt),
mapOf: (keyPatt = M.any(), valuePatt = M.any()) =>

@@ -1054,0 +1098,0 @@ makeMatcher('match:mapOf', [keyPatt, valuePatt]),

@@ -9,3 +9,3 @@ // @ts-check

sameValueZero,
} from '@agoric/marshal';
} from '@endo/marshal';

@@ -30,5 +30,5 @@ const { fromEntries, entries, setPrototypeOf } = Object;

/* s */ ['string', ['s', 't']],
/* u */ ['undefined', ['u', 'v']],
/* v */ ['null', ['v', 'v~']],
/* y */ ['symbol', ['y', 'z']],
/* z */ ['null', ['z', 'z~']],
/* z */ ['undefined', ['z', '{']],
/* | remotable->ordinal mapping prefix: This is not used in covers but it is

@@ -52,3 +52,3 @@ reserved from the same set of strings. Note that the prefix is > any

/**
* @type {WeakMap<CompareRank,WeakSet<Passable[]>>}
* @type {WeakMap<RankCompare,WeakSet<Passable[]>>}
*/

@@ -58,3 +58,3 @@ const memoOfSorted = new WeakMap();

/**
* @type {WeakMap<CompareRank,CompareRank>}
* @type {WeakMap<RankCompare,RankCompare>}
*/

@@ -64,3 +64,3 @@ const comparatorMirrorImages = new WeakMap();

/**
* @param {CompareRank=} compareRemotables
* @param {RankCompare=} compareRemotables
* An option to create a comparator in which an internal order is

@@ -70,6 +70,6 @@ * assigned to remotables. This defaults to a comparator that

* for the same rank.
* @returns {ComparatorKit}
* @returns {RankComparatorKit}
*/
const makeComparatorKit = (compareRemotables = (_x, _y) => 0) => {
/** @type {CompareRank} */
/** @type {RankCompare} */
const comparator = (left, right) => {

@@ -191,3 +191,3 @@ if (sameValueZero(left, right)) {

/** @type {CompareRank} */
/** @type {RankCompare} */
const antiComparator = (x, y) => comparator(y, x);

@@ -203,4 +203,4 @@

/**
* @param {CompareRank} comparator
* @returns {CompareRank=}
* @param {RankCompare} comparator
* @returns {RankCompare=}
*/

@@ -212,3 +212,3 @@ export const comparatorMirrorImage = comparator =>

* @param {Passable[]} passables
* @param {CompareRank} compare
* @param {RankCompare} compare
* @returns {boolean}

@@ -235,3 +235,3 @@ */

* @param {Passable[]} sorted
* @param {CompareRank} compare
* @param {RankCompare} compare
*/

@@ -248,4 +248,13 @@ export const assertRankSorted = (sorted, compare) =>

/**
* TODO SECURITY BUG: https://github.com/Agoric/agoric-sdk/issues/4260
* sortByRank currently uses `Array.prototype.sort` directly, and
* so only works correctly when given a `compare` function that considers
* `undefined` strictly bigger (`>`) than everything else. This is
* because `Array.prototype.sort` bizarrely moves all `undefined`s to
* the end of the array regardless, without consulting the `compare`
* function. This is a genuine bug for us NOW because sometimes we sort
* in reverse order by passing a reversed rank comparison function.
*
* @param {Iterable<Passable>} passables
* @param {CompareRank} compare
* @param {RankCompare} compare
* @returns {Passable[]}

@@ -278,3 +287,3 @@ */

* @param {Passable[]} sorted
* @param {CompareRank} compare
* @param {RankCompare} compare
* @param {Passable} key

@@ -337,3 +346,3 @@ * @param {("leftMost" | "rightMost")=} bias

/**
* @param {CompareRank} compare
* @param {RankCompare} compare
* @param {Passable} a

@@ -346,3 +355,3 @@ * @param {Passable} b

/**
* @param {CompareRank} compare
* @param {RankCompare} compare
* @param {Passable} a

@@ -355,3 +364,3 @@ * @param {Passable} b

/**
* @param {CompareRank} compare
* @param {RankCompare} compare
* @param {RankCover[]} covers

@@ -375,3 +384,3 @@ * @returns {RankCover}

/**
* @param {CompareRank} compare
* @param {RankCompare} compare
* @param {RankCover[]} covers

@@ -421,9 +430,12 @@ * @returns {RankCover}

*
* @returns {ComparatorKit}
* @param {boolean=} longLived
* @returns {FullComparatorKit}
*/
export const makeFullOrderComparatorKit = () => {
export const makeFullOrderComparatorKit = (longLived = false) => {
let numSeen = 0;
// Could be a WeakMap but would perform poorly. There are a dynamic
// number of these created, and each typically has a short lifetime.
const seen = new Map();
// When dynamically created with short lifetimes (the default) a WeakMap
// would perform poorly, and the leak created by a Map only lasts as long
// as the Map.
const MapConstructor = longLived ? WeakMap : Map;
const seen = new MapConstructor();
const tag = r => {

@@ -430,0 +442,0 @@ if (seen.has(r)) {

// @ts-check
import { Far, assertPassable } from '@agoric/marshal';
import {
Far,
assertPassable,
filterIterable,
mapIterable,
} from '@endo/marshal';
import { compareRank } from '../patterns/rankOrder.js';
import { assertScalarKey } from '../keys/checkKey.js';
import { makeCopyMap } from '../keys/copyMap.js';
import { fit, assertPattern } from '../patterns/patternMatchers.js';
import { assertScalarKey, makeCopyMap } from '../keys/checkKey.js';
import { matches, fit, assertPattern } from '../patterns/patternMatchers.js';
import { makeWeakMapStoreMethods } from './scalarWeakMapStore.js';
import { makeCursorKit } from './store-utils.js';
import { makeCurrentKeysKit } from './store-utils.js';
const { details: X, quote: q } = assert;
const { quote: q } = assert;

@@ -16,3 +20,4 @@ /**

* @param {Map<K,V>} jsmap
* @param {(k: K, v: V) => void} assertKVOkToWrite
* @param {(k: K, v: V) => void} assertKVOkToAdd
* @param {(k: K, v: V) => void} assertKVOkToSet
* @param {((k: K) => void)=} assertKeyOkToDelete

@@ -24,3 +29,4 @@ * @param {string=} keyName

jsmap,
assertKVOkToWrite,
assertKVOkToAdd,
assertKVOkToSet,
assertKeyOkToDelete = undefined,

@@ -30,9 +36,9 @@ keyName = 'key',

const {
assertUpdateOnWrite,
assertUpdateOnAdd,
assertUpdateOnDelete,
makeCursor,
makeArray,
} = makeCursorKit(
iterableKeys,
} = makeCurrentKeysKit(
() => jsmap.keys(),
compareRank,
assertKVOkToWrite,
assertKVOkToAdd,
assertKeyOkToDelete,

@@ -42,40 +48,72 @@ keyName,

const methods = harden({
/**
* @param {Pattern=} keyPatt
* @param {Pattern=} valuePatt
* @returns {Iterable<K>}
*/
const keys = (keyPatt = undefined, valuePatt = undefined) => {
if (keyPatt === undefined && valuePatt === undefined) {
return iterableKeys;
}
const filter = k => {
if (keyPatt !== undefined && !matches(k, keyPatt)) {
return false;
}
// Uses the current jsmap value, since the iteratator survives `.set`
if (valuePatt !== undefined && !matches(jsmap.get(k), valuePatt)) {
return false;
}
return true;
};
return filterIterable(iterableKeys, filter);
};
/**
* @param {Pattern=} keyPatt
* @param {Pattern=} valuePatt
* @returns {Iterable<V>}
*/
const values = (keyPatt = undefined, valuePatt = undefined) =>
mapIterable(keys(keyPatt, valuePatt), k => /** @type {V} */ (jsmap.get(k)));
/**
* @param {Pattern=} keyPatt
* @param {Pattern=} valuePatt
* @returns {Iterable<[K,V]>}
*/
const entries = (keyPatt = undefined, valuePatt = undefined) =>
mapIterable(keys(keyPatt, valuePatt), k => [
k,
/** @type {V} */ (jsmap.get(k)),
]);
return harden({
...makeWeakMapStoreMethods(
jsmap,
assertUpdateOnWrite,
/** @type {(k: K, v: V) => void} */ (assertUpdateOnAdd),
assertKVOkToSet,
assertUpdateOnDelete,
keyName,
),
keys,
values,
entries,
cursor: (entryPatt = undefined, direction = 'forward') => {
assert.equal(
direction,
'forward',
X`Non-forward cursors are not yet implemented: map ${q(keyName)}`,
);
return makeCursor(jsmap.entries(), entryPatt);
},
snapshot: (keyPatt = undefined, valuePatt = undefined) =>
makeCopyMap(entries(keyPatt, valuePatt)),
keys: (keyPatt = undefined) => makeArray(jsmap.keys(), keyPatt),
values: (valPatt = undefined) => makeArray(jsmap.values(), valPatt),
entries: (entryPatt = undefined) => makeArray(jsmap.entries(), entryPatt),
getSize: (keyPatt = undefined, valuePatt = undefined) =>
keyPatt === undefined && valuePatt === undefined
? jsmap.size
: [...keys(keyPatt, valuePatt)].length,
snapshot: (entryPatt = undefined) => makeCopyMap(methods.cursor(entryPatt)),
addAll: copyMap => {
const {
payload: { keys, values },
} = copyMap;
const { length } = keys;
for (let i = 0; i < length; i += 1) {
const key = keys[i];
const value = values[i];
// Don't assert that the key either does or does not exist.
assertUpdateOnWrite(key, value);
jsmap.set(key, value);
clear: (keyPatt = undefined, valuePatt = undefined) => {
if (keyPatt === undefined && valuePatt === undefined) {
jsmap.clear();
}
for (const key of keys(keyPatt, valuePatt)) {
jsmap.delete(key);
}
},
});
return methods;
};

@@ -101,25 +139,45 @@

keyName = 'key',
{ schema = undefined } = {},
{ keyPattern = undefined, valuePattern = undefined } = {},
) => {
const jsmap = new Map();
if (schema) {
assertPattern(schema);
if (keyPattern !== undefined) {
assertPattern(keyPattern);
}
const assertKVOkToWrite = (key, value) => {
if (valuePattern !== undefined) {
assertPattern(valuePattern);
}
const assertKVOkToSet = (_key, value) => {
// TODO: Just a transition kludge. Remove when possible.
// See https://github.com/Agoric/agoric-sdk/issues/3606
harden(key);
harden(value);
assertScalarKey(key);
assertPassable(value);
if (schema) {
fit(harden([key, value]), schema);
if (valuePattern !== undefined) {
fit(value, valuePattern);
}
};
const mapStore = Far(`scalar MapStore of ${q(keyName)}`, {
...makeMapStoreMethods(jsmap, assertKVOkToWrite, undefined, keyName),
const assertKVOkToAdd = (key, value) => {
// TODO: Just a transition kludge. Remove when possible.
// See https://github.com/Agoric/agoric-sdk/issues/3606
harden(key);
assertScalarKey(key);
if (keyPattern !== undefined) {
fit(key, keyPattern);
}
assertKVOkToSet(key, value);
};
return Far(`scalar MapStore of ${q(keyName)}`, {
...makeMapStoreMethods(
jsmap,
assertKVOkToAdd,
assertKVOkToSet,
undefined,
keyName,
),
});
return mapStore;
};
harden(makeScalarMapStore);
// @ts-check
import { Far } from '@agoric/marshal';
import { Far, filterIterable } from '@endo/marshal';
import { compareRank } from '../patterns/rankOrder.js';
import { assertScalarKey } from '../keys/checkKey.js';
import { makeCopySet } from '../keys/copySet.js';
import { fit, assertPattern } from '../patterns/patternMatchers.js';
import { assertScalarKey, makeCopySet } from '../keys/checkKey.js';
import { matches, fit, assertPattern } from '../patterns/patternMatchers.js';
import { makeWeakSetStoreMethods } from './scalarWeakSetStore.js';
import { makeCursorKit } from './store-utils.js';
import { makeCurrentKeysKit } from './store-utils.js';
const { details: X, quote: q } = assert;
const { quote: q } = assert;

@@ -16,3 +15,3 @@ /**

* @param {Set<K>} jsset
* @param {(k: K) => void} assertKeyOkToWrite
* @param {(k: K) => void} assertKeyOkToAdd
* @param {((k: K) => void)=} assertKeyOkToDelete

@@ -24,3 +23,3 @@ * @param {string=} keyName

jsset,
assertKeyOkToWrite,
assertKeyOkToAdd,
assertKeyOkToDelete = undefined,

@@ -30,9 +29,9 @@ keyName = 'key',

const {
assertUpdateOnWrite,
assertUpdateOnAdd,
assertUpdateOnDelete,
makeCursor,
makeArray,
} = makeCursorKit(
iterableKeys,
} = makeCurrentKeysKit(
() => jsset.keys(),
compareRank,
assertKeyOkToWrite,
assertKeyOkToAdd,
assertKeyOkToDelete,

@@ -42,6 +41,15 @@ keyName,

const methods = harden({
/**
* @param {Pattern=} keyPatt
* @returns {Iterable<K>}
*/
const keys = (keyPatt = undefined) =>
keyPatt === undefined
? iterableKeys
: filterIterable(iterableKeys, k => matches(k, keyPatt));
return harden({
...makeWeakSetStoreMethods(
jsset,
assertUpdateOnWrite,
assertUpdateOnAdd,
assertUpdateOnDelete,

@@ -51,27 +59,18 @@ keyName,

cursor: (keyPatt = undefined, direction = 'forward') => {
assert.equal(
direction,
'forward',
X`Non-forward cursors are not yet implemented: map ${q(keyName)}`,
);
return makeCursor(jsset.keys(), keyPatt);
},
keys,
keys: (keyPatt = undefined) => makeArray(jsset.keys(), keyPatt),
snapshot: (keyPatt = undefined) => makeCopySet(keys(keyPatt)),
snapshot: (keyPatt = undefined) => makeCopySet(methods.cursor(keyPatt)),
getSize: (keyPatt = undefined) =>
keyPatt === undefined ? jsset.size : [...keys(keyPatt)].length,
addAll: copySet => {
const { payload: keys } = copySet;
const { length } = keys;
for (let i = 0; i < length; i += 1) {
const key = keys[i];
// Don't assert that the key either does or does not exist.
assertKeyOkToWrite(key);
jsset.add(key);
clear: (keyPatt = undefined) => {
if (keyPatt === undefined) {
jsset.clear();
}
for (const key of keys(keyPatt)) {
jsset.delete(key);
}
},
});
return methods;
};

@@ -97,9 +96,10 @@

keyName = 'key',
{ schema = undefined } = {},
{ keyPattern = undefined } = {},
) => {
const jsset = new Set();
if (schema) {
assertPattern(schema);
if (keyPattern !== undefined) {
assertPattern(keyPattern);
}
const assertKeyOkToWrite = key => {
const assertKeyOkToAdd = key => {
// TODO: Just a transition kludge. Remove when possible.

@@ -110,11 +110,11 @@ // See https://github.com/Agoric/agoric-sdk/issues/3606

assertScalarKey(key);
if (schema) {
fit(key, schema);
if (keyPattern !== undefined) {
fit(key, keyPattern);
}
};
const setStore = Far(`scalar SetStore of ${q(keyName)}`, {
...makeSetStoreMethods(jsset, assertKeyOkToWrite, undefined, keyName),
return Far(`scalar SetStore of ${q(keyName)}`, {
...makeSetStoreMethods(jsset, assertKeyOkToAdd, undefined, keyName),
});
return setStore;
};
harden(makeScalarSetStore);
// @ts-check
import { Far, assertPassable, passStyleOf } from '@agoric/marshal';
import { Far, assertPassable, passStyleOf } from '@endo/marshal';
import { fit, assertPattern } from '../patterns/patternMatchers.js';

@@ -11,3 +11,4 @@

* @param {WeakMap<K & Object,V>} jsmap
* @param {(k: K, v: V) => void} assertKVOkToWrite
* @param {(k: K, v: V) => void} assertKVOkToAdd
* @param {(k: K, v: V) => void} assertKVOkToSet
* @param {((k: K) => void)=} assertKeyOkToDelete

@@ -19,4 +20,5 @@ * @param {string=} keyName

jsmap,
assertKVOkToWrite,
assertKeyOkToDelete = () => {},
assertKVOkToAdd,
assertKVOkToSet,
assertKeyOkToDelete = undefined,
keyName = 'key',

@@ -45,3 +47,3 @@ ) => {

assertKeyDoesNotExist(key);
assertKVOkToWrite(key, value);
assertKVOkToAdd(key, value);
jsmap.set(key, value);

@@ -51,3 +53,3 @@ },

assertKeyExists(key);
assertKVOkToWrite(key, value);
assertKVOkToSet(key, value);
jsmap.set(key, value);

@@ -57,5 +59,15 @@ },

assertKeyExists(key);
assertKeyOkToDelete(key);
if (assertKeyOkToDelete !== undefined) {
assertKeyOkToDelete(key);
}
jsmap.delete(key);
},
addAll: entries => {
for (const [key, value] of entries) {
// Don't assert that the key either does or does not exist.
assertKVOkToAdd(key, value);
jsmap.set(key, value);
}
},
});

@@ -65,5 +77,6 @@ };

/**
* This is a *scalar* map in that the keys can only be atomic values, primitives
* or remotables. Other storeMaps will accept, for example, copyArrays and
* copyRecords, as keys and look them up based on equality of their contents.
* This is a *scalar* mapStore in that the keys can only be atomic values:
* primitives or remotables.
* Other mapStores will accept, for example, copyArrays and
* copyRecords as keys and look them up based on equality of their contents.
*

@@ -75,3 +88,3 @@ * TODO For now, this scalarWeakMap accepts only remotables, reflecting the

* there. Though note that this would only enables collection of the
* remotables, since the other primitives may always appear.
* remotables, since the other primitives may always reappear.
*

@@ -85,14 +98,28 @@ * @template K,V

keyName = 'key',
{ longLived = true, schema = undefined } = {},
{ longLived = true, keyPattern = undefined, valuePattern = undefined } = {},
) => {
const jsmap = new (longLived ? WeakMap : Map)();
if (schema) {
assertPattern(schema);
if (keyPattern !== undefined) {
assertPattern(keyPattern);
}
const assertKVOkToWrite = (key, value) => {
if (valuePattern !== undefined) {
assertPattern(valuePattern);
}
const assertKVOkToSet = (_key, value) => {
// TODO: Just a transition kludge. Remove when possible.
// See https://github.com/Agoric/agoric-sdk/issues/3606
harden(key);
harden(value);
assertPassable(value);
if (valuePattern !== undefined) {
fit(value, valuePattern);
}
};
const assertKVOkToAdd = (key, value) => {
// TODO: Just a transition kludge. Remove when possible.
// See https://github.com/Agoric/agoric-sdk/issues/3606
harden(key);
assert(

@@ -102,12 +129,18 @@ passStyleOf(key) === 'remotable',

);
assertPassable(value);
if (schema) {
fit(harden([key, value]), schema);
if (keyPattern !== undefined) {
fit(key, keyPattern);
}
assertKVOkToSet(key, value);
};
const weakMapStore = Far(`scalar WeakMapStore of ${q(keyName)}`, {
...makeWeakMapStoreMethods(jsmap, assertKVOkToWrite, undefined, keyName),
return Far(`scalar WeakMapStore of ${q(keyName)}`, {
...makeWeakMapStoreMethods(
jsmap,
assertKVOkToAdd,
assertKVOkToSet,
undefined,
keyName,
),
});
return weakMapStore;
};
harden(makeScalarWeakMapStore);
// @ts-check
import { Far, passStyleOf } from '@agoric/marshal';
import { Far, passStyleOf } from '@endo/marshal';
import { fit, assertPattern } from '../patterns/patternMatchers.js';

@@ -11,3 +11,3 @@

* @param {WeakSet<K & Object>} jsset
* @param {(k: K) => void} assertKeyOkToWrite
* @param {(k: K) => void} assertKeyOkToAdd
* @param {((k: K) => void)=} assertKeyOkToDelete

@@ -19,4 +19,4 @@ * @param {string=} keyName

jsset,
assertKeyOkToWrite,
assertKeyOkToDelete = () => {},
assertKeyOkToAdd,
assertKeyOkToDelete = undefined,
keyName = 'key',

@@ -36,3 +36,3 @@ ) => {

add: key => {
assertKeyOkToWrite(key);
assertKeyOkToAdd(key);
jsset.add(key);

@@ -42,5 +42,14 @@ },

assertKeyExists(key);
assertKeyOkToDelete(key);
if (assertKeyOkToDelete !== undefined) {
assertKeyOkToDelete(key);
}
jsset.delete(key);
},
addAll: keys => {
for (const key of keys) {
assertKeyOkToAdd(key);
jsset.add(key);
}
},
});

@@ -68,9 +77,10 @@ };

keyName = 'key',
{ longLived = true, schema = undefined } = {},
{ longLived = true, keyPattern = undefined } = {},
) => {
const jsset = new (longLived ? WeakSet : Set)();
if (schema) {
assertPattern(schema);
if (keyPattern !== undefined) {
assertPattern(keyPattern);
}
const assertKeyOkToWrite = key => {
const assertKeyOkToAdd = key => {
// TODO: Just a transition kludge. Remove when possible.

@@ -84,11 +94,11 @@ // See https://github.com/Agoric/agoric-sdk/issues/3606

);
if (schema) {
fit(key, schema);
if (keyPattern !== undefined) {
fit(key, keyPattern);
}
};
const weakSetStore = Far(`scalar WeakSetStore of ${q(keyName)}`, {
...makeWeakSetStoreMethods(jsset, assertKeyOkToWrite, undefined, keyName),
return Far(`scalar WeakSetStore of ${q(keyName)}`, {
...makeWeakSetStoreMethods(jsset, assertKeyOkToAdd, undefined, keyName),
});
return weakSetStore;
};
harden(makeScalarWeakSetStore);
// @ts-check
import { filterIterable } from '@agoric/marshal';
import { matches } from '../patterns/patternMatchers.js';
import { assertRankSorted } from '../patterns/rankOrder.js';
import { Far } from '@endo/marshal';
const { details: X, quote: q } = assert;
export const makeCursorKit = (
/**
* @template K,V
* @typedef {Object} CurrentKeysKit
* @property {(k: K, v?: V) => void} assertUpdateOnAdd
* @property {(k: K) => void} assertUpdateOnDelete
* @property {Iterable<K>} iterableKeys
*/
/**
* @template K,V
* @param {() => Iterable<K>} getRawKeys
* @param {RankCompare} compare
* @param {(k: K, v?: V) => void} assertOkToAdd
* @param {((k: K) => void)=} assertOkToDelete
* @param {string=} keyName
* @returns {CurrentKeysKit<K,V>}
*/
export const makeCurrentKeysKit = (
getRawKeys,
compare,
assertOkToWrite,
assertOkToAdd,
assertOkToDelete = undefined,

@@ -16,55 +32,62 @@ keyName = 'key',

let updateCount = 0;
let sortedKeysMemo;
const cursorKit = harden({
assertUpdateOnWrite: (k, v) => {
assertOkToWrite(k, v);
updateCount += 1;
},
const assertUpdateOnAdd = (k, v = undefined) => {
assertOkToAdd(k, v);
updateCount += 1;
sortedKeysMemo = undefined;
};
assertUpdateOnDelete: assertOkToDelete
? k => {
assertOkToDelete(k);
const assertUpdateOnDelete =
assertOkToDelete === undefined
? _k => {
updateCount += 1;
sortedKeysMemo = undefined;
}
: () => {
: k => {
assertOkToDelete(k);
updateCount += 1;
},
sortedKeysMemo = undefined;
};
makeArray: (baseIterable, pattern = undefined) => {
const filter = pattern ? val => matches(harden(val), pattern) : harden;
const filtered = filterIterable(baseIterable, filter);
const sorted = harden([...filtered].sort(compare));
assertRankSorted(sorted, compare);
return sorted;
},
const getSortedKeys = () => {
if (sortedKeysMemo === undefined) {
sortedKeysMemo = harden([...getRawKeys()].sort(compare));
}
return sortedKeysMemo;
};
makeCursor: (baseIterable, pattern = undefined) => {
const currentUpdateCount = updateCount;
const notStaleFilter = () => {
assert.equal(
currentUpdateCount,
updateCount,
X`MapStore ${q(keyName)} cursor stale`,
);
return true;
};
// TODO In an implementation where the baseIterable returns its data
// already rank sorted, `makeCursor` would use the following
// code to make a cursor, and makeArray would be a snapshot of that.
// However,
// to get the correct external behavior on non-ordered representation,
// we sort in makeArray instead and then makeCursor return a cursor built
// from that.
// const filter = pattern
// ? val => notStaleFilter() && matches(val, pattern)
// : notStaleFilter;
// return filterIterable(baseIterable, filter);
const sorted = cursorKit.makeArray(baseIterable, pattern);
return filterIterable(sorted, notStaleFilter);
const iterableKeys = Far('Iterable of keys', {
[Symbol.iterator]: () => {
const generation = updateCount;
getSortedKeys();
const len = sortedKeysMemo.length;
let i = 0;
return Far('Iterator of keys', {
next: () => {
assert.equal(
generation,
updateCount,
X`Store ${q(keyName)} cursor stale`,
);
// If they're equal, then the sortedKeyMemo is the same one
// we started with.
if (i < len) {
const result = harden({ done: false, value: sortedKeysMemo[i] });
i += 1;
return result;
} else {
return harden({ done: true, value: undefined });
}
},
});
},
});
return cursorKit;
return harden({
assertUpdateOnAdd,
assertUpdateOnDelete,
iterableKeys,
});
};
harden(makeCursorKit);
harden(makeCurrentKeysKit);
// @ts-check
// eslint-disable-next-line spaced-comment
/// <reference types="ses"/>

@@ -9,11 +8,14 @@

* Keys are pass-by-copy structures (CopyArray, CopyRecord,
* CopySet, CopyMap) that end in either passable primitive data or
* CopySet, CopyBag, CopyMap) that end in either passable primitive data or
* Remotables (Far objects or their remote presences.) Keys are so named
* because they can be used as keys in MapStores and CopyMaps, as well as
* the elements of CopySets.
* the elements of CopySets and CopyBags.
*
* Keys cannot contain promises or errors, as these do not have a useful
* distributed equality semantics. Keys also cannot contain any CopyTagged
* except for those recognized as CopySets and CopyMaps.
* except for those recognized as CopySets, CopyBags, and CopyMaps.
*
* Be aware that we may recognize more CopyTaggeds over time, including
* CopyTaggeds recognized as keys.
*
* Distributed equality is location independent.

@@ -27,3 +29,3 @@ * The same two keys, passed to another location, will be equal there iff

* Patterns are pass-by-copy structures (CopyArray, CopyRecord,
* CopySet, CopyMap) that end in either Keys or Matchers. Each pattern
* CopySet, CopyBag, CopyMap) that end in either Keys or Matchers. Each pattern
* acts as a declarative passable predicate over passables, where each passable

@@ -38,4 +40,7 @@ * either passes a given pattern, or does not. Every key is also a pattern.

* Patterns also cannot contain any CopyTaggeds except for those recognized as
* CopySets, CopyMaps, or Matchers.
* CopySets, CopyBags, CopyMaps, or Matchers.
*
* Be aware that we may recognize more CopyTaggeds over time, including
* CopyTaggeds recognized as patterns.
*
* Whether a passable matches a given pattern is location independent.

@@ -53,3 +58,3 @@ * For a passable and a pattern, both passed to another location, the passable

*
* * I say "indended" above because Patterns, in order to be declarative
* * I say "intended" above because Patterns, in order to be declarative
* and passable, cannot have the generality of predicates written in a

@@ -73,2 +78,7 @@ * Turing-universal programming language. Rather, to represent the category of

/**
* @template K
* @typedef {CopyTagged} CopyBag
*/
/**
* @template K,V

@@ -93,11 +103,18 @@ * @typedef {CopyTagged} CopyMap

* Defaults to true, so please mark short lived stores explicitly.
* @property {Pattern=} schema The store will reject
* the insertion of any content that does not match the schema pattern.
* For a SetStore, this is a key pattern.
* For a MapStore, this is an entry pattern,
* i.e., a pattern over `[key,value]` pairs.
* Defaults to `M.any()`.
* @property {Pattern=} keyPattern
* @property {Pattern=} valuePattern
*/
/** @typedef {"forward" | "reverse"} Direction */
/**
* Most store methods are in one of three categories
* * lookup methods (`has`,`get`)
* * update methods (`add`,`init`,`set`,`delete`,`addAll`)
* * query methods (`snapshot`,`keys`,`values`,`entries`,`getSize`)
* * query-update methods (`clear`)
*
* WeakStores have the lookup and update methods but not the query
* or query-update methods.
* Non-weak Stores are like their corresponding WeakStore, but with the
* additional query and query-update methods.
*/

@@ -117,2 +134,3 @@ /**

* Remove the key. Throws if not found.
* @property {(keys: Iterable<K>) => void} addAll
*/

@@ -133,8 +151,7 @@

* Remove the key. Throws if not found.
* @property {(keyPattern?: Pattern) => K[]} keys
* @property {(keyPattern?: Pattern) => CopySet<K>} snapshot
* @property {(copySet: CopySet<K>) => void} addAll
* @property {(keyPattern?: Pattern,
* direction?: Direction
* ) => Iterable<K>} cursor
* @property {(keys: Iterable<K>) => void} addAll
* @property {(keyPatt?: Pattern) => Iterable<K>} keys
* @property {(keyPatt?: Pattern) => CopySet<K>} snapshot
* @property {(keyPatt?: Pattern) => number} getSize
* @property {(keyPatt?: Pattern) => void} clear
*/

@@ -158,2 +175,3 @@

* Remove the key. Throws if not found.
* @property {(entries: Iterable<[K,V]>) => void} addAll
*/

@@ -177,10 +195,12 @@

* Remove the key. Throws if not found.
* @property {(keyPattern?: Pattern) => K[]} keys
* @property {(valuePattern?: Pattern) => V[]} values
* @property {(entryPattern?: Pattern) => [K,V][]} entries
* @property {(entryPattern?: Pattern) => CopyMap<K,V>} snapshot
* @property {(copyMap: CopyMap<K,V>) => void} addAll
* @property {(entryPattern?: Pattern,
* direction?: Direction
* ) => Iterable<[K,V]>} cursor
* @property {(entries: Iterable<[K,V]>) => void} addAll
* @property {(keyPatt?: Pattern, valuePatt?: Pattern) => Iterable<K>} keys
* @property {(keyPatt?: Pattern, valuePatt?: Pattern) => Iterable<V>} values
* @property {(
* keyPatt?: Pattern,
* valuePatt?: Pattern
* ) => Iterable<[K,V]>} entries
* @property {(keyPatt?: Pattern, valuePatt?: Pattern) => CopyMap<K,V>} snapshot
* @property {(keyPatt?: Pattern, valuePatt?: Pattern) => number} getSize
* @property {(keyPatt?: Pattern, valuePatt?: Pattern) => void} clear
*/

@@ -198,13 +218,17 @@

* @template K,V
* @typedef {Object} Store
* @typedef {MapStore<K,V>} Store
* Deprecated type name `Store`. Use `MapStore` instead.
*/
/**
* @template K,V
* @typedef {Object} LegacyWeakMap
* LegacyWeakMap is deprecated. Use WeakMapStore instead.
* @property {(key: K) => boolean} has
* Check if a key exists. The key can be any JavaScript value, though the
* answer will always be false for keys that cannot be found in this map
* Check if a key exists
* @property {(key: K) => V} get
* Return a value for the key. Throws if not found.
* @property {(key: K, value: V) => void} init
* Initialize the key only if it doesn't already exist.
* The key must be one allowed by this store. For example a scalar store only
* allows primitives and remotables.
* Initialize the key only if it
* doesn't already exist
* @property {(key: K, value: V) => void} set

@@ -214,5 +238,2 @@ * Set the key. Throws if not found.

* Remove the key. Throws if not found.
* @property {(keyPattern?: Pattern) => K[]} keys
* @property {(valuePattern?: Pattern) => V[]} values
* @property {(entryPattern?: Pattern) => [K,V][]} entries
*/

@@ -222,3 +243,3 @@

* @template K,V
* @typedef {Object} LegacyWeakMap
* @typedef {Object} LegacyMap
* LegacyWeakMap is deprecated. Use WeakMapStore instead.

@@ -236,15 +257,20 @@ * @property {(key: K) => boolean} has

* Remove the key. Throws if not found.
* @property {() => Iterable<K>} keys
* @property {() => Iterable<V>} values
* @property {() => Iterable<[K,V]>} entries
* @property {() => number} getSize
* @property {() => void} clear
*/
// /////////////////////////////////////////////////////////////////////////////
/**
* @template K,V
* @callback MakeLegacyWeakMap
* @param {string} [keyName='key'] - the column name for the key
* @returns {LegacyWeakMap}
* @typedef {-1 | 0 | 1} RankComparison
* The result of a `RankCompare` function that defines a rank-order, i.e.,
* a total preorder in which different elements are always comparable but
* can be tied for the same rank. See `RankCompare`.
*/
// /////////////////////////////////////////////////////////////////////////////
/**
* @callback CompareRank
* @callback RankCompare
* Returns `-1`, `0`, or `1` depending on whether the rank of `left`

@@ -254,9 +280,9 @@ * is before, tied-with, or after the rank of `right`.

* This comparison function is valid as argument to
* `Array.prototype.sort`. This is often described as a "total order"
* but, depending on your definitions, this is technically incorrect
* because it may return `0` to indicate that two distinguishable elements,
* like `-0` and `0`, are tied, i.e., are in the same equivalence class
* as far as this ordering is concerned. If each such equivalence class is
* `Array.prototype.sort`. This is sometimes described as a "total order"
* but, depending on your definitions, this is technically incorrect because
* it may return `0` to indicate that two distinguishable elements such as
* `-0` and `0` are tied (i.e., are in the same equivalence class
* for the purposes of this ordering). If each such equivalence class is
* a *rank* and ranks are disjoint, then this "rank order" is a
* total order among these ranks. In mathematics this goes by several
* true total order over these ranks. In mathematics this goes by several
* other names such as "total preorder".

@@ -270,19 +296,97 @@ *

* lookups based on those comparisons. For example, in order to get a total
* order among ranks, we put `NaN` after all other JavaScript "number" values.
* But otherwise, we order JavaScript numbers by magnitude,
* with `-0` tied with `0`. A semantically useful ordering of JavaScript number
* values, i.e., IEEE floating point values, would compare magnitudes, and
* so agree with the rank ordering everywhere except `NaN`. An array sorted by
* rank would enable range queries by magnitude.
* order among ranks, we put `NaN` after all other JavaScript "number" values
* (i.e., IEEE 754 floating-point values). But otherwise, we rank JavaScript
* numbers by signed magnitude, with `0` and `-0` tied. A semantically useful
* ordering would also compare magnitudes, and so agree with the rank ordering
* of all values other than `NaN`. An array sorted by rank would enable range
* queries by magnitude.
* @param {Passable} left
* @param {Passable} right
* @returns {-1 | 0 | 1}
* @returns {RankComparison}
*/
/**
* @typedef {Object} ComparatorKit
* @property {CompareRank} comparator
* @property {CompareRank} antiComparator
* @typedef {RankCompare} FullCompare
* A `FullCompare` function satisfies all the invariants stated below for
* `RankCompare`'s relation with KeyCompare.
* In addition, its equality is as precise as the `KeyCompare`
* comparison defined below, in that, for all Keys `x` and `y`,
* `FullCompare(x, y) === 0` iff `KeyCompare(x, y) === 0`.
*
* For non-keys a `FullCompare` should be exactly as imprecise as
* `RankCompare`. For example, both will treat all errors as in the same
* equivalence class. Both will treat all promises as in the same
* equivalence class. Both will order taggeds the same way, which is admittedly
* weird, as some taggeds will be considered keys and other taggeds will be
* considered non-keys.
*/
/**
* @typedef {Object} RankComparatorKit
* @property {RankCompare} comparator
* @property {RankCompare} antiComparator
*/
/**
* @typedef {Object} FullComparatorKit
* @property {FullCompare} comparator
* @property {FullCompare} antiComparator
*/
/**
* @typedef {-1 | 0 | 1 | NaN} KeyComparison
* The result of a `KeyCompare` function that defines a meaningful
* and meaningfully precise partial order of `Key` values. See `KeyCompare`.
*/
/**
* @callback KeyCompare
* `compareKeys` implements a partial order over keys. As with the
* rank ordering produced by `compareRank`, -1, 0, and 1 mean
* "less than", "equivalent to", and "greater than" respectively.
* NaN means "incomparable" --- the first key is not less, equivalent,
* or greater than the second. For example, subsets over sets is
* a partial order.
*
* By using NaN for "incomparable", the normal equivalence for using
* the return value in a comparison is preserved.
* `compareKeys(left, right) >= 0` iff `left` is greater than or
* equivalent to `right` in the partial ordering.
*
* Key order (a partial order) and rank order (a total preorder) are
* co-designed so that we store passables in rank order and index into them
* with keys for key-based queries. To keep these distinct, when speaking
* informally about rank, we talk about "earlier" and "later". When speaking
* informally about keys, we talk about "smaller" and "bigger".
*
* In both orders, the return-0 case defines
* an equivalence class, i.e., those that are tied for the same place in the
* order. The global invariant that we need between the two orders is that the
* key order equivalence class is always at least as precise as the
* rank order equivalence class. IOW, if `compareKeys(X,Y) === 0` then
* `compareRank(X,Y) === 0`. But not vice versa. For example, two different
* remotables are the same rank but incomparable as keys.
*
* A further invariant is if `compareKeys(X,Y) < 0` then
* `compareRank(X,Y) < 0`, i.e., if X is smaller than Y in key order, then X
* must be earlier than Y in rank order. But not vice versa.
* X can be equivalent to or earlier than Y in rank order and still be
* incomparable with Y in key order. For example, the record `{b: 3, a: 5}` is
* earlier than the record `{b: 5, a: 3}` in rank order but they are
* incomparable as keys. And two distinct remotables such as `Far('X', {})` and
* `Far('Y', {})` are equivalent in rank order but incomparable as keys.
*
* This lets us translate a range search over the
* partial key order into a range search over rank order followed by filtering
* out those that don't match. To get this effect, we store the elements of
* a set in an array sorted in reverse rank order, from later to earlier.
* Combined with our lexicographic comparison of arrays, if set X is a subset
* of set Y then the array representing set X will be an earlier rank that the
* array representing set Y.
*
* @param {Key} left
* @param {Key} right
* @returns {KeyComparison}
*/
// ///////////////////// Should be internal only types /////////////////////////

@@ -313,3 +417,3 @@

* @param {Passable[]} sorted
* @param {CompareRank} compare
* @param {RankCompare} compare
* @param {RankCover} rankCover

@@ -378,3 +482,3 @@ * @returns {IndexCover}

* @property {() => Matcher} key
* Can be in a copySet or the key in a CopyMap.
* Can be in a copySet or CopyBag, or the key in a CopyMap.
* (Will eventually be able to a key is a MapStore.)

@@ -397,2 +501,3 @@ * All keys are patterns that match only themselves.

* @property {() => Matcher} set A CopySet
* @property {() => Matcher} bag A CopyBag
* @property {() => Matcher} map A CopyMap

@@ -431,2 +536,3 @@ * @property {() => Matcher} remotable A far object or its remote presence

* @property {(keyPatt?: Pattern) => Matcher} setOf
* @property {(keyPatt?: Pattern) => Matcher} bagOf
* @property {(keyPatt?: Pattern, valuePatt?: Pattern) => Matcher} mapOf

@@ -433,0 +539,0 @@ * @property {(

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc