@exodus/atoms 9.0.2 → 10.0.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.0.0](https://github.com/ExodusMovement/exodus-hydra/compare/@exodus/atoms@9.0.3...@exodus/atoms@10.0.0) (2025-10-08)
7
+
8
+ ### ⚠ BREAKING CHANGES
9
+
10
+ - make `observer.start` a synchronous function (#12186)
11
+
12
+ ### Features
13
+
14
+ - feat(atom): serialization support async function (#13943)
15
+
16
+ - feat: export createAtomObserver separately in atoms (#12310)
17
+
18
+ - feat!: make `observer.start` a synchronous function (#12186)
19
+
20
+ ## [9.0.3](https://github.com/ExodusMovement/exodus-hydra/compare/@exodus/atoms@9.0.2...@exodus/atoms@9.0.3) (2025-04-30)
21
+
22
+ ### Bug Fixes
23
+
24
+ - fix: disable memoization in compute atom enhancer (#12127)
25
+
6
26
  ## [9.0.2](https://github.com/ExodusMovement/exodus-hydra/compare/@exodus/atoms@9.0.1...@exodus/atoms@9.0.2) (2025-03-19)
7
27
 
8
28
  **Note:** Version bump only for package @exodus/atoms
@@ -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,9 +1,7 @@
1
- import { safeMemoize } from '../utils/memoize.js';
2
1
  const compute = ({ atom, selector }) => {
3
- const memoizedSelector = safeMemoize((value) => selector(value));
4
2
  const get = async () => {
5
3
  const values = await atom.get();
6
- return memoizedSelector(values);
4
+ return selector(values);
7
5
  };
8
6
  const set = async () => {
9
7
  throw new Error('selected atom does not support set');
@@ -12,7 +10,7 @@ const compute = ({ atom, selector }) => {
12
10
  let prev;
13
11
  let called;
14
12
  return atom.observe(async (values) => {
15
- const selected = await memoizedSelector(values);
13
+ const selected = await selector(values);
16
14
  if (called && prev === selected)
17
15
  return;
18
16
  called = true;
@@ -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
  };
@@ -7,7 +7,7 @@ const createAtomMock = (options = {}) => {
7
7
  const emitter = new EventEmitter();
8
8
  const initialized = pDefer();
9
9
  const get = async () => {
10
- if (!('defaultValue' in options)) {
10
+ if (!Object.hasOwn(options, 'defaultValue')) {
11
11
  await initialized.promise;
12
12
  }
13
13
  return latestValue;
@@ -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
  }
package/package.json CHANGED
@@ -1,13 +1,16 @@
1
1
  {
2
2
  "name": "@exodus/atoms",
3
- "version": "9.0.2",
3
+ "version": "10.0.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
15
  "test": "run -T exodus-test --jest --esbuild",
13
16
  "lint": "run -T eslint .",
@@ -28,7 +31,6 @@
28
31
  "url": "https://github.com/ExodusMovement/exodus-hydra/issues?q=is%3Aissue+is%3Aopen+label%3Aatoms"
29
32
  },
30
33
  "dependencies": {
31
- "@exodus/basic-utils": "^3.2.0",
32
34
  "@exodus/storage-interface": "^1.0.0",
33
35
  "delay": "^5.0.0",
34
36
  "eventemitter3": "^4.0.7",
@@ -42,13 +44,16 @@
42
44
  "devDependencies": {
43
45
  "@exodus/atom-tests": "^1.0.0",
44
46
  "@exodus/deferring-storage": "^1.0.2",
45
- "@exodus/storage-encrypted": "^1.4.2",
46
- "@exodus/storage-memory": "^2.2.2",
47
+ "@exodus/storage-encrypted": "^1.5.1",
48
+ "@exodus/storage-memory": "^2.3.0",
47
49
  "@types/jest": "^29.5.11",
48
50
  "@types/json-stringify-safe": "^5.0.3",
49
51
  "@types/lodash": "^4.14.200",
50
52
  "@types/minimalistic-assert": "^1.0.2",
51
53
  "events": "^3.3.0"
52
54
  },
53
- "gitHead": "86e9ba86050f754e564bcba758ccee8f9797c32d"
55
+ "publishConfig": {
56
+ "provenance": false
57
+ },
58
+ "gitHead": "72e1044962bc88cb5e22fb69c56997d11e3fdd20"
54
59
  }
@@ -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
- }