@exodus/atoms 9.0.3 → 10.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 +20 -0
- package/lib/countdown-lock.js +2 -1
- package/lib/enhancers/combine.d.ts +3 -3
- package/lib/enhancers/filter.d.ts +6 -2
- package/lib/enhancers/with-serialization.d.ts +2 -2
- package/lib/enhancers/with-serialization.js +3 -3
- package/lib/factories/observer.d.ts +1 -1
- package/lib/factories/observer.js +4 -1
- package/lib/utils/types.d.ts +3 -0
- package/package.json +14 -8
- package/lib/utils/memoize.d.ts +0 -4
- package/lib/utils/memoize.js +0 -57
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,26 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
+
## [10.1.0](https://github.com/ExodusMovement/exodus-hydra/compare/@exodus/atoms@10.0.0...@exodus/atoms@10.1.0) (2026-01-29)
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
|
|
10
|
+
- feat: infer result atom in `filter`, accept readonly atoms (#15069)
|
|
11
|
+
|
|
12
|
+
## [10.0.0](https://github.com/ExodusMovement/exodus-hydra/compare/@exodus/atoms@9.0.3...@exodus/atoms@10.0.0) (2025-10-08)
|
|
13
|
+
|
|
14
|
+
### ⚠ BREAKING CHANGES
|
|
15
|
+
|
|
16
|
+
- make `observer.start` a synchronous function (#12186)
|
|
17
|
+
|
|
18
|
+
### Features
|
|
19
|
+
|
|
20
|
+
- feat(atom): serialization support async function (#13943)
|
|
21
|
+
|
|
22
|
+
- feat: export createAtomObserver separately in atoms (#12310)
|
|
23
|
+
|
|
24
|
+
- feat!: make `observer.start` a synchronous function (#12186)
|
|
25
|
+
|
|
6
26
|
## [9.0.3](https://github.com/ExodusMovement/exodus-hydra/compare/@exodus/atoms@9.0.2...@exodus/atoms@9.0.3) (2025-04-30)
|
|
7
27
|
|
|
8
28
|
### Bug Fixes
|
package/lib/countdown-lock.js
CHANGED
|
@@ -3,8 +3,9 @@ const createCountdownLock = (keys) => {
|
|
|
3
3
|
if (!Array.isArray(keys))
|
|
4
4
|
throw new TypeError('lock keys must be an array');
|
|
5
5
|
for (const key of keys) {
|
|
6
|
-
if (typeof key !== 'string')
|
|
6
|
+
if (typeof key !== 'string') {
|
|
7
7
|
throw new TypeError(`lock keys must be all strings. Invalid ${key}`);
|
|
8
|
+
}
|
|
8
9
|
}
|
|
9
10
|
const deferred = pDefer();
|
|
10
11
|
const unlockedKeys = [];
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { AnyAtom, ReadonlyAtom } from '../utils/types.js';
|
|
2
2
|
type CombinedValue<T> = {
|
|
3
|
-
[Key in keyof T]: T[Key] extends
|
|
3
|
+
[Key in keyof T]: T[Key] extends AnyAtom<infer U> ? U : never;
|
|
4
4
|
};
|
|
5
5
|
declare const combine: <T, U extends {
|
|
6
|
-
[key: string]:
|
|
6
|
+
[key: string]: AnyAtom<T>;
|
|
7
7
|
}>(atoms: U) => ReadonlyAtom<CombinedValue<U>>;
|
|
8
8
|
export default combine;
|
|
@@ -1,2 +1,6 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
|
|
1
|
+
import type { AnyAtom, AtomValue, SetAtomValue } from '../utils/types.js';
|
|
2
|
+
type Predicate<A extends AnyAtom, S extends AtomValue<A>> = ((value: AtomValue<A>) => value is S) | ((value: AtomValue<A>) => unknown);
|
|
3
|
+
type FilteredValue<A extends AnyAtom, P extends Predicate<A, any>> = P extends ((value: AtomValue<A>) => value is infer S extends AtomValue<A>) ? S : AtomValue<A>;
|
|
4
|
+
type ResultAtom<A extends AnyAtom, P extends Predicate<A, any>> = SetAtomValue<A, FilteredValue<A, P>>;
|
|
5
|
+
export default function filter<A extends AnyAtom, S extends AtomValue<A>, P extends Predicate<A, S>>(atom: A, predicate: P): ResultAtom<A, P>;
|
|
6
|
+
export {};
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type { Atom } from '../utils/types.js';
|
|
2
2
|
type Params<T, S> = {
|
|
3
3
|
atom: Atom<S>;
|
|
4
|
-
serialize: (value: T) => S
|
|
5
|
-
deserialize: (serialized: S) => T
|
|
4
|
+
serialize: (value: T) => S | Promise<S>;
|
|
5
|
+
deserialize: (serialized: S) => T | Promise<T>;
|
|
6
6
|
};
|
|
7
7
|
declare const withSerialization: <T, S>({ atom, serialize: customSerialize, deserialize: customDeserialize, }: Params<T, S>) => Atom<T>;
|
|
8
8
|
export default withSerialization;
|
|
@@ -8,13 +8,13 @@ const withSerialization = ({ atom, serialize: customSerialize, deserialize: cust
|
|
|
8
8
|
};
|
|
9
9
|
const set = async (value) => {
|
|
10
10
|
if (isSetter(value)) {
|
|
11
|
-
return atom.set(async (previousValue) => serialize(await value(deserialize(previousValue))));
|
|
11
|
+
return atom.set(async (previousValue) => serialize(await value(await deserialize(previousValue))));
|
|
12
12
|
}
|
|
13
|
-
const serialized = serialize(value);
|
|
13
|
+
const serialized = await serialize(value);
|
|
14
14
|
return atom.set(serialized);
|
|
15
15
|
};
|
|
16
16
|
const observe = (callback) => {
|
|
17
|
-
return atom.observe((value) => callback(deserialize(value), value));
|
|
17
|
+
return atom.observe(async (value) => callback(await deserialize(value), value));
|
|
18
18
|
};
|
|
19
19
|
return { ...atom, get, set, observe };
|
|
20
20
|
};
|
|
@@ -17,13 +17,16 @@ const createAtomObserver = ({ port, atom, event, immediateRegister = true }) =>
|
|
|
17
17
|
unobserve = undefined;
|
|
18
18
|
emitting = false;
|
|
19
19
|
};
|
|
20
|
-
const
|
|
20
|
+
const emitWhenReady = async () => {
|
|
21
21
|
emitting = true;
|
|
22
22
|
const startTime = Date.now();
|
|
23
23
|
const value = await atom.get();
|
|
24
24
|
if (startTime > lastMessage)
|
|
25
25
|
port.emit(event, value);
|
|
26
26
|
};
|
|
27
|
+
const start = () => {
|
|
28
|
+
void emitWhenReady();
|
|
29
|
+
};
|
|
27
30
|
if (immediateRegister) {
|
|
28
31
|
register();
|
|
29
32
|
}
|
package/lib/utils/types.d.ts
CHANGED
|
@@ -26,3 +26,6 @@ export interface Keystore {
|
|
|
26
26
|
deleteSecret(key: string, options?: object): Promise<string>;
|
|
27
27
|
}
|
|
28
28
|
export type Logger = Pick<Console, 'trace' | 'debug' | 'error' | 'info' | 'log' | 'warn'>;
|
|
29
|
+
export type AnyAtom<V = any> = Atom<V> | ReadonlyAtom<V>;
|
|
30
|
+
export type AtomValue<A extends AnyAtom> = A extends AnyAtom<infer V> ? V : never;
|
|
31
|
+
export type SetAtomValue<A extends AnyAtom, V> = A extends Atom<any> ? Atom<V> : A extends ReadonlyAtom<any> ? ReadonlyAtom<V> : never;
|
package/package.json
CHANGED
|
@@ -1,15 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@exodus/atoms",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "10.1.0",
|
|
4
4
|
"description": "Abstraction for encapsulating a piece of data behind a simple unified interface: get, set, observe",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
7
|
-
"exports":
|
|
7
|
+
"exports": {
|
|
8
|
+
".": "./lib/index.js",
|
|
9
|
+
"./factories/observer": "./lib/factories/observer.js"
|
|
10
|
+
},
|
|
8
11
|
"author": "Exodus Movement, Inc.",
|
|
9
12
|
"scripts": {
|
|
10
|
-
"build": "run -T tsc
|
|
13
|
+
"build": "run -T tsc -p tsconfig.build.json",
|
|
11
14
|
"clean": "run -T tsc --build --clean",
|
|
12
|
-
"test": "run -T exodus-test --jest --esbuild",
|
|
15
|
+
"test": "run -T exodus-test --jest --esbuild && run test:types",
|
|
16
|
+
"test:types": "tsc -p tsconfig.json --noEmit",
|
|
13
17
|
"lint": "run -T eslint .",
|
|
14
18
|
"lint:fix": "yarn lint --fix",
|
|
15
19
|
"prepublishOnly": "yarn run -T build --scope @exodus/atoms"
|
|
@@ -28,7 +32,6 @@
|
|
|
28
32
|
"url": "https://github.com/ExodusMovement/exodus-hydra/issues?q=is%3Aissue+is%3Aopen+label%3Aatoms"
|
|
29
33
|
},
|
|
30
34
|
"dependencies": {
|
|
31
|
-
"@exodus/basic-utils": "^3.2.0",
|
|
32
35
|
"@exodus/storage-interface": "^1.0.0",
|
|
33
36
|
"delay": "^5.0.0",
|
|
34
37
|
"eventemitter3": "^4.0.7",
|
|
@@ -42,13 +45,16 @@
|
|
|
42
45
|
"devDependencies": {
|
|
43
46
|
"@exodus/atom-tests": "^1.0.0",
|
|
44
47
|
"@exodus/deferring-storage": "^1.0.2",
|
|
45
|
-
"@exodus/storage-encrypted": "^1.
|
|
46
|
-
"@exodus/storage-memory": "^2.
|
|
48
|
+
"@exodus/storage-encrypted": "^1.5.1",
|
|
49
|
+
"@exodus/storage-memory": "^2.3.0",
|
|
47
50
|
"@types/jest": "^29.5.11",
|
|
48
51
|
"@types/json-stringify-safe": "^5.0.3",
|
|
49
52
|
"@types/lodash": "^4.14.200",
|
|
50
53
|
"@types/minimalistic-assert": "^1.0.2",
|
|
51
54
|
"events": "^3.3.0"
|
|
52
55
|
},
|
|
53
|
-
"
|
|
56
|
+
"publishConfig": {
|
|
57
|
+
"provenance": false
|
|
58
|
+
},
|
|
59
|
+
"gitHead": "b381cc2639d8235acce2377e83ed27ce4dd86e0c"
|
|
54
60
|
}
|
package/lib/utils/memoize.d.ts
DELETED
package/lib/utils/memoize.js
DELETED
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
import { memoize } from '@exodus/basic-utils';
|
|
2
|
-
function processValue(value, seen) {
|
|
3
|
-
if (value === null || value === undefined) {
|
|
4
|
-
return value;
|
|
5
|
-
}
|
|
6
|
-
if (typeof value !== 'object') {
|
|
7
|
-
const isSpecialType = ['symbol', 'function', 'bigint'].includes(typeof value);
|
|
8
|
-
return isSpecialType ? value.toString() : value;
|
|
9
|
-
}
|
|
10
|
-
if (seen.has(value)) {
|
|
11
|
-
return '[Circular]';
|
|
12
|
-
}
|
|
13
|
-
seen.add(value);
|
|
14
|
-
if (isModelWithToJSON(value)) {
|
|
15
|
-
return value.toJSON();
|
|
16
|
-
}
|
|
17
|
-
if (value instanceof RegExp) {
|
|
18
|
-
return value.toString();
|
|
19
|
-
}
|
|
20
|
-
if (value instanceof Date) {
|
|
21
|
-
return value.toISOString();
|
|
22
|
-
}
|
|
23
|
-
if (value instanceof Map) {
|
|
24
|
-
return [...value.entries()].map(([k, v]) => [k, processValue(v, seen)]);
|
|
25
|
-
}
|
|
26
|
-
if (value instanceof Set) {
|
|
27
|
-
return [...value].map((v) => processValue(v, seen));
|
|
28
|
-
}
|
|
29
|
-
if (Array.isArray(value)) {
|
|
30
|
-
return value.map((v) => processValue(v, seen));
|
|
31
|
-
}
|
|
32
|
-
const result = {};
|
|
33
|
-
for (const [key, val] of Object.entries(value)) {
|
|
34
|
-
if (val !== undefined) {
|
|
35
|
-
result[key] = processValue(val, seen);
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
return result;
|
|
39
|
-
}
|
|
40
|
-
function safeSerialize(value) {
|
|
41
|
-
try {
|
|
42
|
-
return JSON.stringify(processValue(value, new WeakSet()));
|
|
43
|
-
}
|
|
44
|
-
catch {
|
|
45
|
-
console.error('Could not serialize value:', value);
|
|
46
|
-
throw new Error('Value could not be serialized for memoization');
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
function isModelWithToJSON(value) {
|
|
50
|
-
return (typeof value === 'object' &&
|
|
51
|
-
value !== null &&
|
|
52
|
-
'toJSON' in value &&
|
|
53
|
-
typeof value.toJSON === 'function');
|
|
54
|
-
}
|
|
55
|
-
export function safeMemoize(fn) {
|
|
56
|
-
return memoize(fn, safeSerialize);
|
|
57
|
-
}
|