A music player that connects to your cloud/distributed storage.
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

at v4 180 lines 4.4 kB view raw
1import { 2 endBatch, 3 setActiveSub, 4 signal as alienSignal, 5 startBatch, 6} from "alien-signals"; 7 8export * from "alien-signals"; 9 10/** 11 * @import {Signal, SignalReader, SignalWriter} from "./signal.d.ts" 12 */ 13 14/** 15 * @param {function(): void} fn 16 * 17 * @example Defers effect execution until batch completes 18 * ```js 19 * import { signal, effect, batch } from "~/common/signal.js"; 20 * 21 * const a = signal(0); 22 * const b = signal(0); 23 * const values = [0]; values.length = 0; // typed as number[] 24 * 25 * effect(() => { values.push(a.get() + b.get()); }); 26 * 27 * const before = [...values]; // [0] 28 * batch(() => { a.set(1); b.set(2); }); 29 * 30 * if (before.join(",") !== "0") throw new Error("expected [0] before batch"); 31 * if (values.join(",") !== "0,3") throw new Error("expected exactly one update after batch, got " + values.join(",")); 32 * ``` 33 */ 34export const batch = (fn) => { 35 startBatch(); 36 try { 37 fn(); 38 } finally { 39 endBatch(); 40 } 41}; 42 43/** 44 * @template T 45 * @param {T} initialValue 46 * @param {{ compare?: (a: T, b: T) => boolean }} [options] 47 * @returns {Signal<T>} 48 * 49 * @example get/set and value getter/setter return and update the value 50 * ```js 51 * import { signal } from "~/common/signal.js"; 52 * 53 * const num = signal(42); 54 * if (num.get() !== 42) throw new Error("get should return initial value"); 55 * if (num.value !== 42) throw new Error("value getter should return initial value"); 56 * 57 * num.set(99); 58 * if (num.get() !== 99) throw new Error("get should return updated value"); 59 * 60 * const str = signal("a"); 61 * str.value = "b"; 62 * if (str.value !== "b") throw new Error("value setter should update value"); 63 * ``` 64 * 65 * @example compare option skips update when values are equal by custom comparator 66 * ```js 67 * import { signal, effect } from "~/common/signal.js"; 68 * 69 * let runCount = 0; 70 * const s = signal({ x: 1 }, { compare: (a, b) => a.x === b.x }); 71 * 72 * effect(() => { s.get(); runCount++; }); 73 * 74 * const before = runCount; 75 * s.set({ x: 1 }); // same by compare 76 * if (runCount !== before) throw new Error("effect should not re-run when value is equal by compare"); 77 * 78 * s.set({ x: 2 }); // different by compare 79 * if (runCount !== before + 1) throw new Error("effect should re-run when value differs by compare"); 80 * ``` 81 */ 82export function signal(initialValue, options) { 83 const s = alienSignal(initialValue); 84 if (options?.compare) { 85 const compare = options.compare; 86 87 return _signal({ 88 get: () => s(), 89 set: (b) => { 90 const a = untracked(() => s()); 91 if (!compare(a, b)) s(b); 92 }, 93 }); 94 } 95 96 return _signal({ 97 get: () => s(), 98 set: (v) => s(v), 99 }); 100} 101 102/** 103 * @template T 104 * @param {function(): T} fn 105 * @returns {T} 106 * 107 * @example Reads a signal without tracking it as a dependency 108 * ```js 109 * import { signal, effect, untracked } from "~/common/signal.js"; 110 * 111 * const a = signal(1); 112 * const b = signal(10); 113 * let runCount = 0; 114 * 115 * effect(() => { 116 * a.get(); // tracked 117 * untracked(() => b.get()); // not tracked 118 * runCount++; 119 * }); 120 * 121 * const before = runCount; // 1 122 * b.set(20); // should NOT re-run effect 123 * if (runCount !== before) throw new Error("untracked read should not trigger re-run"); 124 * 125 * a.set(2); // SHOULD re-run effect 126 * if (runCount !== before + 1) throw new Error("tracked read should trigger re-run"); 127 * 128 * if (untracked(() => a.get()) !== 2) throw new Error("untracked should return the value"); 129 * ``` 130 */ 131export const untracked = (fn) => { 132 const sub = setActiveSub(void 0); 133 try { 134 return fn(); 135 } finally { 136 setActiveSub(sub); 137 } 138}; 139 140/** 141 * @template T 142 * @param {function(): Promise<T>} fn 143 * @returns {Promise<T>} 144 * 145 * @example Returns the resolved value from the async callback 146 * ```js 147 * import { untrackedAsync } from "~/common/signal.js"; 148 * 149 * const result = await untrackedAsync(async () => 99); 150 * if (result !== 99) throw new Error("untrackedAsync should return resolved value"); 151 * ``` 152 */ 153export const untrackedAsync = async (fn) => { 154 const sub = setActiveSub(void 0); 155 try { 156 return await fn(); 157 } finally { 158 setActiveSub(sub); 159 } 160}; 161 162/** 163 * @template T 164 * @param {{ get: SignalReader<T>; set: SignalWriter<T> }} _ 165 * @returns {Signal<T>} 166 */ 167function _signal({ get, set }) { 168 return { 169 get, 170 set, 171 172 get value() { 173 return get(); 174 }, 175 176 set value(v) { 177 set(v); 178 }, 179 }; 180}