@praxisjs/core 1.4.1 → 1.6.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.
Files changed (43) hide show
  1. package/CHANGELOG.md +28 -11
  2. package/dist/__tests__/resource.test.js +35 -0
  3. package/dist/__tests__/resource.test.js.map +1 -1
  4. package/dist/__tests__/synced.test.d.ts +2 -0
  5. package/dist/__tests__/synced.test.d.ts.map +1 -0
  6. package/dist/__tests__/synced.test.js +143 -0
  7. package/dist/__tests__/synced.test.js.map +1 -0
  8. package/dist/internal.d.ts +1 -1
  9. package/dist/internal.d.ts.map +1 -1
  10. package/dist/internal.js +1 -1
  11. package/dist/internal.js.map +1 -1
  12. package/dist/signal/batch.d.ts.map +1 -1
  13. package/dist/signal/batch.js +11 -11
  14. package/dist/signal/batch.js.map +1 -1
  15. package/dist/signal/computed.d.ts +1 -1
  16. package/dist/signal/computed.d.ts.map +1 -1
  17. package/dist/signal/computed.js +46 -36
  18. package/dist/signal/computed.js.map +1 -1
  19. package/dist/signal/effect.d.ts.map +1 -1
  20. package/dist/signal/effect.js +11 -12
  21. package/dist/signal/effect.js.map +1 -1
  22. package/dist/signal/index.d.ts +1 -0
  23. package/dist/signal/index.d.ts.map +1 -1
  24. package/dist/signal/index.js +1 -0
  25. package/dist/signal/index.js.map +1 -1
  26. package/dist/signal/signal.d.ts +9 -0
  27. package/dist/signal/signal.d.ts.map +1 -1
  28. package/dist/signal/signal.js +71 -37
  29. package/dist/signal/signal.js.map +1 -1
  30. package/dist/signal/synced.d.ts +6 -0
  31. package/dist/signal/synced.d.ts.map +1 -0
  32. package/dist/signal/synced.js +43 -0
  33. package/dist/signal/synced.js.map +1 -0
  34. package/package.json +1 -1
  35. package/src/__tests__/resource.test.ts +42 -0
  36. package/src/__tests__/synced.test.ts +173 -0
  37. package/src/internal.ts +2 -0
  38. package/src/signal/batch.ts +9 -11
  39. package/src/signal/computed.ts +42 -39
  40. package/src/signal/effect.ts +11 -12
  41. package/src/signal/index.ts +1 -0
  42. package/src/signal/signal.ts +62 -37
  43. package/src/signal/synced.ts +56 -0
@@ -4,14 +4,13 @@ export let activeEffect: Effect | null = null;
4
4
  const effectStack: Effect[] = [];
5
5
 
6
6
  export function track(effect: Effect) {
7
- const subscriber = effect;
8
- effectStack.push(subscriber);
9
- activeEffect = subscriber;
7
+ effectStack.push(effect);
8
+ activeEffect = effect;
10
9
  try {
11
- subscriber();
10
+ effect();
12
11
  } finally {
13
12
  effectStack.pop();
14
- activeEffect = effectStack[effectStack.length - 1] || null;
13
+ activeEffect = effectStack[effectStack.length - 1] ?? null;
15
14
  }
16
15
  }
17
16
 
