@atproto-labs/simple-store 0.1.2 → 0.3.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 +18 -0
- package/LICENSE.txt +1 -1
- package/dist/cached-getter.d.ts +19 -12
- package/dist/cached-getter.d.ts.map +1 -1
- package/dist/cached-getter.js +21 -17
- package/dist/cached-getter.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/simple-store.d.ts +3 -3
- package/dist/simple-store.d.ts.map +1 -1
- package/dist/util.d.ts +7 -0
- package/dist/util.d.ts.map +1 -0
- package/dist/util.js +3 -0
- package/dist/util.js.map +1 -0
- package/package.json +1 -1
- package/src/cached-getter.ts +61 -29
- package/src/index.ts +1 -0
- package/src/simple-store.ts +3 -3
- package/src/util.ts +5 -0
- package/tsconfig.build.tsbuildinfo +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# @atproto-labs/simple-store
|
|
2
2
|
|
|
3
|
+
## 0.3.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#4108](https://github.com/bluesky-social/atproto/pull/4108) [`f9dc9aa4c`](https://github.com/bluesky-social/atproto/commit/f9dc9aa4c9eaf2f82d140fbf011a9015e7f1a00d) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Allow `Key` type to be anything (except nullable values)
|
|
8
|
+
|
|
9
|
+
- [#4108](https://github.com/bluesky-social/atproto/pull/4108) [`f9dc9aa4c`](https://github.com/bluesky-social/atproto/commit/f9dc9aa4c9eaf2f82d140fbf011a9015e7f1a00d) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Update `Getter` function's option argument to `GetterOptions`
|
|
10
|
+
|
|
11
|
+
## 0.2.0
|
|
12
|
+
|
|
13
|
+
### Minor Changes
|
|
14
|
+
|
|
15
|
+
- [#3776](https://github.com/bluesky-social/atproto/pull/3776) [`0d77d1b55`](https://github.com/bluesky-social/atproto/commit/0d77d1b550a58117aee8f7f1e2be24d255ade9e4) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Remove `bind` method from `CachedGetter`
|
|
16
|
+
|
|
17
|
+
### Patch Changes
|
|
18
|
+
|
|
19
|
+
- [#3776](https://github.com/bluesky-social/atproto/pull/3776) [`0d77d1b55`](https://github.com/bluesky-social/atproto/commit/0d77d1b550a58117aee8f7f1e2be24d255ade9e4) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Add `context` getter option to `CachedGetter` class
|
|
20
|
+
|
|
3
21
|
## 0.1.2
|
|
4
22
|
|
|
5
23
|
### Patch Changes
|
package/LICENSE.txt
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Dual MIT/Apache-2.0 License
|
|
2
2
|
|
|
3
|
-
Copyright (c) 2022-
|
|
3
|
+
Copyright (c) 2022-2025 Bluesky Social PBC, and Contributors
|
|
4
4
|
|
|
5
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
6
|
|
package/dist/cached-getter.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import { GetOptions, Key, SimpleStore, Value } from './simple-store.js';
|
|
2
|
+
import { Awaitable, ContextOptions } from './util.js';
|
|
3
|
+
export type { GetOptions };
|
|
4
|
+
export type GetCachedOptions<C = void> = ContextOptions<C> & {
|
|
3
5
|
signal?: AbortSignal;
|
|
4
6
|
/**
|
|
5
7
|
* Do not use the cache to get the value. Always get a new value from the
|
|
@@ -19,8 +21,13 @@ export type GetCachedOptions = {
|
|
|
19
21
|
*/
|
|
20
22
|
allowStale?: boolean;
|
|
21
23
|
};
|
|
22
|
-
export type
|
|
23
|
-
|
|
24
|
+
export type GetterOptions<C = void> = {
|
|
25
|
+
context: C extends void ? undefined : C;
|
|
26
|
+
noCache: boolean;
|
|
27
|
+
signal?: AbortSignal;
|
|
28
|
+
};
|
|
29
|
+
export type Getter<K extends Key, V extends Value, C = void> = (key: K, options: GetterOptions<C>, storedValue: undefined | V) => Awaitable<V>;
|
|
30
|
+
export type CachedGetterOptions<K extends Key, V extends Value> = {
|
|
24
31
|
isStale?: (key: K, value: V) => boolean | PromiseLike<boolean>;
|
|
25
32
|
onStoreError?: (err: unknown, key: K, value: V) => void | PromiseLike<void>;
|
|
26
33
|
deleteOnError?: (err: unknown, key: K, value: V) => boolean | PromiseLike<boolean>;
|
|
@@ -29,15 +36,15 @@ export type CachedGetterOptions<K, V> = {
|
|
|
29
36
|
* Wrapper utility that uses a store to speed up the retrieval of values from an
|
|
30
37
|
* (expensive) getter function.
|
|
31
38
|
*/
|
|
32
|
-
export declare class CachedGetter<K extends Key = string, V extends Value = Value> {
|
|
33
|
-
readonly getter: Getter<K, V>;
|
|
39
|
+
export declare class CachedGetter<K extends Key = string, V extends Value = Value, C = void> {
|
|
40
|
+
readonly getter: Getter<K, V, C>;
|
|
34
41
|
readonly store: SimpleStore<K, V>;
|
|
35
|
-
readonly options
|
|
36
|
-
private pending;
|
|
37
|
-
constructor(getter: Getter<K, V>, store: SimpleStore<K, V>, options?:
|
|
38
|
-
get(key: K, options?: GetCachedOptions): Promise<V>;
|
|
39
|
-
|
|
40
|
-
getStored(key: K, options?:
|
|
42
|
+
readonly options: CachedGetterOptions<K, V>;
|
|
43
|
+
private readonly pending;
|
|
44
|
+
constructor(getter: Getter<K, V, C>, store: SimpleStore<K, V>, options?: CachedGetterOptions<K, V>);
|
|
45
|
+
get(key: C extends void ? K : never, options?: GetCachedOptions<C>): Promise<V>;
|
|
46
|
+
get(key: C extends void ? never : K, options: GetCachedOptions<C>): Promise<V>;
|
|
47
|
+
getStored(key: K, options?: GetOptions): Promise<V | undefined>;
|
|
41
48
|
setStored(key: K, value: V): Promise<void>;
|
|
42
49
|
delStored(key: K, _cause?: unknown): Promise<void>;
|
|
43
50
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cached-getter.d.ts","sourceRoot":"","sources":["../src/cached-getter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"cached-getter.d.ts","sourceRoot":"","sources":["../src/cached-getter.ts"],"names":[],"mappings":"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"}
|
package/dist/cached-getter.js
CHANGED
|
@@ -8,7 +8,7 @@ const returnFalse = () => false;
|
|
|
8
8
|
* (expensive) getter function.
|
|
9
9
|
*/
|
|
10
10
|
class CachedGetter {
|
|
11
|
-
constructor(getter, store, options) {
|
|
11
|
+
constructor(getter, store, options = {}) {
|
|
12
12
|
Object.defineProperty(this, "getter", {
|
|
13
13
|
enumerable: true,
|
|
14
14
|
configurable: true,
|
|
@@ -34,26 +34,30 @@ class CachedGetter {
|
|
|
34
34
|
value: new Map()
|
|
35
35
|
});
|
|
36
36
|
}
|
|
37
|
-
async get(key,
|
|
38
|
-
|
|
39
|
-
const isStale = this.options
|
|
40
|
-
const allowStored =
|
|
37
|
+
async get(key, { signal, context, allowStale = false, noCache = false, } = {}) {
|
|
38
|
+
signal?.throwIfAborted();
|
|
39
|
+
const { isStale, deleteOnError } = this.options;
|
|
40
|
+
const allowStored = noCache
|
|
41
41
|
? returnFalse // Never allow stored values to be returned
|
|
42
|
-
:
|
|
42
|
+
: allowStale || isStale == null
|
|
43
43
|
? returnTrue // Always allow stored values to be returned
|
|
44
44
|
: async (value) => !(await isStale(key, value));
|
|
45
45
|
// As long as concurrent requests are made for the same key, only one
|
|
46
|
-
// request will be made to the
|
|
47
|
-
// because there is no async operation between the while() loop and
|
|
48
|
-
// pending.set() call. Because of the
|
|
46
|
+
// request will be made to the getStored & getter functions at a time. This
|
|
47
|
+
// works because there is no async operation between the while() loop and
|
|
48
|
+
// the pending.set() call below. Because of the single threaded nature of
|
|
49
49
|
// JavaScript, the pending item will be set before the next iteration of the
|
|
50
|
-
// while loop.
|
|
50
|
+
// while loop of any concurrent request.
|
|
51
51
|
let previousExecutionFlow;
|
|
52
52
|
while ((previousExecutionFlow = this.pending.get(key))) {
|
|
53
53
|
try {
|
|
54
|
+
// If a concurrent request is already in progress, wait for it to finish
|
|
54
55
|
const { isFresh, value } = await previousExecutionFlow;
|
|
56
|
+
// Use the concurrent request's result if it is fresh
|
|
55
57
|
if (isFresh)
|
|
56
58
|
return value;
|
|
59
|
+
// Use the concurrent request's result if not fresh (loaded from the
|
|
60
|
+
// store), and matches the conditions for using a stored value.
|
|
57
61
|
if (await allowStored(value))
|
|
58
62
|
return value;
|
|
59
63
|
}
|
|
@@ -61,11 +65,12 @@ class CachedGetter {
|
|
|
61
65
|
// Ignore errors from previous execution flows (they will have been
|
|
62
66
|
// propagated by that flow).
|
|
63
67
|
}
|
|
64
|
-
|
|
68
|
+
// Break the loop if the signal was aborted
|
|
69
|
+
signal?.throwIfAborted();
|
|
65
70
|
}
|
|
66
71
|
const currentExecutionFlow = Promise.resolve()
|
|
67
72
|
.then(async () => {
|
|
68
|
-
const storedValue = await this.getStored(key,
|
|
73
|
+
const storedValue = await this.getStored(key, { signal });
|
|
69
74
|
if (storedValue !== undefined && (await allowStored(storedValue))) {
|
|
70
75
|
// Use the stored value as return value for the current execution
|
|
71
76
|
// flow. Notify other concurrent execution flows (that should be
|
|
@@ -74,11 +79,13 @@ class CachedGetter {
|
|
|
74
79
|
return { isFresh: false, value: storedValue };
|
|
75
80
|
}
|
|
76
81
|
return Promise.resolve()
|
|
77
|
-
.then(async () =>
|
|
82
|
+
.then(async () => {
|
|
83
|
+
const options = { signal, noCache, context };
|
|
84
|
+
return this.getter.call(null, key, options, storedValue);
|
|
85
|
+
})
|
|
78
86
|
.catch(async (err) => {
|
|
79
87
|
if (storedValue !== undefined) {
|
|
80
88
|
try {
|
|
81
|
-
const deleteOnError = this.options?.deleteOnError;
|
|
82
89
|
if (await deleteOnError?.(err, key, storedValue)) {
|
|
83
90
|
await this.delStored(key, err);
|
|
84
91
|
}
|
|
@@ -109,9 +116,6 @@ class CachedGetter {
|
|
|
109
116
|
const { value } = await currentExecutionFlow;
|
|
110
117
|
return value;
|
|
111
118
|
}
|
|
112
|
-
bind(key) {
|
|
113
|
-
return async (options) => this.get(key, options);
|
|
114
|
-
}
|
|
115
119
|
async getStored(key, options) {
|
|
116
120
|
try {
|
|
117
121
|
return await this.store.get(key, options);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cached-getter.js","sourceRoot":"","sources":["../src/cached-getter.ts"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"cached-getter.js","sourceRoot":"","sources":["../src/cached-getter.ts"],"names":[],"mappings":";;;AAmDA,MAAM,UAAU,GAAG,GAAG,EAAE,CAAC,IAAI,CAAA;AAC7B,MAAM,WAAW,GAAG,GAAG,EAAE,CAAC,KAAK,CAAA;AAE/B;;;GAGG;AACH,MAAa,YAAY;IAOvB,YACW,MAAuB,EACvB,KAAwB,EACxB,UAAqC,EAAE;QAFhD;;;;mBAAS,MAAM;WAAiB;QAChC;;;;mBAAS,KAAK;WAAmB;QACjC;;;;mBAAS,OAAO;WAAgC;QALjC;;;;mBAAU,IAAI,GAAG,EAAqB;WAAA;IAMpD,CAAC;IAUJ,KAAK,CAAC,GAAG,CACP,GAAM,EACN,EACE,MAAM,EACN,OAAO,EACP,UAAU,GAAG,KAAK,EAClB,OAAO,GAAG,KAAK,MACb,EAAyB;QAE7B,MAAM,EAAE,cAAc,EAAE,CAAA;QAExB,MAAM,EAAE,OAAO,EAAE,aAAa,EAAE,GAAG,IAAI,CAAC,OAAO,CAAA;QAE/C,MAAM,WAAW,GAAqC,OAAO;YAC3D,CAAC,CAAC,WAAW,CAAC,2CAA2C;YACzD,CAAC,CAAC,UAAU,IAAI,OAAO,IAAI,IAAI;gBAC7B,CAAC,CAAC,UAAU,CAAC,4CAA4C;gBACzD,CAAC,CAAC,KAAK,EAAE,KAAQ,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,OAAO,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC,CAAA;QAEtD,qEAAqE;QACrE,2EAA2E;QAC3E,yEAAyE;QACzE,yEAAyE;QACzE,4EAA4E;QAC5E,wCAAwC;QACxC,IAAI,qBAAiD,CAAA;QACrD,OAAO,CAAC,qBAAqB,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;YACvD,IAAI,CAAC;gBACH,wEAAwE;gBACxE,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,GAAG,MAAM,qBAAqB,CAAA;gBAEtD,qDAAqD;gBACrD,IAAI,OAAO;oBAAE,OAAO,KAAK,CAAA;gBACzB,oEAAoE;gBACpE,+DAA+D;gBAC/D,IAAI,MAAM,WAAW,CAAC,KAAK,CAAC;oBAAE,OAAO,KAAK,CAAA;YAC5C,CAAC;YAAC,MAAM,CAAC;gBACP,mEAAmE;gBACnE,4BAA4B;YAC9B,CAAC;YAED,2CAA2C;YAC3C,MAAM,EAAE,cAAc,EAAE,CAAA;QAC1B,CAAC;QAED,MAAM,oBAAoB,GAAmB,OAAO,CAAC,OAAO,EAAE;aAC3D,IAAI,CAAC,KAAK,IAAI,EAAE;YACf,MAAM,WAAW,GAAG,MAAM,IAAI,CAAC,SAAS,CAAC,GAAG,EAAE,EAAE,MAAM,EAAE,CAAC,CAAA;YAEzD,IAAI,WAAW,KAAK,SAAS,IAAI,CAAC,MAAM,WAAW,CAAC,WAAW,CAAC,CAAC,EAAE,CAAC;gBAClE,iEAAiE;gBACjE,gEAAgE;gBAChE,sEAAsE;gBACtE,8DAA8D;gBAC9D,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,WAAW,EAAE,CAAA;YAC/C,CAAC;YAED,OAAO,OAAO,CAAC,OAAO,EAAE;iBACrB,IAAI,CAAC,KAAK,IAAI,EAAE;gBACf,MAAM,OAAO,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,EAAsB,CAAA;gBAChE,OAAO,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,GAAG,EAAE,OAAO,EAAE,WAAW,CAAC,CAAA;YAC1D,CAAC,CAAC;iBACD,KAAK,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE;gBACnB,IAAI,WAAW,KAAK,SAAS,EAAE,CAAC;oBAC9B,IAAI,CAAC;wBACH,IAAI,MAAM,aAAa,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,WAAW,CAAC,EAAE,CAAC;4BACjD,MAAM,IAAI,CAAC,SAAS,CAAC,GAAG,EAAE,GAAG,CAAC,CAAA;wBAChC,CAAC;oBACH,CAAC;oBAAC,OAAO,KAAK,EAAE,CAAC;wBACf,MAAM,IAAI,cAAc,CACtB,CAAC,GAAG,EAAE,KAAK,CAAC,EACZ,mCAAmC,CACpC,CAAA;oBACH,CAAC;gBACH,CAAC;gBACD,MAAM,GAAG,CAAA;YACX,CAAC,CAAC;iBACD,IAAI,CAAC,KAAK,EAAE,KAAK,EAAE,EAAE;gBACpB,6DAA6D;gBAC7D,MAAM,IAAI,CAAC,SAAS,CAAC,GAAG,EAAE,KAAK,CAAC,CAAA;gBAChC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,CAAA;YACjC,CAAC,CAAC,CAAA;QACN,CAAC,CAAC;aACD,OAAO,CAAC,GAAG,EAAE;YACZ,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,GAAG,CAAC,CAAA;QAC1B,CAAC,CAAC,CAAA;QAEJ,IAAI,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC;YAC1B,kEAAkE;YAClE,mEAAmE;YACnE,sEAAsE;YACtE,6CAA6C;YAC7C,MAAM,IAAI,KAAK,CAAC,qCAAqC,CAAC,CAAA;QACxD,CAAC;QAED,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,EAAE,oBAAoB,CAAC,CAAA;QAE3C,MAAM,EAAE,KAAK,EAAE,GAAG,MAAM,oBAAoB,CAAA;QAC5C,OAAO,KAAK,CAAA;IACd,CAAC;IAED,KAAK,CAAC,SAAS,CAAC,GAAM,EAAE,OAAoB;QAC1C,IAAI,CAAC;YACH,OAAO,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,OAAO,CAAC,CAAA;QAC3C,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,SAAS,CAAA;QAClB,CAAC;IACH,CAAC;IAED,KAAK,CAAC,SAAS,CAAC,GAAM,EAAE,KAAQ;QAC9B,IAAI,CAAC;YACH,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAA;QAClC,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,YAAY,GAAG,IAAI,CAAC,OAAO,EAAE,YAAY,CAAA;YAC/C,MAAM,YAAY,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,KAAK,CAAC,CAAA;QACvC,CAAC;IACH,CAAC;IAED,KAAK,CAAC,SAAS,CAAC,GAAM,EAAE,MAAgB;QACtC,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;IAC3B,CAAC;CACF;AA9ID,oCA8IC"}
|
package/dist/index.d.ts
CHANGED
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,oBAAoB,CAAA;AAClC,cAAc,mBAAmB,CAAA"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,oBAAoB,CAAA;AAClC,cAAc,mBAAmB,CAAA;AACjC,cAAc,WAAW,CAAA"}
|
package/dist/index.js
CHANGED
|
@@ -16,4 +16,5 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
17
|
__exportStar(require("./cached-getter.js"), exports);
|
|
18
18
|
__exportStar(require("./simple-store.js"), exports);
|
|
19
|
+
__exportStar(require("./util.js"), exports);
|
|
19
20
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,qDAAkC;AAClC,oDAAiC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,qDAAkC;AAClC,oDAAiC;AACjC,4CAAyB"}
|
package/dist/simple-store.d.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
|
|
2
|
-
export type Key =
|
|
1
|
+
import { Awaitable } from './util.js';
|
|
2
|
+
export type Key = NonNullable<unknown>;
|
|
3
3
|
export type Value = NonNullable<unknown> | null;
|
|
4
4
|
export type GetOptions = {
|
|
5
5
|
signal?: AbortSignal;
|
|
6
6
|
};
|
|
7
|
-
export interface SimpleStore<K extends Key
|
|
7
|
+
export interface SimpleStore<K extends Key, V extends Value> {
|
|
8
8
|
/**
|
|
9
9
|
* @return undefined if the key is not in the store (which is why Value cannot contain "undefined").
|
|
10
10
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"simple-store.d.ts","sourceRoot":"","sources":["../src/simple-store.ts"],"names":[],"mappings":"AAAA,
|
|
1
|
+
{"version":3,"file":"simple-store.d.ts","sourceRoot":"","sources":["../src/simple-store.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAA;AAErC,MAAM,MAAM,GAAG,GAAG,WAAW,CAAC,OAAO,CAAC,CAAA;AACtC,MAAM,MAAM,KAAK,GAAG,WAAW,CAAC,OAAO,CAAC,GAAG,IAAI,CAAA;AAE/C,MAAM,MAAM,UAAU,GAAG;IAAE,MAAM,CAAC,EAAE,WAAW,CAAA;CAAE,CAAA;AAEjD,MAAM,WAAW,WAAW,CAAC,CAAC,SAAS,GAAG,EAAE,CAAC,SAAS,KAAK;IACzD;;OAEG;IACH,GAAG,EAAE,CAAC,GAAG,EAAE,CAAC,EAAE,OAAO,CAAC,EAAE,UAAU,KAAK,SAAS,CAAC,SAAS,GAAG,CAAC,CAAC,CAAA;IAC/D,GAAG,EAAE,CAAC,GAAG,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,KAAK,SAAS,CAAC,IAAI,CAAC,CAAA;IAC1C,GAAG,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,SAAS,CAAC,IAAI,CAAC,CAAA;IAChC,KAAK,CAAC,EAAE,MAAM,SAAS,CAAC,IAAI,CAAC,CAAA;CAC9B"}
|
package/dist/util.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"util.d.ts","sourceRoot":"","sources":["../src/util.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,SAAS,CAAC,CAAC,IAAI,CAAC,GAAG,WAAW,CAAC,CAAC,CAAC,CAAA;AAE7C,MAAM,MAAM,cAAc,CAAC,CAAC,IAAI,CAAC,SAAS,IAAI,GAAG,SAAS,GACtD;IAAE,OAAO,CAAC,EAAE,SAAS,CAAA;CAAE,GACvB;IAAE,OAAO,EAAE,CAAC,CAAA;CAAE,CAAA"}
|
package/dist/util.js
ADDED
package/dist/util.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"util.js","sourceRoot":"","sources":["../src/util.ts"],"names":[],"mappings":""}
|
package/package.json
CHANGED
package/src/cached-getter.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { GetOptions, Key, SimpleStore, Value } from './simple-store.js'
|
|
2
|
+
import { Awaitable, ContextOptions } from './util.js'
|
|
2
3
|
|
|
3
|
-
export type
|
|
4
|
+
export type { GetOptions }
|
|
5
|
+
export type GetCachedOptions<C = void> = ContextOptions<C> & {
|
|
4
6
|
signal?: AbortSignal
|
|
5
7
|
|
|
6
8
|
/**
|
|
@@ -23,13 +25,19 @@ export type GetCachedOptions = {
|
|
|
23
25
|
allowStale?: boolean
|
|
24
26
|
}
|
|
25
27
|
|
|
26
|
-
export type
|
|
28
|
+
export type GetterOptions<C = void> = {
|
|
29
|
+
context: C extends void ? undefined : C
|
|
30
|
+
noCache: boolean
|
|
31
|
+
signal?: AbortSignal
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export type Getter<K extends Key, V extends Value, C = void> = (
|
|
27
35
|
key: K,
|
|
28
|
-
options:
|
|
36
|
+
options: GetterOptions<C>,
|
|
29
37
|
storedValue: undefined | V,
|
|
30
38
|
) => Awaitable<V>
|
|
31
39
|
|
|
32
|
-
export type CachedGetterOptions<K, V> = {
|
|
40
|
+
export type CachedGetterOptions<K extends Key, V extends Value> = {
|
|
33
41
|
isStale?: (key: K, value: V) => boolean | PromiseLike<boolean>
|
|
34
42
|
onStoreError?: (err: unknown, key: K, value: V) => void | PromiseLike<void>
|
|
35
43
|
deleteOnError?: (
|
|
@@ -48,50 +56,76 @@ const returnFalse = () => false
|
|
|
48
56
|
* Wrapper utility that uses a store to speed up the retrieval of values from an
|
|
49
57
|
* (expensive) getter function.
|
|
50
58
|
*/
|
|
51
|
-
export class CachedGetter<
|
|
52
|
-
|
|
59
|
+
export class CachedGetter<
|
|
60
|
+
K extends Key = string,
|
|
61
|
+
V extends Value = Value,
|
|
62
|
+
C = void,
|
|
63
|
+
> {
|
|
64
|
+
private readonly pending = new Map<K, PendingItem<V>>()
|
|
53
65
|
|
|
54
66
|
constructor(
|
|
55
|
-
readonly getter: Getter<K, V>,
|
|
67
|
+
readonly getter: Getter<K, V, C>,
|
|
56
68
|
readonly store: SimpleStore<K, V>,
|
|
57
|
-
readonly options
|
|
69
|
+
readonly options: CachedGetterOptions<K, V> = {},
|
|
58
70
|
) {}
|
|
59
71
|
|
|
60
|
-
async get(
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
72
|
+
async get(
|
|
73
|
+
key: C extends void ? K : never,
|
|
74
|
+
options?: GetCachedOptions<C>,
|
|
75
|
+
): Promise<V>
|
|
76
|
+
async get(
|
|
77
|
+
key: C extends void ? never : K,
|
|
78
|
+
options: GetCachedOptions<C>,
|
|
79
|
+
): Promise<V>
|
|
80
|
+
async get(
|
|
81
|
+
key: K,
|
|
82
|
+
{
|
|
83
|
+
signal,
|
|
84
|
+
context,
|
|
85
|
+
allowStale = false,
|
|
86
|
+
noCache = false,
|
|
87
|
+
} = {} as GetCachedOptions<C>,
|
|
88
|
+
): Promise<V> {
|
|
89
|
+
signal?.throwIfAborted()
|
|
90
|
+
|
|
91
|
+
const { isStale, deleteOnError } = this.options
|
|
92
|
+
|
|
93
|
+
const allowStored: (value: V) => Awaitable<boolean> = noCache
|
|
66
94
|
? returnFalse // Never allow stored values to be returned
|
|
67
|
-
:
|
|
95
|
+
: allowStale || isStale == null
|
|
68
96
|
? returnTrue // Always allow stored values to be returned
|
|
69
97
|
: async (value: V) => !(await isStale(key, value))
|
|
70
98
|
|
|
71
99
|
// As long as concurrent requests are made for the same key, only one
|
|
72
|
-
// request will be made to the
|
|
73
|
-
// because there is no async operation between the while() loop and
|
|
74
|
-
// pending.set() call. Because of the
|
|
100
|
+
// request will be made to the getStored & getter functions at a time. This
|
|
101
|
+
// works because there is no async operation between the while() loop and
|
|
102
|
+
// the pending.set() call below. Because of the single threaded nature of
|
|
75
103
|
// JavaScript, the pending item will be set before the next iteration of the
|
|
76
|
-
// while loop.
|
|
104
|
+
// while loop of any concurrent request.
|
|
77
105
|
let previousExecutionFlow: undefined | PendingItem<V>
|
|
78
106
|
while ((previousExecutionFlow = this.pending.get(key))) {
|
|
79
107
|
try {
|
|
108
|
+
// If a concurrent request is already in progress, wait for it to finish
|
|
80
109
|
const { isFresh, value } = await previousExecutionFlow
|
|
81
110
|
|
|
111
|
+
// Use the concurrent request's result if it is fresh
|
|
82
112
|
if (isFresh) return value
|
|
113
|
+
// Use the concurrent request's result if not fresh (loaded from the
|
|
114
|
+
// store), and matches the conditions for using a stored value.
|
|
83
115
|
if (await allowStored(value)) return value
|
|
84
116
|
} catch {
|
|
85
117
|
// Ignore errors from previous execution flows (they will have been
|
|
86
118
|
// propagated by that flow).
|
|
87
119
|
}
|
|
88
120
|
|
|
89
|
-
|
|
121
|
+
// Break the loop if the signal was aborted
|
|
122
|
+
signal?.throwIfAborted()
|
|
90
123
|
}
|
|
91
124
|
|
|
92
125
|
const currentExecutionFlow: PendingItem<V> = Promise.resolve()
|
|
93
126
|
.then(async () => {
|
|
94
|
-
const storedValue = await this.getStored(key,
|
|
127
|
+
const storedValue = await this.getStored(key, { signal })
|
|
128
|
+
|
|
95
129
|
if (storedValue !== undefined && (await allowStored(storedValue))) {
|
|
96
130
|
// Use the stored value as return value for the current execution
|
|
97
131
|
// flow. Notify other concurrent execution flows (that should be
|
|
@@ -101,11 +135,13 @@ export class CachedGetter<K extends Key = string, V extends Value = Value> {
|
|
|
101
135
|
}
|
|
102
136
|
|
|
103
137
|
return Promise.resolve()
|
|
104
|
-
.then(async () =>
|
|
138
|
+
.then(async () => {
|
|
139
|
+
const options = { signal, noCache, context } as GetterOptions<C>
|
|
140
|
+
return this.getter.call(null, key, options, storedValue)
|
|
141
|
+
})
|
|
105
142
|
.catch(async (err) => {
|
|
106
143
|
if (storedValue !== undefined) {
|
|
107
144
|
try {
|
|
108
|
-
const deleteOnError = this.options?.deleteOnError
|
|
109
145
|
if (await deleteOnError?.(err, key, storedValue)) {
|
|
110
146
|
await this.delStored(key, err)
|
|
111
147
|
}
|
|
@@ -142,11 +178,7 @@ export class CachedGetter<K extends Key = string, V extends Value = Value> {
|
|
|
142
178
|
return value
|
|
143
179
|
}
|
|
144
180
|
|
|
145
|
-
|
|
146
|
-
return async (options) => this.get(key, options)
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
async getStored(key: K, options?: GetCachedOptions): Promise<V | undefined> {
|
|
181
|
+
async getStored(key: K, options?: GetOptions): Promise<V | undefined> {
|
|
150
182
|
try {
|
|
151
183
|
return await this.store.get(key, options)
|
|
152
184
|
} catch (err) {
|
package/src/index.ts
CHANGED
package/src/simple-store.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
|
|
1
|
+
import { Awaitable } from './util.js'
|
|
2
2
|
|
|
3
|
-
export type Key =
|
|
3
|
+
export type Key = NonNullable<unknown>
|
|
4
4
|
export type Value = NonNullable<unknown> | null
|
|
5
5
|
|
|
6
6
|
export type GetOptions = { signal?: AbortSignal }
|
|
7
7
|
|
|
8
|
-
export interface SimpleStore<K extends Key
|
|
8
|
+
export interface SimpleStore<K extends Key, V extends Value> {
|
|
9
9
|
/**
|
|
10
10
|
* @return undefined if the key is not in the store (which is why Value cannot contain "undefined").
|
|
11
11
|
*/
|
package/src/util.ts
ADDED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"root":["./src/cached-getter.ts","./src/index.ts","./src/simple-store.ts"],"version":"5.
|
|
1
|
+
{"root":["./src/cached-getter.ts","./src/index.ts","./src/simple-store.ts","./src/util.ts"],"version":"5.8.2"}
|