@atproto-labs/simple-store 0.2.0 → 0.4.0-next.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 +22 -0
- package/LICENSE.txt +1 -1
- package/dist/cached-getter.d.ts +15 -13
- package/dist/cached-getter.d.ts.map +1 -1
- package/dist/cached-getter.js +26 -43
- 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 +3 -18
- 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/simple-store.js +1 -2
- package/dist/simple-store.js.map +1 -1
- package/dist/util.d.ts +7 -0
- package/dist/util.d.ts.map +1 -0
- package/dist/util.js +2 -0
- package/dist/util.js.map +1 -0
- package/package.json +6 -5
- package/src/cached-getter.ts +47 -30
- 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,27 @@
|
|
|
1
1
|
# @atproto-labs/simple-store
|
|
2
2
|
|
|
3
|
+
## 0.4.0-next.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#4929](https://github.com/bluesky-social/atproto/pull/4929) [`bb7491c`](https://github.com/bluesky-social/atproto/commit/bb7491c29e06181e1d2f8cf6eb454f9bb8ab961b) Thanks [@devinivy](https://github.com/devinivy)! - **BREAKING:** Drop support for Node.js 18 and 20. Node.js 22 is now the minimum supported version. Docker images now use Node.js 24.
|
|
8
|
+
|
|
9
|
+
- [#4943](https://github.com/bluesky-social/atproto/pull/4943) [`07ae5d4`](https://github.com/bluesky-social/atproto/commit/07ae5d4452df51e045e0239da7a04cf0bc154028) Thanks [@devinivy](https://github.com/devinivy)! - **BREAKING:** Convert to pure ESM. All packages now ship `"type": "module"` with ES module output and Node16 module resolution.
|
|
10
|
+
|
|
11
|
+
Node.js 22's `require()` compatibility layer can still load these packages in CommonJS code.
|
|
12
|
+
|
|
13
|
+
- [#4930](https://github.com/bluesky-social/atproto/pull/4930) [`042df15`](https://github.com/bluesky-social/atproto/commit/042df15087c0e62cd1e715fcbf58852fab875af9) Thanks [@devinivy](https://github.com/devinivy)! - Build with TypeScript 6.0. Emitted `.d.ts` files now use TypeScript 6's stricter `Uint8Array<ArrayBuffer>` typing in places where Web/Node APIs require buffer-backed (not shared-memory) byte arrays. Consumers compiling against these types on older TypeScript should see no runtime impact, but may need to widen or cast in spots that previously relied on `Uint8Array` defaulting to `<ArrayBufferLike>`.
|
|
14
|
+
|
|
15
|
+
Internal: tsconfig `moduleResolution: "node"` is silenced via `ignoreDeprecations: "6.0"` for now; the proper migration to `node16`/`bundler` resolution is deferred.
|
|
16
|
+
|
|
17
|
+
## 0.3.0
|
|
18
|
+
|
|
19
|
+
### Minor Changes
|
|
20
|
+
|
|
21
|
+
- [#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)
|
|
22
|
+
|
|
23
|
+
- [#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`
|
|
24
|
+
|
|
3
25
|
## 0.2.0
|
|
4
26
|
|
|
5
27
|
### Minor 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-2026 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,6 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
export type
|
|
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> & {
|
|
4
5
|
signal?: AbortSignal;
|
|
5
6
|
/**
|
|
6
7
|
* Do not use the cache to get the value. Always get a new value from the
|
|
@@ -19,12 +20,13 @@ export type GetCachedOptions<C = void> = {
|
|
|
19
20
|
* @default false // If no isStale option was provided to the CachedGetter
|
|
20
21
|
*/
|
|
21
22
|
allowStale?: boolean;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
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>;
|
|
28
30
|
export type CachedGetterOptions<K extends Key, V extends Value> = {
|
|
29
31
|
isStale?: (key: K, value: V) => boolean | PromiseLike<boolean>;
|
|
30
32
|
onStoreError?: (err: unknown, key: K, value: V) => void | PromiseLike<void>;
|
|
@@ -37,12 +39,12 @@ export type CachedGetterOptions<K extends Key, V extends Value> = {
|
|
|
37
39
|
export declare class CachedGetter<K extends Key = string, V extends Value = Value, C = void> {
|
|
38
40
|
readonly getter: Getter<K, V, C>;
|
|
39
41
|
readonly store: SimpleStore<K, V>;
|
|
40
|
-
readonly options
|
|
41
|
-
private pending;
|
|
42
|
-
constructor(getter: Getter<K, V, C>, store: SimpleStore<K, V>, options?: CachedGetterOptions<K, V>
|
|
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>);
|
|
43
45
|
get(key: C extends void ? K : never, options?: GetCachedOptions<C>): Promise<V>;
|
|
44
46
|
get(key: C extends void ? never : K, options: GetCachedOptions<C>): Promise<V>;
|
|
45
|
-
getStored(key: K, options?:
|
|
47
|
+
getStored(key: K, options?: GetOptions): Promise<V | undefined>;
|
|
46
48
|
setStored(key: K, value: V): Promise<void>;
|
|
47
49
|
delStored(key: K, _cause?: unknown): Promise<void>;
|
|
48
50
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cached-getter.d.ts","sourceRoot":"","sources":["../src/cached-getter.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
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
|
@@ -1,59 +1,40 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.CachedGetter = void 0;
|
|
4
1
|
const returnTrue = () => true;
|
|
5
2
|
const returnFalse = () => false;
|
|
6
3
|
/**
|
|
7
4
|
* Wrapper utility that uses a store to speed up the retrieval of values from an
|
|
8
5
|
* (expensive) getter function.
|
|
9
6
|
*/
|
|
10
|
-
class CachedGetter {
|
|
11
|
-
constructor(getter, store, options) {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
value: getter
|
|
17
|
-
});
|
|
18
|
-
Object.defineProperty(this, "store", {
|
|
19
|
-
enumerable: true,
|
|
20
|
-
configurable: true,
|
|
21
|
-
writable: true,
|
|
22
|
-
value: store
|
|
23
|
-
});
|
|
24
|
-
Object.defineProperty(this, "options", {
|
|
25
|
-
enumerable: true,
|
|
26
|
-
configurable: true,
|
|
27
|
-
writable: true,
|
|
28
|
-
value: options
|
|
29
|
-
});
|
|
30
|
-
Object.defineProperty(this, "pending", {
|
|
31
|
-
enumerable: true,
|
|
32
|
-
configurable: true,
|
|
33
|
-
writable: true,
|
|
34
|
-
value: new Map()
|
|
35
|
-
});
|
|
7
|
+
export class CachedGetter {
|
|
8
|
+
constructor(getter, store, options = {}) {
|
|
9
|
+
this.getter = getter;
|
|
10
|
+
this.store = store;
|
|
11
|
+
this.options = options;
|
|
12
|
+
this.pending = new Map();
|
|
36
13
|
}
|
|
37
|
-
async get(key,
|
|
38
|
-
|
|
39
|
-
const isStale = this.options
|
|
40
|
-
const allowStored =
|
|
14
|
+
async get(key, { signal, context, allowStale = false, noCache = false, } = {}) {
|
|
15
|
+
signal?.throwIfAborted();
|
|
16
|
+
const { isStale, deleteOnError } = this.options;
|
|
17
|
+
const allowStored = noCache
|
|
41
18
|
? returnFalse // Never allow stored values to be returned
|
|
42
|
-
:
|
|
19
|
+
: allowStale || isStale == null
|
|
43
20
|
? returnTrue // Always allow stored values to be returned
|
|
44
21
|
: async (value) => !(await isStale(key, value));
|
|
45
22
|
// 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
|
|
23
|
+
// request will be made to the getStored & getter functions at a time. This
|
|
24
|
+
// works because there is no async operation between the while() loop and
|
|
25
|
+
// the pending.set() call below. Because of the single threaded nature of
|
|
49
26
|
// JavaScript, the pending item will be set before the next iteration of the
|
|
50
|
-
// while loop.
|
|
27
|
+
// while loop of any concurrent request.
|
|
51
28
|
let previousExecutionFlow;
|
|
52
29
|
while ((previousExecutionFlow = this.pending.get(key))) {
|
|
53
30
|
try {
|
|
31
|
+
// If a concurrent request is already in progress, wait for it to finish
|
|
54
32
|
const { isFresh, value } = await previousExecutionFlow;
|
|
33
|
+
// Use the concurrent request's result if it is fresh
|
|
55
34
|
if (isFresh)
|
|
56
35
|
return value;
|
|
36
|
+
// Use the concurrent request's result if not fresh (loaded from the
|
|
37
|
+
// store), and matches the conditions for using a stored value.
|
|
57
38
|
if (await allowStored(value))
|
|
58
39
|
return value;
|
|
59
40
|
}
|
|
@@ -61,11 +42,12 @@ class CachedGetter {
|
|
|
61
42
|
// Ignore errors from previous execution flows (they will have been
|
|
62
43
|
// propagated by that flow).
|
|
63
44
|
}
|
|
64
|
-
|
|
45
|
+
// Break the loop if the signal was aborted
|
|
46
|
+
signal?.throwIfAborted();
|
|
65
47
|
}
|
|
66
48
|
const currentExecutionFlow = Promise.resolve()
|
|
67
49
|
.then(async () => {
|
|
68
|
-
const storedValue = await this.getStored(key,
|
|
50
|
+
const storedValue = await this.getStored(key, { signal });
|
|
69
51
|
if (storedValue !== undefined && (await allowStored(storedValue))) {
|
|
70
52
|
// Use the stored value as return value for the current execution
|
|
71
53
|
// flow. Notify other concurrent execution flows (that should be
|
|
@@ -74,11 +56,13 @@ class CachedGetter {
|
|
|
74
56
|
return { isFresh: false, value: storedValue };
|
|
75
57
|
}
|
|
76
58
|
return Promise.resolve()
|
|
77
|
-
.then(async () =>
|
|
59
|
+
.then(async () => {
|
|
60
|
+
const options = { signal, noCache, context };
|
|
61
|
+
return this.getter.call(null, key, options, storedValue);
|
|
62
|
+
})
|
|
78
63
|
.catch(async (err) => {
|
|
79
64
|
if (storedValue !== undefined) {
|
|
80
65
|
try {
|
|
81
|
-
const deleteOnError = this.options?.deleteOnError;
|
|
82
66
|
if (await deleteOnError?.(err, key, storedValue)) {
|
|
83
67
|
await this.delStored(key, err);
|
|
84
68
|
}
|
|
@@ -130,5 +114,4 @@ class CachedGetter {
|
|
|
130
114
|
await this.store.del(key);
|
|
131
115
|
}
|
|
132
116
|
}
|
|
133
|
-
exports.CachedGetter = CachedGetter;
|
|
134
117
|
//# sourceMappingURL=cached-getter.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cached-getter.js","sourceRoot":"","sources":["../src/cached-getter.ts"],"names":[],"mappings":"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"}
|
|
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,MAAM,OAAO,YAAY;IAOvB,YACW,MAAuB,EACvB,KAAwB,EACxB,UAAqC,EAAE;QAFvC,WAAM,GAAN,MAAM,CAAiB;QACvB,UAAK,GAAL,KAAK,CAAmB;QACxB,YAAO,GAAP,OAAO,CAAgC;QALjC,YAAO,GAAG,IAAI,GAAG,EAAqB,CAAA;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","sourcesContent":["import { GetOptions, Key, SimpleStore, Value } from './simple-store.js'\nimport { Awaitable, ContextOptions } from './util.js'\n\nexport type { GetOptions }\nexport type GetCachedOptions<C = void> = ContextOptions<C> & {\n signal?: AbortSignal\n\n /**\n * Do not use the cache to get the value. Always get a new value from the\n * getter function.\n *\n * @default false\n */\n noCache?: boolean\n\n /**\n * When getting a value from the cache, allow the value to be returned even if\n * it is stale.\n *\n * Has no effect if the `isStale` option was not provided to the CachedGetter.\n *\n * @default true // If the CachedGetter has an isStale option\n * @default false // If no isStale option was provided to the CachedGetter\n */\n allowStale?: boolean\n}\n\nexport type GetterOptions<C = void> = {\n context: C extends void ? undefined : C\n noCache: boolean\n signal?: AbortSignal\n}\n\nexport type Getter<K extends Key, V extends Value, C = void> = (\n key: K,\n options: GetterOptions<C>,\n storedValue: undefined | V,\n) => Awaitable<V>\n\nexport type CachedGetterOptions<K extends Key, V extends Value> = {\n isStale?: (key: K, value: V) => boolean | PromiseLike<boolean>\n onStoreError?: (err: unknown, key: K, value: V) => void | PromiseLike<void>\n deleteOnError?: (\n err: unknown,\n key: K,\n value: V,\n ) => boolean | PromiseLike<boolean>\n}\n\ntype PendingItem<V> = Promise<{ value: V; isFresh: boolean }>\n\nconst returnTrue = () => true\nconst returnFalse = () => false\n\n/**\n * Wrapper utility that uses a store to speed up the retrieval of values from an\n * (expensive) getter function.\n */\nexport class CachedGetter<\n K extends Key = string,\n V extends Value = Value,\n C = void,\n> {\n private readonly pending = new Map<K, PendingItem<V>>()\n\n constructor(\n readonly getter: Getter<K, V, C>,\n readonly store: SimpleStore<K, V>,\n readonly options: CachedGetterOptions<K, V> = {},\n ) {}\n\n async get(\n key: C extends void ? K : never,\n options?: GetCachedOptions<C>,\n ): Promise<V>\n async get(\n key: C extends void ? never : K,\n options: GetCachedOptions<C>,\n ): Promise<V>\n async get(\n key: K,\n {\n signal,\n context,\n allowStale = false,\n noCache = false,\n } = {} as GetCachedOptions<C>,\n ): Promise<V> {\n signal?.throwIfAborted()\n\n const { isStale, deleteOnError } = this.options\n\n const allowStored: (value: V) => Awaitable<boolean> = noCache\n ? returnFalse // Never allow stored values to be returned\n : allowStale || isStale == null\n ? returnTrue // Always allow stored values to be returned\n : async (value: V) => !(await isStale(key, value))\n\n // As long as concurrent requests are made for the same key, only one\n // request will be made to the getStored & getter functions at a time. This\n // works because there is no async operation between the while() loop and\n // the pending.set() call below. Because of the single threaded nature of\n // JavaScript, the pending item will be set before the next iteration of the\n // while loop of any concurrent request.\n let previousExecutionFlow: undefined | PendingItem<V>\n while ((previousExecutionFlow = this.pending.get(key))) {\n try {\n // If a concurrent request is already in progress, wait for it to finish\n const { isFresh, value } = await previousExecutionFlow\n\n // Use the concurrent request's result if it is fresh\n if (isFresh) return value\n // Use the concurrent request's result if not fresh (loaded from the\n // store), and matches the conditions for using a stored value.\n if (await allowStored(value)) return value\n } catch {\n // Ignore errors from previous execution flows (they will have been\n // propagated by that flow).\n }\n\n // Break the loop if the signal was aborted\n signal?.throwIfAborted()\n }\n\n const currentExecutionFlow: PendingItem<V> = Promise.resolve()\n .then(async () => {\n const storedValue = await this.getStored(key, { signal })\n\n if (storedValue !== undefined && (await allowStored(storedValue))) {\n // Use the stored value as return value for the current execution\n // flow. Notify other concurrent execution flows (that should be\n // \"stuck\" in the loop before until this promise resolves) that we got\n // a value, but that it came from the store (isFresh = false).\n return { isFresh: false, value: storedValue }\n }\n\n return Promise.resolve()\n .then(async () => {\n const options = { signal, noCache, context } as GetterOptions<C>\n return this.getter.call(null, key, options, storedValue)\n })\n .catch(async (err) => {\n if (storedValue !== undefined) {\n try {\n if (await deleteOnError?.(err, key, storedValue)) {\n await this.delStored(key, err)\n }\n } catch (error) {\n throw new AggregateError(\n [err, error],\n 'Error while deleting stored value',\n )\n }\n }\n throw err\n })\n .then(async (value) => {\n // The value should be stored even is the signal was aborted.\n await this.setStored(key, value)\n return { isFresh: true, value }\n })\n })\n .finally(() => {\n this.pending.delete(key)\n })\n\n if (this.pending.has(key)) {\n // This should never happen. Indeed, there must not be any 'await'\n // statement between this and the loop iteration check meaning that\n // this.pending.get returned undefined. It is there to catch bugs that\n // would occur in future changes to the code.\n throw new Error('Concurrent request for the same key')\n }\n\n this.pending.set(key, currentExecutionFlow)\n\n const { value } = await currentExecutionFlow\n return value\n }\n\n async getStored(key: K, options?: GetOptions): Promise<V | undefined> {\n try {\n return await this.store.get(key, options)\n } catch (err) {\n return undefined\n }\n }\n\n async setStored(key: K, value: V): Promise<void> {\n try {\n await this.store.set(key, value)\n } catch (err) {\n const onStoreError = this.options?.onStoreError\n await onStoreError?.(err, key, value)\n }\n }\n\n async delStored(key: K, _cause?: unknown): Promise<void> {\n await this.store.del(key)\n }\n}\n"]}
|
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
|
@@ -1,19 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
-
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
-
};
|
|
16
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
-
__exportStar(require("./cached-getter.js"), exports);
|
|
18
|
-
__exportStar(require("./simple-store.js"), exports);
|
|
1
|
+
export * from './cached-getter.js';
|
|
2
|
+
export * from './simple-store.js';
|
|
3
|
+
export * from './util.js';
|
|
19
4
|
//# 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":"
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,oBAAoB,CAAA;AAClC,cAAc,mBAAmB,CAAA;AACjC,cAAc,WAAW,CAAA","sourcesContent":["export * from './cached-getter.js'\nexport * from './simple-store.js'\nexport * from './util.js'\n"]}
|
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/simple-store.js
CHANGED
package/dist/simple-store.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"simple-store.js","sourceRoot":"","sources":["../src/simple-store.ts"],"names":[],"mappings":""}
|
|
1
|
+
{"version":3,"file":"simple-store.js","sourceRoot":"","sources":["../src/simple-store.ts"],"names":[],"mappings":"","sourcesContent":["import { Awaitable } from './util.js'\n\nexport type Key = NonNullable<unknown>\nexport type Value = NonNullable<unknown> | null\n\nexport type GetOptions = { signal?: AbortSignal }\n\nexport interface SimpleStore<K extends Key, V extends Value> {\n /**\n * @return undefined if the key is not in the store (which is why Value cannot contain \"undefined\").\n */\n get: (key: K, options?: GetOptions) => Awaitable<undefined | V>\n set: (key: K, value: V) => Awaitable<void>\n del: (key: K) => Awaitable<void>\n clear?: () => Awaitable<void>\n}\n"]}
|
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":"","sourcesContent":["export type Awaitable<V> = V | PromiseLike<V>\n\nexport type ContextOptions<C> = C extends void | undefined\n ? { context?: undefined }\n : { context: C }\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atproto-labs/simple-store",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0-next.0",
|
|
4
|
+
"engines": {
|
|
5
|
+
"node": ">=22"
|
|
6
|
+
},
|
|
4
7
|
"license": "MIT",
|
|
5
8
|
"description": "Simple store interfaces & utilities",
|
|
6
9
|
"keywords": [
|
|
@@ -13,9 +16,7 @@
|
|
|
13
16
|
"url": "https://github.com/bluesky-social/atproto",
|
|
14
17
|
"directory": "packages/internal/simple-store"
|
|
15
18
|
},
|
|
16
|
-
"type": "
|
|
17
|
-
"main": "dist/index.js",
|
|
18
|
-
"types": "dist/index.d.ts",
|
|
19
|
+
"type": "module",
|
|
19
20
|
"exports": {
|
|
20
21
|
".": {
|
|
21
22
|
"types": "./dist/index.d.ts",
|
|
@@ -23,7 +24,7 @@
|
|
|
23
24
|
}
|
|
24
25
|
},
|
|
25
26
|
"devDependencies": {
|
|
26
|
-
"typescript": "^
|
|
27
|
+
"typescript": "^6.0.3"
|
|
27
28
|
},
|
|
28
29
|
"scripts": {
|
|
29
30
|
"build": "tsc --build tsconfig.build.json"
|
package/src/cached-getter.ts
CHANGED
|
@@ -1,13 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
Value,
|
|
7
|
-
} from './simple-store.js'
|
|
8
|
-
|
|
9
|
-
export type { GetStoredOptions }
|
|
10
|
-
export type GetCachedOptions<C = void> = {
|
|
1
|
+
import { GetOptions, Key, SimpleStore, Value } from './simple-store.js'
|
|
2
|
+
import { Awaitable, ContextOptions } from './util.js'
|
|
3
|
+
|
|
4
|
+
export type { GetOptions }
|
|
5
|
+
export type GetCachedOptions<C = void> = ContextOptions<C> & {
|
|
11
6
|
signal?: AbortSignal
|
|
12
7
|
|
|
13
8
|
/**
|
|
@@ -28,11 +23,17 @@ export type GetCachedOptions<C = void> = {
|
|
|
28
23
|
* @default false // If no isStale option was provided to the CachedGetter
|
|
29
24
|
*/
|
|
30
25
|
allowStale?: boolean
|
|
31
|
-
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export type GetterOptions<C = void> = {
|
|
29
|
+
context: C extends void ? undefined : C
|
|
30
|
+
noCache: boolean
|
|
31
|
+
signal?: AbortSignal
|
|
32
|
+
}
|
|
32
33
|
|
|
33
34
|
export type Getter<K extends Key, V extends Value, C = void> = (
|
|
34
35
|
key: K,
|
|
35
|
-
options:
|
|
36
|
+
options: GetterOptions<C>,
|
|
36
37
|
storedValue: undefined | V,
|
|
37
38
|
) => Awaitable<V>
|
|
38
39
|
|
|
@@ -60,12 +61,12 @@ export class CachedGetter<
|
|
|
60
61
|
V extends Value = Value,
|
|
61
62
|
C = void,
|
|
62
63
|
> {
|
|
63
|
-
private pending = new Map<K, PendingItem<V>>()
|
|
64
|
+
private readonly pending = new Map<K, PendingItem<V>>()
|
|
64
65
|
|
|
65
66
|
constructor(
|
|
66
67
|
readonly getter: Getter<K, V, C>,
|
|
67
68
|
readonly store: SimpleStore<K, V>,
|
|
68
|
-
readonly options
|
|
69
|
+
readonly options: CachedGetterOptions<K, V> = {},
|
|
69
70
|
) {}
|
|
70
71
|
|
|
71
72
|
async get(
|
|
@@ -76,41 +77,55 @@ export class CachedGetter<
|
|
|
76
77
|
key: C extends void ? never : K,
|
|
77
78
|
options: GetCachedOptions<C>,
|
|
78
79
|
): Promise<V>
|
|
79
|
-
async get(
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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
|
|
85
94
|
? returnFalse // Never allow stored values to be returned
|
|
86
|
-
:
|
|
95
|
+
: allowStale || isStale == null
|
|
87
96
|
? returnTrue // Always allow stored values to be returned
|
|
88
97
|
: async (value: V) => !(await isStale(key, value))
|
|
89
98
|
|
|
90
99
|
// As long as concurrent requests are made for the same key, only one
|
|
91
|
-
// request will be made to the
|
|
92
|
-
// because there is no async operation between the while() loop and
|
|
93
|
-
// 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
|
|
94
103
|
// JavaScript, the pending item will be set before the next iteration of the
|
|
95
|
-
// while loop.
|
|
104
|
+
// while loop of any concurrent request.
|
|
96
105
|
let previousExecutionFlow: undefined | PendingItem<V>
|
|
97
106
|
while ((previousExecutionFlow = this.pending.get(key))) {
|
|
98
107
|
try {
|
|
108
|
+
// If a concurrent request is already in progress, wait for it to finish
|
|
99
109
|
const { isFresh, value } = await previousExecutionFlow
|
|
100
110
|
|
|
111
|
+
// Use the concurrent request's result if it is fresh
|
|
101
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.
|
|
102
115
|
if (await allowStored(value)) return value
|
|
103
116
|
} catch {
|
|
104
117
|
// Ignore errors from previous execution flows (they will have been
|
|
105
118
|
// propagated by that flow).
|
|
106
119
|
}
|
|
107
120
|
|
|
108
|
-
|
|
121
|
+
// Break the loop if the signal was aborted
|
|
122
|
+
signal?.throwIfAborted()
|
|
109
123
|
}
|
|
110
124
|
|
|
111
125
|
const currentExecutionFlow: PendingItem<V> = Promise.resolve()
|
|
112
126
|
.then(async () => {
|
|
113
|
-
const storedValue = await this.getStored(key,
|
|
127
|
+
const storedValue = await this.getStored(key, { signal })
|
|
128
|
+
|
|
114
129
|
if (storedValue !== undefined && (await allowStored(storedValue))) {
|
|
115
130
|
// Use the stored value as return value for the current execution
|
|
116
131
|
// flow. Notify other concurrent execution flows (that should be
|
|
@@ -120,11 +135,13 @@ export class CachedGetter<
|
|
|
120
135
|
}
|
|
121
136
|
|
|
122
137
|
return Promise.resolve()
|
|
123
|
-
.then(async () =>
|
|
138
|
+
.then(async () => {
|
|
139
|
+
const options = { signal, noCache, context } as GetterOptions<C>
|
|
140
|
+
return this.getter.call(null, key, options, storedValue)
|
|
141
|
+
})
|
|
124
142
|
.catch(async (err) => {
|
|
125
143
|
if (storedValue !== undefined) {
|
|
126
144
|
try {
|
|
127
|
-
const deleteOnError = this.options?.deleteOnError
|
|
128
145
|
if (await deleteOnError?.(err, key, storedValue)) {
|
|
129
146
|
await this.delStored(key, err)
|
|
130
147
|
}
|
|
@@ -161,7 +178,7 @@ export class CachedGetter<
|
|
|
161
178
|
return value
|
|
162
179
|
}
|
|
163
180
|
|
|
164
|
-
async getStored(key: K, options?:
|
|
181
|
+
async getStored(key: K, options?: GetOptions): Promise<V | undefined> {
|
|
165
182
|
try {
|
|
166
183
|
return await this.store.get(key, options)
|
|
167
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":"
|
|
1
|
+
{"root":["./src/cached-getter.ts","./src/index.ts","./src/simple-store.ts","./src/util.ts"],"version":"6.0.3"}
|