@@ -36,21 +35,21 @@ export function effect(fn: () => Cleanup) {
36
35
  let cleanup: Cleanup;
37
36
  let stopped = false;
38
37
 
39
- const wrappedEffect = () => {
38
+ function run(): void {
40
39
  if (stopped) return;
41
40
  cleanup?.();
42
- const prevEffect = activeEffect;
43
- activeEffect = wrappedEffect;
41
+ const prev = activeEffect;
42
+ activeEffect = run;
44
43
  try {
45
44
  cleanup = fn();
46
45
  } finally {
47
- activeEffect = prevEffect;
46
+ activeEffect = prev;
48
47
  }
49
- };
48
+ }
50
49
 
51
- wrappedEffect();
50
+ run();
52
51
 
53
- return () => {
52
+ return function stop(): void {
54
53
  stopped = true;
55
54
  cleanup?.();
56
55
  cleanup = undefined;
@@ -3,4 +3,5 @@ export { peek } from "./peek";
3
3
  export { signal } from "./signal";
4
4
  export { computed } from "./computed";
5
5
  export { type PersistedSignalOptions, persistedSignal } from "./persisted";
6
+ export { type SyncedSignal, syncedSignal } from "./synced";
6
7
  export { effect, untrack } from "./effect";
@@ -3,53 +3,78 @@ import type { Signal } from "@praxisjs/shared";
3
3
  import { isBatching, enqueueEffect } from "./batch";
4
4
  import { activeEffect, type Effect } from "./effect";
5
5
 
6
+ export type SubList = Effect | Effect[] | null;
7
+
8
+ export function addSub(holder: { subs: SubList }, eff: Effect): void {
9
+ const subs = holder.subs;
10
+ if (subs === null) {
11
+ holder.subs = eff;
12
+ } else if (typeof subs === "function") {
13
+ if (subs !== eff) holder.subs = [subs, eff];
14
+ } else {
15
+ if (!subs.includes(eff)) subs.push(eff);
16
+ }
17
+ }
18
+
19
+ export function removeSub(holder: { subs: SubList }, eff: Effect): void {
20
+ const subs = holder.subs;
21
+ if (subs === null) return;
22
+ if (typeof subs === "function") {
23
+ if (subs === eff) holder.subs = null;
24
+ } else {
25
+ const idx = subs.indexOf(eff);
26
+ if (idx >= 0) subs.splice(idx, 1);
27
+ }
28
+ }
29
+
30
+ export function notifySubs(subs: Exclude<SubList, null>, batching: boolean): void {
31
+ if (typeof subs === "function") {
32
+ if (batching) enqueueEffect(subs); else subs();
33
+ return;
34
+ }
35
+ if (batching) {
36
+ for (const sub of subs) enqueueEffect(sub);
37
+ return;
38
+ }
39
+ let lastError: unknown;
40
+ let hasError = false;
41
+ for (const sub of subs) {
42
+ try {
43
+ sub();
44
+ } catch (e) {
45
+ lastError = e;
46
+ hasError = true;
47
+ }
48
+ }
49
+ if (hasError) throw lastError;
50
+ }
51
+
6
52
  export function signal<T>(initialValue: T): Signal<T> {
7
53
  let value = initialValue;
8
- const subscribers = new Set<Effect>();
54
+ const holder = { subs: null as SubList };
9
55
 
10
- function read() {
11
- if (activeEffect) {
12
- subscribers.add(activeEffect);
13
- }
56
+ function read(): T {
57
+ if (activeEffect !== null) addSub(holder, activeEffect);
14
58
  return value;
15
59
  }
16
60
 
17
- function set(newValue: T) {
61
+ const s = read as Signal<T>;
62
+
63
+ s.set = (newValue: T) => {
18
64
  if (Object.is(value, newValue)) return;
19
65
  value = newValue;
20
- if (isBatching()) {
21
- [...subscribers].forEach((sub) => { enqueueEffect(sub); });
22
- return;
23
- }
24
- const errors: unknown[] = [];
25
- [...subscribers].forEach((sub) => {
26
- try {
27
- sub();
28
- } catch (e) {
29
- errors.push(e);
30
- }
31
- });
32
- if (errors.length > 0) throw errors[errors.length - 1];
33
- }
66
+ if (holder.subs !== null) notifySubs(holder.subs, isBatching());
67
+ };
34
68
 
35
- function update(fn: (prev: T) => T) {
36
- set(fn(value));
37
- }
69
+ s.update = (fn: (prev: T) => T) => { s.set(fn(value)); };
38
70
 
39
- function subscribe(fn: (value: T) => void) {
40
- const wrapped = () => {
41
- fn(value);
42
- };
43
- subscribers.add(wrapped);
71
+ s.subscribe = (fn: (value: T) => void) => {
72
+ const wrapped: Effect = () => { fn(value); };
73
+ addSub(holder, wrapped);
44
74
  wrapped();
45
- return () => subscribers.delete(wrapped);
46
- }
47
-
48
- const signal = read as Signal<T>;
49
- signal.set = set;
50
- signal.update = update;
51
- signal.subscribe = subscribe;
52
- signal.__isSignal = true;
75
+ return () => { removeSub(holder, wrapped); };
76
+ };
53
77
 
54
- return signal;
78
+ s.__isSignal = true;
79
+ return s;
55
80
  }
@@ -0,0 +1,56 @@
1
+ import type { Signal } from "@praxisjs/shared";
2
+
3
+ import { signal } from "./signal";
4
+
5
+ export interface SyncedSignal<T> extends Signal<T> {
6
+ close(): void;
7
+ }
8
+
9
+ export function syncedSignal<T>(
10
+ channelName: string,
11
+ initialValue: T,
12
+ ): SyncedSignal<T> {
13
+ const inner = signal(initialValue);
14
+ const channel = new BroadcastChannel(channelName);
15
+ let posting = false;
16
+
17
+ channel.onmessage = (event: MessageEvent) => {
18
+ if (posting) return;
19
+ try {
20
+ inner.set(JSON.parse(String(event.data)) as T);
21
+ } catch {
22
+ // ignore malformed messages
23
+ }
24
+ };
25
+
26
+ function read() {
27
+ return inner();
28
+ }
29
+
30
+ function set(value: T) {
31
+ posting = true;
32
+ try {
33
+ inner.set(value);
34
+ channel.postMessage(JSON.stringify(value));
35
+ } finally {
36
+ posting = false;
37
+ }
38
+ }
39
+
40
+ function update(fn: (prev: T) => T) {
41
+ set(fn(inner()));
42
+ }
43
+
44
+ function close() {
45
+ channel.close();
46
+ }
47
+
48
+ const source = read as SyncedSignal<T>;
49
+ source.set = set;
50
+ source.update = update;
51
+ source.subscribe = inner.subscribe.bind(inner);
52
+ source.__isSignal = true;
53
+ source.close = close;
54
+
55
+ return source;
56
+ }