@atproto-labs/simple-store-memory 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/LICENSE.txt +7 -0
- package/dist/index.d.ts +52 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +56 -0
- package/dist/index.js.map +1 -0
- package/dist/util.d.ts +5 -0
- package/dist/util.d.ts.map +1 -0
- package/dist/util.js +75 -0
- package/dist/util.js.map +1 -0
- package/package.json +36 -0
- package/src/index.ts +99 -0
- package/src/util.ts +77 -0
- package/tsconfig.build.json +8 -0
- package/tsconfig.json +4 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# @atproto-labs/simple-store-memory
|
|
2
|
+
|
|
3
|
+
## 0.1.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#2482](https://github.com/bluesky-social/atproto/pull/2482) [`a8d6c1123`](https://github.com/bluesky-social/atproto/commit/a8d6c112359f5c4c0cfbe2df63443ed275f2a646) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Add OAuth provider capability & support for DPoP signed tokens
|
|
8
|
+
|
|
9
|
+
### Patch Changes
|
|
10
|
+
|
|
11
|
+
- Updated dependencies [[`a8d6c1123`](https://github.com/bluesky-social/atproto/commit/a8d6c112359f5c4c0cfbe2df63443ed275f2a646)]:
|
|
12
|
+
- @atproto-labs/simple-store@0.1.0
|
package/LICENSE.txt
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
Dual MIT/Apache-2.0 License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2022-2024 Bluesky PBC, and Contributors
|
|
4
|
+
|
|
5
|
+
Except as otherwise noted in individual files, this software is licensed under the MIT license (<http://opensource.org/licenses/MIT>), or the Apache License, Version 2.0 (<http://www.apache.org/licenses/LICENSE-2.0>).
|
|
6
|
+
|
|
7
|
+
Downstream projects and end users may chose either license individually, or both together, at their discretion. The motivation for this dual-licensing is the additional software patent assurance provided by Apache 2.0.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { SimpleStore, Key, Value } from '@atproto-labs/simple-store';
|
|
2
|
+
export type SimpleStoreMemoryOptions<K extends Key, V extends Value> = {
|
|
3
|
+
/**
|
|
4
|
+
* The maximum number of entries in the cache.
|
|
5
|
+
*/
|
|
6
|
+
max?: number;
|
|
7
|
+
/**
|
|
8
|
+
* The time-to-live of a cache entry, in milliseconds.
|
|
9
|
+
*/
|
|
10
|
+
ttl?: number;
|
|
11
|
+
/**
|
|
12
|
+
* Whether to automatically prune expired entries.
|
|
13
|
+
*/
|
|
14
|
+
ttlAutopurge?: boolean;
|
|
15
|
+
/**
|
|
16
|
+
* The maximum total size of the cache, in units defined by the sizeCalculation
|
|
17
|
+
* function.
|
|
18
|
+
*
|
|
19
|
+
* @default No limit
|
|
20
|
+
*/
|
|
21
|
+
maxSize?: number;
|
|
22
|
+
/**
|
|
23
|
+
* The maximum size of a single cache entry, in units defined by the
|
|
24
|
+
* sizeCalculation function.
|
|
25
|
+
*
|
|
26
|
+
* @default No limit
|
|
27
|
+
*/
|
|
28
|
+
maxEntrySize?: number;
|
|
29
|
+
/**
|
|
30
|
+
* A function that returns the size of a value. The size is used to determine
|
|
31
|
+
* when the cache should be pruned, based on `maxSize`.
|
|
32
|
+
*
|
|
33
|
+
* @default The (rough) size in bytes used in memory.
|
|
34
|
+
*/
|
|
35
|
+
sizeCalculation?: (value: V, key: K) => number;
|
|
36
|
+
} & ({
|
|
37
|
+
max: number;
|
|
38
|
+
} | {
|
|
39
|
+
maxSize: number;
|
|
40
|
+
} | {
|
|
41
|
+
ttl: number;
|
|
42
|
+
ttlAutopurge: boolean;
|
|
43
|
+
});
|
|
44
|
+
export declare class SimpleStoreMemory<K extends Key, V extends Value> implements SimpleStore<K, V> {
|
|
45
|
+
#private;
|
|
46
|
+
constructor({ sizeCalculation, ...options }: SimpleStoreMemoryOptions<K, V>);
|
|
47
|
+
get(key: K): V | undefined;
|
|
48
|
+
set(key: K, value: V): void;
|
|
49
|
+
del(key: K): void;
|
|
50
|
+
clear(): void;
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,4BAA4B,CAAA;AAKpE,MAAM,MAAM,wBAAwB,CAAC,CAAC,SAAS,GAAG,EAAE,CAAC,SAAS,KAAK,IAAI;IACrE;;OAEG;IACH,GAAG,CAAC,EAAE,MAAM,CAAA;IAEZ;;OAEG;IACH,GAAG,CAAC,EAAE,MAAM,CAAA;IAEZ;;OAEG;IACH,YAAY,CAAC,EAAE,OAAO,CAAA;IAEtB;;;;;OAKG;IACH,OAAO,CAAC,EAAE,MAAM,CAAA;IAEhB;;;;;OAKG;IACH,YAAY,CAAC,EAAE,MAAM,CAAA;IAErB;;;;;OAKG;IACH,eAAe,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,KAAK,MAAM,CAAA;CAC/C,GAAG,CACA;IAAE,GAAG,EAAE,MAAM,CAAA;CAAE,GACf;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,GACnB;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,YAAY,EAAE,OAAO,CAAA;CAAE,CACzC,CAAA;AAYD,qBAAa,iBAAiB,CAAC,CAAC,SAAS,GAAG,EAAE,CAAC,SAAS,KAAK,CAC3D,YAAW,WAAW,CAAC,CAAC,EAAE,CAAC,CAAC;;gBAIhB,EAAE,eAAe,EAAE,GAAG,OAAO,EAAE,EAAE,wBAAwB,CAAC,CAAC,EAAE,CAAC,CAAC;IAe3E,GAAG,CAAC,GAAG,EAAE,CAAC,GAAG,CAAC,GAAG,SAAS;IAO1B,GAAG,CAAC,GAAG,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,GAAG,IAAI;IAI3B,GAAG,CAAC,GAAG,EAAE,CAAC,GAAG,IAAI;IAIjB,KAAK,IAAI,IAAI;CAGd"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
|
|
3
|
+
if (kind === "m") throw new TypeError("Private method is not writable");
|
|
4
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
|
|
5
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
|
|
6
|
+
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
|
|
7
|
+
};
|
|
8
|
+
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
|
|
9
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
|
10
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
|
11
|
+
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
12
|
+
};
|
|
13
|
+
var _SimpleStoreMemory_cache;
|
|
14
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
15
|
+
exports.SimpleStoreMemory = void 0;
|
|
16
|
+
const lru_cache_1 = require("lru-cache");
|
|
17
|
+
const util_js_1 = require("./util.js");
|
|
18
|
+
// LRUCache does not allow storing "null", so we use a symbol to represent it.
|
|
19
|
+
const nullSymbol = Symbol('nullItem');
|
|
20
|
+
const toLruValue = (value) => (value === null ? nullSymbol : value);
|
|
21
|
+
const fromLruValue = (value) => (value === nullSymbol ? null : value);
|
|
22
|
+
class SimpleStoreMemory {
|
|
23
|
+
constructor({ sizeCalculation, ...options }) {
|
|
24
|
+
_SimpleStoreMemory_cache.set(this, void 0);
|
|
25
|
+
__classPrivateFieldSet(this, _SimpleStoreMemory_cache, new lru_cache_1.LRUCache({
|
|
26
|
+
...options,
|
|
27
|
+
allowStale: false,
|
|
28
|
+
updateAgeOnGet: false,
|
|
29
|
+
updateAgeOnHas: false,
|
|
30
|
+
sizeCalculation: sizeCalculation
|
|
31
|
+
? (value, key) => sizeCalculation(fromLruValue(value), key)
|
|
32
|
+
: options.maxEntrySize != null || options.maxSize != null
|
|
33
|
+
? // maxEntrySize and maxSize require a size calculation function.
|
|
34
|
+
util_js_1.roughSizeOfObject
|
|
35
|
+
: undefined,
|
|
36
|
+
}), "f");
|
|
37
|
+
}
|
|
38
|
+
get(key) {
|
|
39
|
+
const value = __classPrivateFieldGet(this, _SimpleStoreMemory_cache, "f").get(key);
|
|
40
|
+
if (value === undefined)
|
|
41
|
+
return undefined;
|
|
42
|
+
return fromLruValue(value);
|
|
43
|
+
}
|
|
44
|
+
set(key, value) {
|
|
45
|
+
__classPrivateFieldGet(this, _SimpleStoreMemory_cache, "f").set(key, toLruValue(value));
|
|
46
|
+
}
|
|
47
|
+
del(key) {
|
|
48
|
+
__classPrivateFieldGet(this, _SimpleStoreMemory_cache, "f").delete(key);
|
|
49
|
+
}
|
|
50
|
+
clear() {
|
|
51
|
+
__classPrivateFieldGet(this, _SimpleStoreMemory_cache, "f").clear();
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
exports.SimpleStoreMemory = SimpleStoreMemory;
|
|
55
|
+
_SimpleStoreMemory_cache = new WeakMap();
|
|
56
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;AACA,yCAAoC;AAEpC,uCAA6C;AA+C7C,8EAA8E;AAC9E,MAAM,UAAU,GAAG,MAAM,CAAC,UAAU,CAAC,CAAA;AAIrC,MAAM,UAAU,GAAG,CAAkB,KAAQ,EAAE,EAAE,CAC/C,CAAC,KAAK,KAAK,IAAI,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,KAAK,CAAkB,CAAA;AACxD,MAAM,YAAY,GAAG,CAAkB,KAAoB,EAAE,EAAE,CAC7D,CAAC,KAAK,KAAK,UAAU,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,CAAM,CAAA;AAE5C,MAAa,iBAAiB;IAK5B,YAAY,EAAE,eAAe,EAAE,GAAG,OAAO,EAAkC;QAF3E,2CAAkC;QAGhC,uBAAA,IAAI,4BAAU,IAAI,oBAAQ,CAAmB;YAC3C,GAAG,OAAO;YACV,UAAU,EAAE,KAAK;YACjB,cAAc,EAAE,KAAK;YACrB,cAAc,EAAE,KAAK;YACrB,eAAe,EAAE,eAAe;gBAC9B,CAAC,CAAC,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE,CAAC,eAAe,CAAC,YAAY,CAAC,KAAK,CAAC,EAAE,GAAG,CAAC;gBAC3D,CAAC,CAAC,OAAO,CAAC,YAAY,IAAI,IAAI,IAAI,OAAO,CAAC,OAAO,IAAI,IAAI;oBACvD,CAAC,CAAC,gEAAgE;wBAChE,2BAAiB;oBACnB,CAAC,CAAC,SAAS;SAChB,CAAC,MAAA,CAAA;IACJ,CAAC;IAED,GAAG,CAAC,GAAM;QACR,MAAM,KAAK,GAAG,uBAAA,IAAI,gCAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;QAClC,IAAI,KAAK,KAAK,SAAS;YAAE,OAAO,SAAS,CAAA;QAEzC,OAAO,YAAY,CAAC,KAAK,CAAC,CAAA;IAC5B,CAAC;IAED,GAAG,CAAC,GAAM,EAAE,KAAQ;QAClB,uBAAA,IAAI,gCAAO,CAAC,GAAG,CAAC,GAAG,EAAE,UAAU,CAAC,KAAK,CAAC,CAAC,CAAA;IACzC,CAAC;IAED,GAAG,CAAC,GAAM;QACR,uBAAA,IAAI,gCAAO,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;IACzB,CAAC;IAED,KAAK;QACH,uBAAA,IAAI,gCAAO,CAAC,KAAK,EAAE,CAAA;IACrB,CAAC;CACF;AAtCD,8CAsCC"}
|
package/dist/util.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"util.d.ts","sourceRoot":"","sources":["../src/util.ts"],"names":[],"mappings":"AAEA;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAuExD"}
|
package/dist/util.js
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.roughSizeOfObject = void 0;
|
|
4
|
+
const knownSizes = new WeakMap();
|
|
5
|
+
/**
|
|
6
|
+
* @see {@link https://stackoverflow.com/a/11900218/356537}
|
|
7
|
+
*/
|
|
8
|
+
function roughSizeOfObject(value) {
|
|
9
|
+
const objectList = new Set();
|
|
10
|
+
const stack = [value]; // This would be more efficient using a circular buffer
|
|
11
|
+
let bytes = 0;
|
|
12
|
+
while (stack.length) {
|
|
13
|
+
const value = stack.pop();
|
|
14
|
+
// > All objects on the heap start with a shape descriptor, which takes one
|
|
15
|
+
// > pointer size (usually 4 bytes these days, thanks to "pointer
|
|
16
|
+
// > compression" on 64-bit platforms).
|
|
17
|
+
switch (typeof value) {
|
|
18
|
+
// Types are ordered by frequency
|
|
19
|
+
case 'string':
|
|
20
|
+
// https://stackoverflow.com/a/68791382/356537
|
|
21
|
+
bytes += 12 + 4 * Math.ceil(value.length / 4);
|
|
22
|
+
break;
|
|
23
|
+
case 'number':
|
|
24
|
+
bytes += 12; // Shape descriptor + double
|
|
25
|
+
break;
|
|
26
|
+
case 'boolean':
|
|
27
|
+
bytes += 4; // Shape descriptor
|
|
28
|
+
break;
|
|
29
|
+
case 'object':
|
|
30
|
+
bytes += 4; // Shape descriptor
|
|
31
|
+
if (value === null) {
|
|
32
|
+
break;
|
|
33
|
+
}
|
|
34
|
+
if (knownSizes.has(value)) {
|
|
35
|
+
bytes += knownSizes.get(value);
|
|
36
|
+
break;
|
|
37
|
+
}
|
|
38
|
+
if (objectList.has(value))
|
|
39
|
+
continue;
|
|
40
|
+
objectList.add(value);
|
|
41
|
+
if (Array.isArray(value)) {
|
|
42
|
+
bytes += 4;
|
|
43
|
+
stack.push(...value);
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
bytes += 8;
|
|
47
|
+
const keys = Object.getOwnPropertyNames(value);
|
|
48
|
+
for (let i = 0; i < keys.length; i++) {
|
|
49
|
+
bytes += 4;
|
|
50
|
+
const key = keys[i];
|
|
51
|
+
const val = value[key];
|
|
52
|
+
if (val !== undefined)
|
|
53
|
+
stack.push(val);
|
|
54
|
+
stack.push(key);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
break;
|
|
58
|
+
case 'function':
|
|
59
|
+
bytes += 8; // Shape descriptor + pointer (assuming functions are shared)
|
|
60
|
+
break;
|
|
61
|
+
case 'symbol':
|
|
62
|
+
bytes += 8; // Shape descriptor + pointer
|
|
63
|
+
break;
|
|
64
|
+
case 'bigint':
|
|
65
|
+
bytes += 16; // Shape descriptor + BigInt
|
|
66
|
+
break;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
if (typeof value === 'object' && value !== null) {
|
|
70
|
+
knownSizes.set(value, bytes);
|
|
71
|
+
}
|
|
72
|
+
return bytes;
|
|
73
|
+
}
|
|
74
|
+
exports.roughSizeOfObject = roughSizeOfObject;
|
|
75
|
+
//# sourceMappingURL=util.js.map
|
package/dist/util.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"util.js","sourceRoot":"","sources":["../src/util.ts"],"names":[],"mappings":";;;AAAA,MAAM,UAAU,GAAG,IAAI,OAAO,EAAkB,CAAA;AAEhD;;GAEG;AACH,SAAgB,iBAAiB,CAAC,KAAc;IAC9C,MAAM,UAAU,GAAG,IAAI,GAAG,EAAE,CAAA;IAC5B,MAAM,KAAK,GAAG,CAAC,KAAK,CAAC,CAAA,CAAC,uDAAuD;IAC7E,IAAI,KAAK,GAAG,CAAC,CAAA;IAEb,OAAO,KAAK,CAAC,MAAM,EAAE,CAAC;QACpB,MAAM,KAAK,GAAG,KAAK,CAAC,GAAG,EAAE,CAAA;QAEzB,2EAA2E;QAC3E,iEAAiE;QACjE,uCAAuC;QAEvC,QAAQ,OAAO,KAAK,EAAE,CAAC;YACrB,iCAAiC;YACjC,KAAK,QAAQ;gBACX,8CAA8C;gBAC9C,KAAK,IAAI,EAAE,GAAG,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAA;gBAC7C,MAAK;YACP,KAAK,QAAQ;gBACX,KAAK,IAAI,EAAE,CAAA,CAAC,4BAA4B;gBACxC,MAAK;YACP,KAAK,SAAS;gBACZ,KAAK,IAAI,CAAC,CAAA,CAAC,mBAAmB;gBAC9B,MAAK;YACP,KAAK,QAAQ;gBACX,KAAK,IAAI,CAAC,CAAA,CAAC,mBAAmB;gBAE9B,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;oBACnB,MAAK;gBACP,CAAC;gBAED,IAAI,UAAU,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC;oBAC1B,KAAK,IAAI,UAAU,CAAC,GAAG,CAAC,KAAK,CAAE,CAAA;oBAC/B,MAAK;gBACP,CAAC;gBAED,IAAI,UAAU,CAAC,GAAG,CAAC,KAAK,CAAC;oBAAE,SAAQ;gBACnC,UAAU,CAAC,GAAG,CAAC,KAAK,CAAC,CAAA;gBAErB,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;oBACzB,KAAK,IAAI,CAAC,CAAA;oBACV,KAAK,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC,CAAA;gBACtB,CAAC;qBAAM,CAAC;oBACN,KAAK,IAAI,CAAC,CAAA;oBACV,MAAM,IAAI,GAAG,MAAM,CAAC,mBAAmB,CAAC,KAAK,CAAC,CAAA;oBAC9C,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;wBACrC,KAAK,IAAI,CAAC,CAAA;wBACV,MAAM,GAAG,GAAG,IAAI,CAAC,CAAC,CAAC,CAAA;wBACnB,MAAM,GAAG,GAAG,KAAK,CAAC,GAAG,CAAC,CAAA;wBACtB,IAAI,GAAG,KAAK,SAAS;4BAAE,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;wBACtC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAA;oBACjB,CAAC;gBACH,CAAC;gBACD,MAAK;YACP,KAAK,UAAU;gBACb,KAAK,IAAI,CAAC,CAAA,CAAC,6DAA6D;gBACxE,MAAK;YACP,KAAK,QAAQ;gBACX,KAAK,IAAI,CAAC,CAAA,CAAC,6BAA6B;gBACxC,MAAK;YACP,KAAK,QAAQ;gBACX,KAAK,IAAI,EAAE,CAAA,CAAC,4BAA4B;gBACxC,MAAK;QACT,CAAC;IACH,CAAC;IAED,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;QAChD,UAAU,CAAC,GAAG,CAAC,KAAK,EAAE,KAAK,CAAC,CAAA;IAC9B,CAAC;IAED,OAAO,KAAK,CAAA;AACd,CAAC;AAvED,8CAuEC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@atproto-labs/simple-store-memory",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"description": "Memory based simple-store implementation",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"cache",
|
|
8
|
+
"isomorphic",
|
|
9
|
+
"memory"
|
|
10
|
+
],
|
|
11
|
+
"homepage": "https://atproto.com",
|
|
12
|
+
"repository": {
|
|
13
|
+
"type": "git",
|
|
14
|
+
"url": "https://github.com/bluesky-social/atproto",
|
|
15
|
+
"directory": "packages/internal/simple-store-memory"
|
|
16
|
+
},
|
|
17
|
+
"type": "commonjs",
|
|
18
|
+
"main": "dist/index.js",
|
|
19
|
+
"types": "dist/index.d.ts",
|
|
20
|
+
"exports": {
|
|
21
|
+
".": {
|
|
22
|
+
"types": "./dist/index.d.ts",
|
|
23
|
+
"default": "./dist/index.js"
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"lru-cache": "^10.2.0",
|
|
28
|
+
"@atproto-labs/simple-store": "0.1.0"
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"typescript": "^5.3.3"
|
|
32
|
+
},
|
|
33
|
+
"scripts": {
|
|
34
|
+
"build": "tsc --build tsconfig.build.json"
|
|
35
|
+
}
|
|
36
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { SimpleStore, Key, Value } from '@atproto-labs/simple-store'
|
|
2
|
+
import { LRUCache } from 'lru-cache'
|
|
3
|
+
|
|
4
|
+
import { roughSizeOfObject } from './util.js'
|
|
5
|
+
|
|
6
|
+
export type SimpleStoreMemoryOptions<K extends Key, V extends Value> = {
|
|
7
|
+
/**
|
|
8
|
+
* The maximum number of entries in the cache.
|
|
9
|
+
*/
|
|
10
|
+
max?: number
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* The time-to-live of a cache entry, in milliseconds.
|
|
14
|
+
*/
|
|
15
|
+
ttl?: number
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Whether to automatically prune expired entries.
|
|
19
|
+
*/
|
|
20
|
+
ttlAutopurge?: boolean
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* The maximum total size of the cache, in units defined by the sizeCalculation
|
|
24
|
+
* function.
|
|
25
|
+
*
|
|
26
|
+
* @default No limit
|
|
27
|
+
*/
|
|
28
|
+
maxSize?: number
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* The maximum size of a single cache entry, in units defined by the
|
|
32
|
+
* sizeCalculation function.
|
|
33
|
+
*
|
|
34
|
+
* @default No limit
|
|
35
|
+
*/
|
|
36
|
+
maxEntrySize?: number
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* A function that returns the size of a value. The size is used to determine
|
|
40
|
+
* when the cache should be pruned, based on `maxSize`.
|
|
41
|
+
*
|
|
42
|
+
* @default The (rough) size in bytes used in memory.
|
|
43
|
+
*/
|
|
44
|
+
sizeCalculation?: (value: V, key: K) => number
|
|
45
|
+
} & ( // Memory is not infinite, so at least one pruning option is required.
|
|
46
|
+
| { max: number }
|
|
47
|
+
| { maxSize: number }
|
|
48
|
+
| { ttl: number; ttlAutopurge: boolean }
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
// LRUCache does not allow storing "null", so we use a symbol to represent it.
|
|
52
|
+
const nullSymbol = Symbol('nullItem')
|
|
53
|
+
type AsLruValue<V extends Value> = V extends null
|
|
54
|
+
? typeof nullSymbol
|
|
55
|
+
: Exclude<V, null>
|
|
56
|
+
const toLruValue = <V extends Value>(value: V) =>
|
|
57
|
+
(value === null ? nullSymbol : value) as AsLruValue<V>
|
|
58
|
+
const fromLruValue = <V extends Value>(value: AsLruValue<V>) =>
|
|
59
|
+
(value === nullSymbol ? null : value) as V
|
|
60
|
+
|
|
61
|
+
export class SimpleStoreMemory<K extends Key, V extends Value>
|
|
62
|
+
implements SimpleStore<K, V>
|
|
63
|
+
{
|
|
64
|
+
#cache: LRUCache<K, AsLruValue<V>>
|
|
65
|
+
|
|
66
|
+
constructor({ sizeCalculation, ...options }: SimpleStoreMemoryOptions<K, V>) {
|
|
67
|
+
this.#cache = new LRUCache<K, AsLruValue<V>>({
|
|
68
|
+
...options,
|
|
69
|
+
allowStale: false,
|
|
70
|
+
updateAgeOnGet: false,
|
|
71
|
+
updateAgeOnHas: false,
|
|
72
|
+
sizeCalculation: sizeCalculation
|
|
73
|
+
? (value, key) => sizeCalculation(fromLruValue(value), key)
|
|
74
|
+
: options.maxEntrySize != null || options.maxSize != null
|
|
75
|
+
? // maxEntrySize and maxSize require a size calculation function.
|
|
76
|
+
roughSizeOfObject
|
|
77
|
+
: undefined,
|
|
78
|
+
})
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
get(key: K): V | undefined {
|
|
82
|
+
const value = this.#cache.get(key)
|
|
83
|
+
if (value === undefined) return undefined
|
|
84
|
+
|
|
85
|
+
return fromLruValue(value)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
set(key: K, value: V): void {
|
|
89
|
+
this.#cache.set(key, toLruValue(value))
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
del(key: K): void {
|
|
93
|
+
this.#cache.delete(key)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
clear(): void {
|
|
97
|
+
this.#cache.clear()
|
|
98
|
+
}
|
|
99
|
+
}
|
package/src/util.ts
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
const knownSizes = new WeakMap<object, number>()
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @see {@link https://stackoverflow.com/a/11900218/356537}
|
|
5
|
+
*/
|
|
6
|
+
export function roughSizeOfObject(value: unknown): number {
|
|
7
|
+
const objectList = new Set()
|
|
8
|
+
const stack = [value] // This would be more efficient using a circular buffer
|
|
9
|
+
let bytes = 0
|
|
10
|
+
|
|
11
|
+
while (stack.length) {
|
|
12
|
+
const value = stack.pop()
|
|
13
|
+
|
|
14
|
+
// > All objects on the heap start with a shape descriptor, which takes one
|
|
15
|
+
// > pointer size (usually 4 bytes these days, thanks to "pointer
|
|
16
|
+
// > compression" on 64-bit platforms).
|
|
17
|
+
|
|
18
|
+
switch (typeof value) {
|
|
19
|
+
// Types are ordered by frequency
|
|
20
|
+
case 'string':
|
|
21
|
+
// https://stackoverflow.com/a/68791382/356537
|
|
22
|
+
bytes += 12 + 4 * Math.ceil(value.length / 4)
|
|
23
|
+
break
|
|
24
|
+
case 'number':
|
|
25
|
+
bytes += 12 // Shape descriptor + double
|
|
26
|
+
break
|
|
27
|
+
case 'boolean':
|
|
28
|
+
bytes += 4 // Shape descriptor
|
|
29
|
+
break
|
|
30
|
+
case 'object':
|
|
31
|
+
bytes += 4 // Shape descriptor
|
|
32
|
+
|
|
33
|
+
if (value === null) {
|
|
34
|
+
break
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (knownSizes.has(value)) {
|
|
38
|
+
bytes += knownSizes.get(value)!
|
|
39
|
+
break
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (objectList.has(value)) continue
|
|
43
|
+
objectList.add(value)
|
|
44
|
+
|
|
45
|
+
if (Array.isArray(value)) {
|
|
46
|
+
bytes += 4
|
|
47
|
+
stack.push(...value)
|
|
48
|
+
} else {
|
|
49
|
+
bytes += 8
|
|
50
|
+
const keys = Object.getOwnPropertyNames(value)
|
|
51
|
+
for (let i = 0; i < keys.length; i++) {
|
|
52
|
+
bytes += 4
|
|
53
|
+
const key = keys[i]
|
|
54
|
+
const val = value[key]
|
|
55
|
+
if (val !== undefined) stack.push(val)
|
|
56
|
+
stack.push(key)
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
break
|
|
60
|
+
case 'function':
|
|
61
|
+
bytes += 8 // Shape descriptor + pointer (assuming functions are shared)
|
|
62
|
+
break
|
|
63
|
+
case 'symbol':
|
|
64
|
+
bytes += 8 // Shape descriptor + pointer
|
|
65
|
+
break
|
|
66
|
+
case 'bigint':
|
|
67
|
+
bytes += 16 // Shape descriptor + BigInt
|
|
68
|
+
break
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (typeof value === 'object' && value !== null) {
|
|
73
|
+
knownSizes.set(value, bytes)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return bytes
|
|
77
|
+
}
|
package/tsconfig.json
ADDED