@agoric/store
Advanced tools
Comparing version 0.6.9-dev-caa75cd.0 to 0.6.9-dev-cc79ff6.0
{ | ||
"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" | ||
} |
// @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); |
232
src/types.js
// @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 {( |
Manifest confusion
Supply chain riskThis package has inconsistent metadata. This could be malicious or caused by an error when publishing the package.
Found 1 instance in 1 package
Manifest confusion
Supply chain riskThis package has inconsistent metadata. This could be malicious or caused by an error when publishing the package.
Found 1 instance in 1 package
170113
24
4197
+ Added@endo/marshal@^0.5.4
+ Added@endo/env-options@0.1.4(transitive)
+ Added@endo/eventual-send@0.14.8(transitive)
+ Added@endo/marshal@0.5.4(transitive)
+ Added@endo/nat@4.1.31(transitive)
+ Added@endo/promise-kit@0.2.60(transitive)
+ Addedses@0.18.8(transitive)