@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 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
@@ -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 { Atom, ReadonlyAtom } from '../utils/types.js';
1
+ import type { AnyAtom, ReadonlyAtom } from '../utils/types.js';
2
2
  type CombinedValue<T> = {
3
- [Key in keyof T]: T[Key] extends Atom<infer U> ? U : never;
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]: Atom<T>;
6
+ [key: string]: AnyAtom<T>;
7
7
  }>(atoms: U) => ReadonlyAtom<CombinedValue<U>>;
8
8
  export default combine;
@@ -1,2 +1,6 @@
1
- import type { Atom } from '../utils/types.js';
2
- export default function filter<T>(atom: Atom<T>, predicate: (value: T) => boolean): Atom<T>;
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
  };
@@ -8,6 +8,6 @@ type Params<T> = {
8
8
  declare const createAtomObserver: <T>({ port, atom, event, immediateRegister }: Params<T>) => {
9
9
  register: () => void;
10
10
  unregister: () => void;
11
- start: () => Promise<void>;
11
+ start: () => void;
12
12
  };
13
13
  export default createAtomObserver;
@@ -17,13 +17,16 @@ const createAtomObserver = ({ port, atom, event, immediateRegister = true }) =>
17
17
  unobserve = undefined;
18
18
  emitting = false;
19
19
  };
20
- const start = async () => {
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
  }
@@ -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": "9.0.3",
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": "./lib/index.js",
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 --build tsconfig.build.json",
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.4.2",
46
- "@exodus/storage-memory": "^2.2.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
- "gitHead": "01cb326181e5365ba81bb086f2a7261d180d24bd"
56
+ "publishConfig": {
57
+ "provenance": false
58
+ },
59
+ "gitHead": "b381cc2639d8235acce2377e83ed27ce4dd86e0c"
54
60
  }
@@ -1,4 +0,0 @@
1
- export type ModelLike = Record<string, any> & {
2
- toJSON: () => object;
3
- };
4
- export declare function safeMemoize<T extends (...args: any[]) => any>(fn: T): T;
@@ -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
- }