@agoric/internal 0.3.3-dev-aa5ce6f.0.aa5ce6f → 0.3.3-dev-70fb93a.0.70fb93a
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +4 -4
- package/src/kv-store.d.ts +29 -0
- package/src/kv-store.d.ts.map +1 -0
- package/src/kv-store.js +253 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agoric/internal",
|
|
3
|
-
"version": "0.3.3-dev-
|
|
3
|
+
"version": "0.3.3-dev-70fb93a.0.70fb93a",
|
|
4
4
|
"description": "Externally unsupported utilities internal to agoric-sdk",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.js",
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"lint:types": "yarn run -T tsc"
|
|
21
21
|
},
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"@agoric/base-zone": "0.1.1-dev-
|
|
23
|
+
"@agoric/base-zone": "0.1.1-dev-70fb93a.0.70fb93a",
|
|
24
24
|
"@endo/cache-map": "^1.1.0",
|
|
25
25
|
"@endo/common": "^1.2.13",
|
|
26
26
|
"@endo/compartment-mapper": "^1.6.3",
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"jessie.js": "^0.3.4"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
|
-
"@agoric/cosmic-proto": "0.4.1-dev-
|
|
41
|
+
"@agoric/cosmic-proto": "0.4.1-dev-70fb93a.0.70fb93a",
|
|
42
42
|
"@endo/exo": "^1.5.12",
|
|
43
43
|
"@endo/init": "^1.1.12",
|
|
44
44
|
"@endo/ses-ava": "^1.3.2",
|
|
@@ -66,5 +66,5 @@
|
|
|
66
66
|
"typeCoverage": {
|
|
67
67
|
"atLeast": 92.89
|
|
68
68
|
},
|
|
69
|
-
"gitHead": "
|
|
69
|
+
"gitHead": "70fb93afcb746d40510dcbe3a20370786fb627db"
|
|
70
70
|
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @template [T=string]
|
|
3
|
+
* @typedef {{
|
|
4
|
+
* has: (key: string) => boolean;
|
|
5
|
+
* get: (key: string) => T | undefined;
|
|
6
|
+
* getNextKey: (previousKey: string) => string | undefined;
|
|
7
|
+
* set: (key: string, value: T) => void;
|
|
8
|
+
* delete: (key: string) => void;
|
|
9
|
+
* }} KVStore
|
|
10
|
+
*/
|
|
11
|
+
/**
|
|
12
|
+
* @param {object} db The SQLite database connection.
|
|
13
|
+
* @param {() => void} beforeMutation Called before mutating methods to
|
|
14
|
+
* establish a DB transaction if needed
|
|
15
|
+
* @param {(...args: string[]) => void} trace Called after set/delete to record
|
|
16
|
+
* a debug log
|
|
17
|
+
* @returns {KVStore}
|
|
18
|
+
*/
|
|
19
|
+
export function makeKVStore(db: object, beforeMutation: () => void, trace: (...args: string[]) => void): KVStore;
|
|
20
|
+
export function compareByCodePoints(left: any, right: any): 0 | 1 | -1;
|
|
21
|
+
export function makeKVStoreFromMap<T = unknown>(map: Map<string, T>): KVStore<T>;
|
|
22
|
+
export type KVStore<T = string> = {
|
|
23
|
+
has: (key: string) => boolean;
|
|
24
|
+
get: (key: string) => T | undefined;
|
|
25
|
+
getNextKey: (previousKey: string) => string | undefined;
|
|
26
|
+
set: (key: string, value: T) => void;
|
|
27
|
+
delete: (key: string) => void;
|
|
28
|
+
};
|
|
29
|
+
//# sourceMappingURL=kv-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"kv-store.d.ts","sourceRoot":"","sources":["kv-store.js"],"names":[],"mappings":"AAGA;;;;;;;;;GASG;AAEH;;;;;;;GAOG;AAEH,gCARW,MAAM,kBACN,MAAM,IAAI,SAEV,CAAC,GAAG,IAAI,EAAE,MAAM,EAAE,KAAK,IAAI,GAEzB,OAAO,CA0InB;AAOM,uEAoBN;AAOM,mCAJO,CAAC,iBACJ,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC,GACZ,OAAO,CAAC,CAAC,CAAC,CA8DtB;oBAxPa,CAAC,aACF;IACR,GAAG,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC;IAC9B,GAAG,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,CAAC,GAAG,SAAS,CAAC;IACpC,UAAU,EAAE,CAAC,WAAW,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAC;IACxD,GAAG,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,KAAK,IAAI,CAAC;IACrC,MAAM,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;CAC/B"}
|
package/src/kv-store.js
ADDED
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
import { Fail } from '@endo/errors';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @template [T=string]
|
|
6
|
+
* @typedef {{
|
|
7
|
+
* has: (key: string) => boolean;
|
|
8
|
+
* get: (key: string) => T | undefined;
|
|
9
|
+
* getNextKey: (previousKey: string) => string | undefined;
|
|
10
|
+
* set: (key: string, value: T) => void;
|
|
11
|
+
* delete: (key: string) => void;
|
|
12
|
+
* }} KVStore
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @param {object} db The SQLite database connection.
|
|
17
|
+
* @param {() => void} beforeMutation Called before mutating methods to
|
|
18
|
+
* establish a DB transaction if needed
|
|
19
|
+
* @param {(...args: string[]) => void} trace Called after set/delete to record
|
|
20
|
+
* a debug log
|
|
21
|
+
* @returns {KVStore}
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
export function makeKVStore(db, beforeMutation, trace) {
|
|
25
|
+
db.exec(`
|
|
26
|
+
CREATE TABLE IF NOT EXISTS kvStore (
|
|
27
|
+
key TEXT,
|
|
28
|
+
value TEXT,
|
|
29
|
+
PRIMARY KEY (key)
|
|
30
|
+
)
|
|
31
|
+
`);
|
|
32
|
+
|
|
33
|
+
const sqlKVGet = db.prepare(`
|
|
34
|
+
SELECT value
|
|
35
|
+
FROM kvStore
|
|
36
|
+
WHERE key = ?
|
|
37
|
+
`);
|
|
38
|
+
sqlKVGet.pluck(true);
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Obtain the value stored for a given key.
|
|
42
|
+
*
|
|
43
|
+
* @param {string} key The key whose value is sought.
|
|
44
|
+
* @returns {string | undefined} the (string) value for the given key, or
|
|
45
|
+
* undefined if there is no such value.
|
|
46
|
+
* @throws if key is not a string.
|
|
47
|
+
*/
|
|
48
|
+
function get(key) {
|
|
49
|
+
typeof key === 'string' || Fail`key must be a string`;
|
|
50
|
+
return sqlKVGet.get(key);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const sqlKVGetNextKey = db.prepare(`
|
|
54
|
+
SELECT key
|
|
55
|
+
FROM kvStore
|
|
56
|
+
WHERE key > ?
|
|
57
|
+
LIMIT 1
|
|
58
|
+
`);
|
|
59
|
+
sqlKVGetNextKey.pluck(true);
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* getNextKey enables callers to iterate over all keys within a given range.
|
|
63
|
+
* To build an iterator of all keys from start (inclusive) to end (exclusive),
|
|
64
|
+
* do:
|
|
65
|
+
*
|
|
66
|
+
* ```js
|
|
67
|
+
* function* iterate(start, end) {
|
|
68
|
+
* if (kvStore.has(start)) {
|
|
69
|
+
* yield start;
|
|
70
|
+
* }
|
|
71
|
+
* let prev = start;
|
|
72
|
+
* while (true) {
|
|
73
|
+
* let next = kvStore.getNextKey(prev);
|
|
74
|
+
* if (!next || next >= end) {
|
|
75
|
+
* break;
|
|
76
|
+
* }
|
|
77
|
+
* yield next;
|
|
78
|
+
* prev = next;
|
|
79
|
+
* }
|
|
80
|
+
* }
|
|
81
|
+
* ```
|
|
82
|
+
*
|
|
83
|
+
* @param {string} previousKey The key returned will always be later than this
|
|
84
|
+
* one.
|
|
85
|
+
* @returns {string | undefined} a key string, or undefined if we reach the
|
|
86
|
+
* end of the store
|
|
87
|
+
* @throws if previousKey is not a string
|
|
88
|
+
*/
|
|
89
|
+
|
|
90
|
+
function getNextKey(previousKey) {
|
|
91
|
+
typeof previousKey === 'string' || Fail`previousKey must be a string`;
|
|
92
|
+
return sqlKVGetNextKey.get(previousKey);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Test if the state contains a value for a given key.
|
|
97
|
+
*
|
|
98
|
+
* @param {string} key The key that is of interest.
|
|
99
|
+
* @returns {boolean} true if a value is stored for the key, false if not.
|
|
100
|
+
* @throws if key is not a string.
|
|
101
|
+
*/
|
|
102
|
+
function has(key) {
|
|
103
|
+
typeof key === 'string' || Fail`key must be a string`;
|
|
104
|
+
return get(key) !== undefined;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const sqlKVSet = db.prepare(`
|
|
108
|
+
INSERT INTO kvStore (key, value)
|
|
109
|
+
VALUES (?, ?)
|
|
110
|
+
ON CONFLICT DO UPDATE SET value = excluded.value
|
|
111
|
+
`);
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Store a value for a given key. The value will replace any prior value if
|
|
115
|
+
* there was one.
|
|
116
|
+
*
|
|
117
|
+
* @param {string} key The key whose value is being set.
|
|
118
|
+
* @param {string} value The value to set the key to.
|
|
119
|
+
* @throws if either parameter is not a string.
|
|
120
|
+
*/
|
|
121
|
+
function set(key, value) {
|
|
122
|
+
typeof key === 'string' || Fail`key must be a string`;
|
|
123
|
+
typeof value === 'string' || Fail`value must be a string`;
|
|
124
|
+
// synchronous read after write within a transaction is safe
|
|
125
|
+
// The transaction's overall success will be awaited during commit
|
|
126
|
+
beforeMutation();
|
|
127
|
+
sqlKVSet.run(key, value);
|
|
128
|
+
trace('set', key, value);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const sqlKVDel = db.prepare(`
|
|
132
|
+
DELETE FROM kvStore
|
|
133
|
+
WHERE key = ?
|
|
134
|
+
`);
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Remove any stored value for a given key. It is permissible for there to be
|
|
138
|
+
* no existing stored value for the key.
|
|
139
|
+
*
|
|
140
|
+
* @param {string} key The key whose value is to be deleted
|
|
141
|
+
* @throws if key is not a string.
|
|
142
|
+
*/
|
|
143
|
+
function del(key) {
|
|
144
|
+
typeof key === 'string' || Fail`key must be a string`;
|
|
145
|
+
beforeMutation();
|
|
146
|
+
sqlKVDel.run(key);
|
|
147
|
+
trace('del', key);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const kvStore = {
|
|
151
|
+
has,
|
|
152
|
+
get,
|
|
153
|
+
getNextKey,
|
|
154
|
+
set,
|
|
155
|
+
delete: del,
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
return kvStore;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// TODO: Replace compareByCodePoints and makeKVStoreFromMap and
|
|
162
|
+
// provideEnhancedKVStore with imports when
|
|
163
|
+
// available.
|
|
164
|
+
// https://github.com/Agoric/agoric-sdk/pull/10299
|
|
165
|
+
|
|
166
|
+
export const compareByCodePoints = (left, right) => {
|
|
167
|
+
const leftIter = left[Symbol.iterator]();
|
|
168
|
+
const rightIter = right[Symbol.iterator]();
|
|
169
|
+
for (;;) {
|
|
170
|
+
const { value: leftChar } = leftIter.next();
|
|
171
|
+
const { value: rightChar } = rightIter.next();
|
|
172
|
+
if (leftChar === undefined && rightChar === undefined) {
|
|
173
|
+
return 0;
|
|
174
|
+
} else if (leftChar === undefined) {
|
|
175
|
+
// left is a prefix of right.
|
|
176
|
+
return -1;
|
|
177
|
+
} else if (rightChar === undefined) {
|
|
178
|
+
// right is a prefix of left.
|
|
179
|
+
return 1;
|
|
180
|
+
}
|
|
181
|
+
const leftCodepoint = /** @type {number} */ (leftChar.codePointAt(0));
|
|
182
|
+
const rightCodepoint = /** @type {number} */ (rightChar.codePointAt(0));
|
|
183
|
+
if (leftCodepoint < rightCodepoint) return -1;
|
|
184
|
+
if (leftCodepoint > rightCodepoint) return 1;
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* @template [T=unknown]
|
|
190
|
+
* @param {Map<string, T>} map
|
|
191
|
+
* @returns {KVStore<T>}
|
|
192
|
+
*/
|
|
193
|
+
export const makeKVStoreFromMap = map => {
|
|
194
|
+
let sortedKeys;
|
|
195
|
+
let priorKeyReturned;
|
|
196
|
+
let priorKeyIndex;
|
|
197
|
+
|
|
198
|
+
const ensureSorted = () => {
|
|
199
|
+
if (sortedKeys) return;
|
|
200
|
+
sortedKeys = [...map.keys()].sort(compareByCodePoints);
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
const clearGetNextKeyCache = () => {
|
|
204
|
+
priorKeyReturned = undefined;
|
|
205
|
+
priorKeyIndex = -1;
|
|
206
|
+
};
|
|
207
|
+
clearGetNextKeyCache();
|
|
208
|
+
|
|
209
|
+
const clearSorted = () => {
|
|
210
|
+
sortedKeys = undefined;
|
|
211
|
+
clearGetNextKeyCache();
|
|
212
|
+
};
|
|
213
|
+
|
|
214
|
+
/** @type {KVStore<T>} */
|
|
215
|
+
const fakeStore = harden({
|
|
216
|
+
has: key => map.has(key),
|
|
217
|
+
get: key => map.get(key),
|
|
218
|
+
getNextKey: priorKey => {
|
|
219
|
+
assert.typeof(priorKey, 'string');
|
|
220
|
+
ensureSorted();
|
|
221
|
+
const start =
|
|
222
|
+
priorKeyReturned === undefined
|
|
223
|
+
? 0
|
|
224
|
+
: // If priorKeyReturned <= priorKey, start just after it.
|
|
225
|
+
(compareByCodePoints(priorKeyReturned, priorKey) <= 0 &&
|
|
226
|
+
priorKeyIndex + 1) ||
|
|
227
|
+
// Else if priorKeyReturned immediately follows priorKey, start at
|
|
228
|
+
// its index (and expect to return it again).
|
|
229
|
+
(sortedKeys.at(priorKeyIndex - 1) === priorKey && priorKeyIndex) ||
|
|
230
|
+
// Otherwise, start at the beginning.
|
|
231
|
+
0;
|
|
232
|
+
for (let i = start; i < sortedKeys.length; i += 1) {
|
|
233
|
+
const key = sortedKeys[i];
|
|
234
|
+
if (compareByCodePoints(key, priorKey) <= 0) continue;
|
|
235
|
+
priorKeyReturned = key;
|
|
236
|
+
priorKeyIndex = i;
|
|
237
|
+
return key;
|
|
238
|
+
}
|
|
239
|
+
// reached end without finding the key, so clear our cache
|
|
240
|
+
clearGetNextKeyCache();
|
|
241
|
+
return undefined;
|
|
242
|
+
},
|
|
243
|
+
set: (key, value) => {
|
|
244
|
+
if (!map.has(key)) clearSorted();
|
|
245
|
+
map.set(key, value);
|
|
246
|
+
},
|
|
247
|
+
delete: key => {
|
|
248
|
+
if (map.has(key)) clearSorted();
|
|
249
|
+
map.delete(key);
|
|
250
|
+
},
|
|
251
|
+
});
|
|
252
|
+
return fakeStore;
|
|
253
|
+
};
|