synstate 4.0.0 → 4.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/README.md CHANGED
@@ -24,7 +24,7 @@
24
24
  - 🎯 **Simple State Management**: Easy-to-use `createState` and `createReducer` similar to React useState/useReducer for global state
25
25
  - ⚡ **High Performance**: Optimized for fast state updates and minimal re-renders
26
26
  - 🎨 **Type-Safe**: Full TypeScript support with precise type inference
27
- - 🚀 **Lightweight**: <!-- bundle-size:synstate -->~6 kB min+gzip<!-- /bundle-size:synstate --> with two external runtime dependencies ([ts-data-forge](https://www.npmjs.com/package/ts-data-forge) and [ts-std-forge](https://www.npmjs.com/package/ts-std-forge), and the first depends on the second)
27
+ - 🚀 **Lightweight**: <!-- bundle-size:synstate -->~6.1 kB min+gzip<!-- /bundle-size:synstate --> with two external runtime dependencies ([ts-data-forge](https://www.npmjs.com/package/ts-data-forge) and [ts-std-forge](https://www.npmjs.com/package/ts-std-forge), and the first depends on the second)
28
28
  - 🌐 **Framework Agnostic**: Works with React, Vue, Svelte, or vanilla JavaScript
29
29
  - 🔄 **Reactive Updates**: Automatic propagation of state changes to all subscribers
30
30
  - 📡 **Event System**: Built-in `createValueEmitter`, `createEventEmitter` for event-driven architecture
@@ -0,0 +1,32 @@
1
+ import type { RootObservable } from '../types/index.mjs';
2
+ /**
3
+ * Creates an Observable that holds no value and is already completed, so it
4
+ * never passes anything on (almost equivalent to RxJS `EMPTY`). Useful inside
5
+ * `switchMap` for the branch that should follow nothing, where a `source()`
6
+ * would offer a `next` that nothing calls.
7
+ *
8
+ * @template A - The type of the values it would carry
9
+ * @returns An Observable with no value that is already completed
10
+ *
11
+ * @example
12
+ * ```ts
13
+ * const nothing$ = empty<number>();
14
+ *
15
+ * const valueHistory: number[] = [];
16
+ *
17
+ * nothing$.subscribe(
18
+ * (value) => {
19
+ * valueHistory.push(value);
20
+ * },
21
+ * () => {
22
+ * // onComplete — called immediately since empty() completes right away
23
+ * },
24
+ * );
25
+ *
26
+ * assert.deepStrictEqual(valueHistory, []);
27
+ *
28
+ * assert.strictEqual(nothing$.isCompleted, true);
29
+ * ```
30
+ */
31
+ export declare const empty: <A>() => RootObservable<A>;
32
+ //# sourceMappingURL=empty.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"empty.d.mts","sourceRoot":"","sources":["../../../src/core/create/empty.mts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAEzD;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,eAAO,MAAM,KAAK,GAAI,CAAC,OAAM,cAAc,CAAC,CAAC,CAQ5C,CAAC"}
@@ -0,0 +1,39 @@
1
+ import { Optional } from 'ts-data-forge';
2
+ import { RootObservableClass } from '../class/index.mjs';
3
+
4
+
5
+
6
+
7
+
8
+
9
+
10
+
11
+
12
+
13
+
14
+
15
+
16
+
17
+
18
+
19
+
20
+
21
+
22
+
23
+
24
+
25
+
26
+
27
+
28
+
29
+
30
+
31
+
32
+ export const empty = () => {
33
+ const obs = new RootObservableClass({
34
+ initialValue: Optional.none,
35
+ });
36
+ obs.complete();
37
+ return obs;
38
+ };
39
+ //# sourceMappingURL=empty.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"empty.mjs","sourceRoot":"","sources":["../../../src/core/create/empty.mts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAGzD;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,MAAM,CAAC,MAAM,KAAK,GAAG,GAA0B,EAAE;IAC/C,MAAM,GAAG,GAAG,IAAI,mBAAmB,CAAI;QACrC,YAAY,EAAE,QAAQ,CAAC,IAAI;KAC5B,CAAC,CAAC;IAEH,GAAG,CAAC,QAAQ,EAAE,CAAC;IAEf,OAAO,GAAG,CAAC;AACb,CAAC,CAAC"}
@@ -1,4 +1,5 @@
1
1
  export * from './counter.mjs';
2
+ export * from './empty.mjs';
2
3
  export * from './from-abortable-promise.mjs';
3
4
  export * from './from-promise.mjs';
4
5
  export * from './from-subscribable.mjs';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","sourceRoot":"","sources":["../../../src/core/create/index.mts"],"names":[],"mappings":"AAAA,cAAc,eAAe,CAAC;AAC9B,cAAc,8BAA8B,CAAC;AAC7C,cAAc,oBAAoB,CAAC;AACnC,cAAc,yBAAyB,CAAC;AACxC,cAAc,YAAY,CAAC;AAC3B,cAAc,cAAc,CAAC;AAC7B,cAAc,aAAa,CAAC"}
1
+ {"version":3,"file":"index.d.mts","sourceRoot":"","sources":["../../../src/core/create/index.mts"],"names":[],"mappings":"AAAA,cAAc,eAAe,CAAC;AAC9B,cAAc,aAAa,CAAC;AAC5B,cAAc,8BAA8B,CAAC;AAC7C,cAAc,oBAAoB,CAAC;AACnC,cAAc,yBAAyB,CAAC;AACxC,cAAc,YAAY,CAAC;AAC3B,cAAc,cAAc,CAAC;AAC7B,cAAc,aAAa,CAAC"}
@@ -1,4 +1,5 @@
1
1
  export * from './counter.mjs';
2
+ export * from './empty.mjs';
2
3
  export * from './from-abortable-promise.mjs';
3
4
  export * from './from-promise.mjs';
4
5
  export * from './from-subscribable.mjs';
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","sourceRoot":"","sources":["../../../src/core/create/index.mts"],"names":[],"mappings":"AAAA,cAAc,eAAe,CAAC;AAC9B,cAAc,8BAA8B,CAAC;AAC7C,cAAc,oBAAoB,CAAC;AACnC,cAAc,yBAAyB,CAAC;AACxC,cAAc,YAAY,CAAC;AAC3B,cAAc,cAAc,CAAC;AAC7B,cAAc,aAAa,CAAC"}
1
+ {"version":3,"file":"index.mjs","sourceRoot":"","sources":["../../../src/core/create/index.mts"],"names":[],"mappings":"AAAA,cAAc,eAAe,CAAC;AAC9B,cAAc,aAAa,CAAC;AAC5B,cAAc,8BAA8B,CAAC;AAC7C,cAAc,oBAAoB,CAAC;AACnC,cAAc,yBAAyB,CAAC;AACxC,cAAc,YAAY,CAAC;AAC3B,cAAc,cAAc,CAAC;AAC7B,cAAc,aAAa,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "synstate",
3
- "version": "4.0.0",
3
+ "version": "4.1.0",
4
4
  "description": "Type-safe State Management Library for TypeScript/JavaScript",
5
5
  "repository": {
6
6
  "type": "git",
@@ -29,20 +29,20 @@
29
29
  "LICENSE"
30
30
  ],
31
31
  "dependencies": {
32
- "ts-data-forge": "^14.8.0",
33
- "ts-std-forge": "^0.5.1",
32
+ "ts-data-forge": "^14.9.0",
33
+ "ts-std-forge": "^0.5.2",
34
34
  "ts-type-forge": "^9.2.2"
35
35
  },
36
36
  "devDependencies": {
37
37
  "@reduxjs/toolkit": "2.13.0",
38
- "@sumi-lang/cli": "0.0.6",
38
+ "@sumi-lang/cli": "0.0.7",
39
39
  "@types/react": "19.3.0",
40
40
  "@vitest/browser-playwright": "5.0.3",
41
41
  "argparse": "3.0.2",
42
42
  "eslint-config-typed": "5.19.0",
43
- "eslint-plugin-ts-data-forge": "0.9.0",
44
- "eslint-plugin-ts-fortress": "1.3.4",
45
- "eslint-plugin-ts-type-forge": "0.8.9",
43
+ "eslint-plugin-ts-data-forge": "0.10.0",
44
+ "eslint-plugin-ts-fortress": "1.3.5",
45
+ "eslint-plugin-ts-type-forge": "0.8.10",
46
46
  "jotai": "3.0.1",
47
47
  "mobx": "7.0.6",
48
48
  "playwright": "1.64.0",
@@ -51,8 +51,8 @@
51
51
  "prettier-plugin-packagejson": "3.0.2",
52
52
  "react": "19.3.0",
53
53
  "rxjs": "7.8.2",
54
- "synstate-react-hooks": "4.0.0",
55
- "synstate-react-hooks-compat": "4.0.0",
54
+ "synstate-react-hooks": "4.1.0",
55
+ "synstate-react-hooks-compat": "4.1.0",
56
56
  "ts-repo-utils": "10.7.1",
57
57
  "valtio": "2.3.2",
58
58
  "zustand": "5.0.15"
@@ -0,0 +1,42 @@
1
+ import { Optional } from 'ts-data-forge';
2
+ import { RootObservableClass } from '../class/index.mjs';
3
+ import type { RootObservable } from '../types/index.mjs';
4
+
5
+ /**
6
+ * Creates an Observable that holds no value and is already completed, so it
7
+ * never passes anything on (almost equivalent to RxJS `EMPTY`). Useful inside
8
+ * `switchMap` for the branch that should follow nothing, where a `source()`
9
+ * would offer a `next` that nothing calls.
10
+ *
11
+ * @template A - The type of the values it would carry
12
+ * @returns An Observable with no value that is already completed
13
+ *
14
+ * @example
15
+ * ```ts
16
+ * const nothing$ = empty<number>();
17
+ *
18
+ * const valueHistory: number[] = [];
19
+ *
20
+ * nothing$.subscribe(
21
+ * (value) => {
22
+ * valueHistory.push(value);
23
+ * },
24
+ * () => {
25
+ * // onComplete — called immediately since empty() completes right away
26
+ * },
27
+ * );
28
+ *
29
+ * assert.deepStrictEqual(valueHistory, []);
30
+ *
31
+ * assert.strictEqual(nothing$.isCompleted, true);
32
+ * ```
33
+ */
34
+ export const empty = <A,>(): RootObservable<A> => {
35
+ const obs = new RootObservableClass<A>({
36
+ initialValue: Optional.none,
37
+ });
38
+
39
+ obs.complete();
40
+
41
+ return obs;
42
+ };
@@ -0,0 +1,65 @@
1
+ import { Arr, Optional } from 'ts-data-forge';
2
+ import { createState } from '../../utils/index.mjs';
3
+ import { switchMap } from '../operators/index.mjs';
4
+ import { empty } from './empty.mjs';
5
+
6
+ describe(empty, () => {
7
+ test('holds no value and is already completed', () => {
8
+ const nothing$ = empty<number>();
9
+
10
+ assert.isTrue(nothing$.isCompleted);
11
+
12
+ assert.isTrue(Optional.isNone(nothing$.getSnapshot()));
13
+ });
14
+
15
+ test('passes nothing on to a subscriber, and completes it at once', () => {
16
+ const nothing$ = empty<number>();
17
+
18
+ const mut_seen: number[] = [];
19
+
20
+ const mut_completed = { value: false };
21
+
22
+ nothing$.subscribe(
23
+ (value) => {
24
+ mut_seen.push(value);
25
+ },
26
+ () => {
27
+ mut_completed.value = true;
28
+ },
29
+ );
30
+
31
+ assert.deepStrictEqual(mut_seen, []);
32
+
33
+ assert.isTrue(mut_completed.value);
34
+ });
35
+
36
+ test('switched to, it passes nothing on, and switchMap goes on', () => {
37
+ const [on$, setOn] = createState(true);
38
+
39
+ const [count$, setCount] = createState(0);
40
+
41
+ const followed$ = on$.pipe(
42
+ switchMap((isOn) => (isOn ? count$ : empty<number>())),
43
+ );
44
+
45
+ const mut_seen: number[] = [];
46
+
47
+ followed$.subscribe((value) => {
48
+ mut_seen.push(value);
49
+ });
50
+
51
+ setOn(false);
52
+
53
+ const seenBefore = Arr.length(mut_seen);
54
+
55
+ setCount(1);
56
+
57
+ assert.strictEqual(Arr.length(mut_seen), seenBefore);
58
+
59
+ setOn(true);
60
+
61
+ assert.deepStrictEqual(Arr.last(mut_seen), Optional.some(1));
62
+
63
+ assert.isFalse(followed$.isCompleted);
64
+ });
65
+ });
@@ -1,4 +1,5 @@
1
1
  export * from './counter.mjs';
2
+ export * from './empty.mjs';
2
3
  export * from './from-abortable-promise.mjs';
3
4
  export * from './from-promise.mjs';
4
5
  export * from './from-subscribable.mjs';