@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.
- package/CHANGELOG.md +28 -11
- package/dist/__tests__/resource.test.js +35 -0
- package/dist/__tests__/resource.test.js.map +1 -1
- package/dist/__tests__/synced.test.d.ts +2 -0
- package/dist/__tests__/synced.test.d.ts.map +1 -0
- package/dist/__tests__/synced.test.js +143 -0
- package/dist/__tests__/synced.test.js.map +1 -0
- package/dist/internal.d.ts +1 -1
- package/dist/internal.d.ts.map +1 -1
- package/dist/internal.js +1 -1
- package/dist/internal.js.map +1 -1
- package/dist/signal/batch.d.ts.map +1 -1
- package/dist/signal/batch.js +11 -11
- package/dist/signal/batch.js.map +1 -1
- package/dist/signal/computed.d.ts +1 -1
- package/dist/signal/computed.d.ts.map +1 -1
- package/dist/signal/computed.js +46 -36
- package/dist/signal/computed.js.map +1 -1
- package/dist/signal/effect.d.ts.map +1 -1
- package/dist/signal/effect.js +11 -12
- package/dist/signal/effect.js.map +1 -1
- package/dist/signal/index.d.ts +1 -0
- package/dist/signal/index.d.ts.map +1 -1
- package/dist/signal/index.js +1 -0
- package/dist/signal/index.js.map +1 -1
- package/dist/signal/signal.d.ts +9 -0
- package/dist/signal/signal.d.ts.map +1 -1
- package/dist/signal/signal.js +71 -37
- package/dist/signal/signal.js.map +1 -1
- package/dist/signal/synced.d.ts +6 -0
- package/dist/signal/synced.d.ts.map +1 -0
- package/dist/signal/synced.js +43 -0
- package/dist/signal/synced.js.map +1 -0
- package/package.json +1 -1
- package/src/__tests__/resource.test.ts +42 -0
- package/src/__tests__/synced.test.ts +173 -0
- package/src/internal.ts +2 -0
- package/src/signal/batch.ts +9 -11
- package/src/signal/computed.ts +42 -39
- package/src/signal/effect.ts +11 -12
- package/src/signal/index.ts +1 -0
- package/src/signal/signal.ts +62 -37
- package/src/signal/synced.ts +56 -0
package/src/signal/effect.ts
CHANGED
|
@@ -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
|
-
|
|
8
|
-
|
|
9
|
-
activeEffect = subscriber;
|
|
7
|
+
effectStack.push(effect);
|
|
8
|
+
activeEffect = effect;
|
|
10
9
|
try {
|
|
11
|
-
|
|
10
|
+
effect();
|
|
12
11
|
} finally {
|
|
13
12
|
effectStack.pop();
|
|
14
|
-
activeEffect = effectStack[effectStack.length - 1]
|
|
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
|
-
|
|
38
|
+
function run(): void {
|
|
40
39
|
if (stopped) return;
|
|
41
40
|
cleanup?.();
|
|
42
|
-
const
|
|
43
|
-
activeEffect =
|
|
41
|
+
const prev = activeEffect;
|
|
42
|
+
activeEffect = run;
|
|
44
43
|
try {
|
|
45
44
|
cleanup = fn();
|
|
46
45
|
} finally {
|
|
47
|
-
activeEffect =
|
|
46
|
+
activeEffect = prev;
|
|
48
47
|
}
|
|
49
|
-
}
|
|
48
|
+
}
|
|
50
49
|
|
|
51
|
-
|
|
50
|
+
run();
|
|
52
51
|
|
|
53
|
-
return ()
|
|
52
|
+
return function stop(): void {
|
|
54
53
|
stopped = true;
|
|
55
54
|
cleanup?.();
|
|
56
55
|
cleanup = undefined;
|
package/src/signal/index.ts
CHANGED
|
@@ -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";
|
package/src/signal/signal.ts
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
36
|
-
set(fn(value));
|
|
37
|
-
}
|
|
69
|
+
s.update = (fn: (prev: T) => T) => { s.set(fn(value)); };
|
|
38
70
|
|
|
39
|
-
|
|
40
|
-
const wrapped = () => {
|
|
41
|
-
|
|
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 () =>
|
|
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
|
-
|
|
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
|
+
}
|