@atproto-labs/simple-store 0.4.1 → 0.4.3

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 CHANGED
@@ -1,5 +1,21 @@
1
1
  # @atproto-labs/simple-store
2
2
 
3
+ ## 0.4.3
4
+
5
+ ### Patch Changes
6
+
7
+ - [#5099](https://github.com/bluesky-social/atproto/pull/5099) [`b43ec31`](https://github.com/bluesky-social/atproto/commit/b43ec31f247f4461725b01226885f88bd430ca07) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Update TypeScript build to rely on references to composite internal projects
8
+
9
+ - [#5099](https://github.com/bluesky-social/atproto/pull/5099) [`b43ec31`](https://github.com/bluesky-social/atproto/commit/b43ec31f247f4461725b01226885f88bd430ca07) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Bundle only necessary files in the NPM tarball, including the `CHANGELOG.md` and `README.md` files (if present).
10
+
11
+ - [#5099](https://github.com/bluesky-social/atproto/pull/5099) [`b43ec31`](https://github.com/bluesky-social/atproto/commit/b43ec31f247f4461725b01226885f88bd430ca07) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Build with `noImplicitAny` enabled
12
+
13
+ ## 0.4.2
14
+
15
+ ### Patch Changes
16
+
17
+ - [#5151](https://github.com/bluesky-social/atproto/pull/5151) [`a51c45d`](https://github.com/bluesky-social/atproto/commit/a51c45d38f6bd7b8765f640e564cf921d52162e7) Thanks [@matthieusieben](https://github.com/matthieusieben)! - Update dependencies
18
+
3
19
  ## 0.4.1
4
20
 
5
21
  ### Patch Changes
package/package.json CHANGED
@@ -1,9 +1,6 @@
1
1
  {
2
2
  "name": "@atproto-labs/simple-store",
3
- "version": "0.4.1",
4
- "engines": {
5
- "node": ">=22"
6
- },
3
+ "version": "0.4.3",
7
4
  "license": "MIT",
8
5
  "description": "Simple store interfaces & utilities",
9
6
  "keywords": [
@@ -16,6 +13,10 @@
16
13
  "url": "https://github.com/bluesky-social/atproto",
17
14
  "directory": "packages/internal/simple-store"
18
15
  },
16
+ "files": [
17
+ "./dist",
18
+ "./CHANGELOG.md"
19
+ ],
19
20
  "type": "module",
20
21
  "exports": {
21
22
  ".": {
@@ -23,7 +24,9 @@
23
24
  "default": "./dist/index.js"
24
25
  }
25
26
  },
26
- "devDependencies": {},
27
+ "engines": {
28
+ "node": ">=22"
29
+ },
27
30
  "scripts": {
28
31
  "build": "tsgo --build tsconfig.build.json"
29
32
  }
@@ -1,201 +0,0 @@
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> & {
6
- signal?: AbortSignal
7
-
8
- /**
9
- * Do not use the cache to get the value. Always get a new value from the
10
- * getter function.
11
- *
12
- * @default false
13
- */
14
- noCache?: boolean
15
-
16
- /**
17
- * When getting a value from the cache, allow the value to be returned even if
18
- * it is stale.
19
- *
20
- * Has no effect if the `isStale` option was not provided to the CachedGetter.
21
- *
22
- * @default true // If the CachedGetter has an isStale option
23
- * @default false // If no isStale option was provided to the CachedGetter
24
- */
25
- allowStale?: boolean
26
- }
27
-
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> = (
35
- key: K,
36
- options: GetterOptions<C>,
37
- storedValue: undefined | V,
38
- ) => Awaitable<V>
39
-
40
- export type CachedGetterOptions<K extends Key, V extends Value> = {
41
- isStale?: (key: K, value: V) => boolean | PromiseLike<boolean>
42
- onStoreError?: (err: unknown, key: K, value: V) => void | PromiseLike<void>
43
- deleteOnError?: (
44
- err: unknown,
45
- key: K,
46
- value: V,
47
- ) => boolean | PromiseLike<boolean>
48
- }
49
-
50
- type PendingItem<V> = Promise<{ value: V; isFresh: boolean }>
51
-
52
- const returnTrue = () => true
53
- const returnFalse = () => false
54
-
55
- /**
56
- * Wrapper utility that uses a store to speed up the retrieval of values from an
57
- * (expensive) getter function.
58
- */
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>>()
65
-
66
- constructor(
67
- readonly getter: Getter<K, V, C>,
68
- readonly store: SimpleStore<K, V>,
69
- readonly options: CachedGetterOptions<K, V> = {},
70
- ) {}
71
-
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
94
- ? returnFalse // Never allow stored values to be returned
95
- : allowStale || isStale == null
96
- ? returnTrue // Always allow stored values to be returned
97
- : async (value: V) => !(await isStale(key, value))
98
-
99
- // As long as concurrent requests are made for the same key, only one
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
103
- // JavaScript, the pending item will be set before the next iteration of the
104
- // while loop of any concurrent request.
105
- let previousExecutionFlow: undefined | PendingItem<V>
106
- while ((previousExecutionFlow = this.pending.get(key))) {
107
- try {
108
- // If a concurrent request is already in progress, wait for it to finish
109
- const { isFresh, value } = await previousExecutionFlow
110
-
111
- // Use the concurrent request's result if it is fresh
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.
115
- if (await allowStored(value)) return value
116
- } catch {
117
- // Ignore errors from previous execution flows (they will have been
118
- // propagated by that flow).
119
- }
120
-
121
- // Break the loop if the signal was aborted
122
- signal?.throwIfAborted()
123
- }
124
-
125
- const currentExecutionFlow: PendingItem<V> = Promise.resolve()
126
- .then(async () => {
127
- const storedValue = await this.getStored(key, { signal })
128
-
129
- if (storedValue !== undefined && (await allowStored(storedValue))) {
130
- // Use the stored value as return value for the current execution
131
- // flow. Notify other concurrent execution flows (that should be
132
- // "stuck" in the loop before until this promise resolves) that we got
133
- // a value, but that it came from the store (isFresh = false).
134
- return { isFresh: false, value: storedValue }
135
- }
136
-
137
- return Promise.resolve()
138
- .then(async () => {
139
- const options = { signal, noCache, context } as GetterOptions<C>
140
- return this.getter.call(null, key, options, storedValue)
141
- })
142
- .catch(async (err) => {
143
- if (storedValue !== undefined) {
144
- try {
145
- if (await deleteOnError?.(err, key, storedValue)) {
146
- await this.delStored(key, err)
147
- }
148
- } catch (error) {
149
- throw new AggregateError(
150
- [err, error],
151
- 'Error while deleting stored value',
152
- )
153
- }
154
- }
155
- throw err
156
- })
157
- .then(async (value) => {
158
- // The value should be stored even is the signal was aborted.
159
- await this.setStored(key, value)
160
- return { isFresh: true, value }
161
- })
162
- })
163
- .finally(() => {
164
- this.pending.delete(key)
165
- })
166
-
167
- if (this.pending.has(key)) {
168
- // This should never happen. Indeed, there must not be any 'await'
169
- // statement between this and the loop iteration check meaning that
170
- // this.pending.get returned undefined. It is there to catch bugs that
171
- // would occur in future changes to the code.
172
- throw new Error('Concurrent request for the same key')
173
- }
174
-
175
- this.pending.set(key, currentExecutionFlow)
176
-
177
- const { value } = await currentExecutionFlow
178
- return value
179
- }
180
-
181
- async getStored(key: K, options?: GetOptions): Promise<V | undefined> {
182
- try {
183
- return await this.store.get(key, options)
184
- } catch (err) {
185
- return undefined
186
- }
187
- }
188
-
189
- async setStored(key: K, value: V): Promise<void> {
190
- try {
191
- await this.store.set(key, value)
192
- } catch (err) {
193
- const onStoreError = this.options?.onStoreError
194
- await onStoreError?.(err, key, value)
195
- }
196
- }
197
-
198
- async delStored(key: K, _cause?: unknown): Promise<void> {
199
- await this.store.del(key)
200
- }
201
- }
package/src/index.ts DELETED
@@ -1,3 +0,0 @@
1
- export * from './cached-getter.js'
2
- export * from './simple-store.js'
3
- export * from './util.js'
@@ -1,16 +0,0 @@
1
- import { Awaitable } from './util.js'
2
-
3
- export type Key = NonNullable<unknown>
4
- export type Value = NonNullable<unknown> | null
5
-
6
- export type GetOptions = { signal?: AbortSignal }
7
-
8
- export interface SimpleStore<K extends Key, V extends Value> {
9
- /**
10
- * @return undefined if the key is not in the store (which is why Value cannot contain "undefined").
11
- */
12
- get: (key: K, options?: GetOptions) => Awaitable<undefined | V>
13
- set: (key: K, value: V) => Awaitable<void>
14
- del: (key: K) => Awaitable<void>
15
- clear?: () => Awaitable<void>
16
- }
package/src/util.ts DELETED
@@ -1,5 +0,0 @@
1
- export type Awaitable<V> = V | PromiseLike<V>
2
-
3
- export type ContextOptions<C> = C extends void | undefined
4
- ? { context?: undefined }
5
- : { context: C }
@@ -1,8 +0,0 @@
1
- {
2
- "extends": "../../../tsconfig/isomorphic.json",
3
- "compilerOptions": {
4
- "rootDir": "./src",
5
- "outDir": "./dist",
6
- },
7
- "include": ["./src"],
8
- }
@@ -1 +0,0 @@
1
- {"version":"7.0.0-dev.20260614.1","root":["./src/cached-getter.ts","./src/index.ts","./src/simple-store.ts","./src/util.ts"]}
package/tsconfig.json DELETED
@@ -1,4 +0,0 @@
1
- {
2
- "include": [],
3
- "references": [{ "path": "./tsconfig.build.json" }],
4
- }