signalium 0.2.3 → 0.2.5

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
@@ -1,5 +1,17 @@
1
1
  # signalium
2
2
 
3
+ ## 0.2.5
4
+
5
+ ### Patch Changes
6
+
7
+ - 4ee723a: Add main entry to package.json
8
+
9
+ ## 0.2.4
10
+
11
+ ### Patch Changes
12
+
13
+ - c2af4d0: Refactor scheduling, add batching for React Native
14
+
3
15
  ## 0.2.3
4
16
 
5
17
  ### Patch Changes
package/dist/config.d.ts CHANGED
@@ -1,3 +1,7 @@
1
1
  export type FlushCallback = () => Promise<void>;
2
- export declare let scheduleWatchers: (flushWatchers: FlushCallback) => void;
3
- export declare let scheduleDisconnects: (flushDisconnects: FlushCallback) => void;
2
+ export type FlushFn = (fn: () => Promise<void>) => void;
3
+ export declare let scheduleFlush: FlushFn;
4
+ export declare const setScheduleFlush: (flushFn: FlushFn) => void;
5
+ export type BatchFn = (fn: () => void) => void;
6
+ export declare let runBatch: BatchFn;
7
+ export declare const setRunBatch: (batchFn: BatchFn) => void;
package/dist/config.js CHANGED
@@ -1,19 +1,16 @@
1
- let currentWatcherFlush = null;
2
- let currentDisconnectFlush = null;
3
- const idleCallback = typeof requestIdleCallback === 'function' ? requestIdleCallback : (cb) => setTimeout(cb, 0);
4
- export let scheduleWatchers = flushWatchers => {
5
- if (currentWatcherFlush !== null)
1
+ let currentFlush = null;
2
+ export let scheduleFlush = flushWatchers => {
3
+ if (currentFlush !== null)
6
4
  return;
7
- currentWatcherFlush = setTimeout(() => {
8
- currentWatcherFlush = null;
5
+ currentFlush = setTimeout(() => {
6
+ currentFlush = null;
9
7
  flushWatchers();
10
8
  }, 0);
11
9
  };
12
- export let scheduleDisconnects = flushDisconnects => {
13
- if (currentDisconnectFlush !== null)
14
- return;
15
- currentDisconnectFlush = idleCallback(() => {
16
- currentDisconnectFlush = null;
17
- flushDisconnects();
18
- });
10
+ export const setScheduleFlush = (flushFn) => {
11
+ scheduleFlush = flushFn;
12
+ };
13
+ export let runBatch = fn => fn();
14
+ export const setRunBatch = (batchFn) => {
15
+ runBatch = batchFn;
19
16
  };
@@ -1,3 +1,5 @@
1
- import type { ComputedSignal } from './signals.js';
1
+ import { ComputedSignal } from './signals.js';
2
2
  export declare const scheduleWatcher: (watcher: ComputedSignal<any>) => void;
3
+ export declare const scheduleDirty: (signal: ComputedSignal<any>) => void;
4
+ export declare const schedulePull: (signal: ComputedSignal<any>) => void;
3
5
  export declare const scheduleDisconnect: (disconnect: ComputedSignal<any>) => void;
@@ -1,36 +1,46 @@
1
- import { scheduleWatchers, scheduleDisconnects } from './config.js';
2
- let PENDING_FLUSH_WATCHERS = null;
3
- const PENDING_WATCHERS = [];
4
- const PENDING_DISCONNECTS = new Map();
1
+ import { scheduleFlush, runBatch } from './config.js';
2
+ let PENDING_DIRTIES = [];
3
+ let PENDING_PULLS = [];
4
+ let PENDING_WATCHERS = [];
5
+ let PENDING_DISCONNECTS = new Map();
6
+ const microtask = () => Promise.resolve();
5
7
  export const scheduleWatcher = (watcher) => {
6
8
  PENDING_WATCHERS.push(watcher);
7
- if (PENDING_FLUSH_WATCHERS === null) {
8
- let resolve;
9
- const promise = new Promise(r => {
10
- resolve = r;
11
- });
12
- PENDING_FLUSH_WATCHERS = { promise, resolve: resolve };
13
- }
14
- scheduleWatchers(flushWatchers);
9
+ scheduleFlush(flushWatchers);
15
10
  };
16
- const flushWatchers = async () => {
17
- PENDING_FLUSH_WATCHERS.resolve();
18
- PENDING_FLUSH_WATCHERS = null;
19
- let watcher;
20
- while ((watcher = PENDING_WATCHERS.shift())) {
21
- watcher._check();
22
- }
23
- PENDING_WATCHERS.length = 0;
11
+ export const scheduleDirty = (signal) => {
12
+ PENDING_DIRTIES.push(signal);
13
+ scheduleFlush(flushWatchers);
14
+ };
15
+ export const schedulePull = (signal) => {
16
+ PENDING_PULLS.push(signal);
17
+ scheduleFlush(flushWatchers);
24
18
  };
25
19
  export const scheduleDisconnect = (disconnect) => {
26
20
  const current = PENDING_DISCONNECTS.get(disconnect) ?? 0;
27
21
  PENDING_DISCONNECTS.set(disconnect, current + 1);
28
- scheduleDisconnects(flushDisconnects);
22
+ scheduleFlush(flushWatchers);
29
23
  };
30
- const flushDisconnects = async () => {
31
- await PENDING_FLUSH_WATCHERS?.promise;
32
- for (const [signal, count] of PENDING_DISCONNECTS) {
33
- signal._disconnect(count);
24
+ const flushWatchers = async () => {
25
+ while (PENDING_DIRTIES.length > 0 || PENDING_PULLS.length > 0) {
26
+ for (const dirty of PENDING_DIRTIES) {
27
+ dirty._dirtyConsumers();
28
+ }
29
+ for (const pull of PENDING_PULLS) {
30
+ pull._check();
31
+ }
32
+ PENDING_DIRTIES = [];
33
+ PENDING_PULLS = [];
34
+ await microtask();
34
35
  }
35
- PENDING_DISCONNECTS.clear();
36
+ runBatch(() => {
37
+ for (const watcher of PENDING_WATCHERS) {
38
+ watcher._check();
39
+ }
40
+ for (const [signal, count] of PENDING_DISCONNECTS) {
41
+ signal._disconnect(count);
42
+ }
43
+ PENDING_WATCHERS = [];
44
+ PENDING_DISCONNECTS.clear();
45
+ });
36
46
  };
package/dist/signals.js CHANGED
@@ -1,4 +1,5 @@
1
- import { scheduleDisconnect, scheduleWatcher } from './scheduling.js';
1
+ import { scheduleDirty, scheduleDisconnect, schedulePull, scheduleWatcher } from './scheduling.js';
2
+ import WeakRef from './weakref.js';
2
3
  let CURRENT_ORD = 0;
3
4
  let CURRENT_CONSUMER;
4
5
  let CURRENT_IS_WAITING = false;
@@ -47,7 +48,7 @@ export class ComputedSignal {
47
48
  const value = this._currentValue;
48
49
  if (value.isPending) {
49
50
  const currentConsumer = CURRENT_CONSUMER;
50
- ACTIVE_ASYNCS.get(this)?.finally(() => currentConsumer._check());
51
+ ACTIVE_ASYNCS.get(this)?.finally(() => schedulePull(currentConsumer));
51
52
  CURRENT_IS_WAITING = true;
52
53
  throw WAITING;
53
54
  }
@@ -198,7 +199,7 @@ export class ComputedSignal {
198
199
  value.isPending = false;
199
200
  value.isSuccess = true;
200
201
  this._version++;
201
- this._dirtyConsumers();
202
+ scheduleDirty(this);
202
203
  }, error => {
203
204
  if (currentVersion !== this._version || error === WAITING) {
204
205
  return;
@@ -207,7 +208,7 @@ export class ComputedSignal {
207
208
  value.isPending = false;
208
209
  value.isError = true;
209
210
  this._version++;
210
- this._dirtyConsumers();
211
+ scheduleDirty(this);
211
212
  });
212
213
  ACTIVE_ASYNCS.set(this, nextValue);
213
214
  value.isPending = true;
@@ -0,0 +1,2 @@
1
+ declare const _default: WeakRefConstructor;
2
+ export default _default;
@@ -0,0 +1,10 @@
1
+ class WeakRefPolyfill {
2
+ value;
3
+ constructor(value) {
4
+ this.value = value;
5
+ }
6
+ deref() {
7
+ return this.value;
8
+ }
9
+ }
10
+ export default typeof WeakRef === 'function' ? WeakRef : WeakRefPolyfill;
package/package.json CHANGED
@@ -1,9 +1,10 @@
1
1
  {
2
2
  "name": "signalium",
3
- "version": "0.2.3",
3
+ "version": "0.2.5",
4
4
  "type": "module",
5
5
  "repository": "https://github.com/pzuraq/signalium",
6
6
  "description": "Chain-reactivity at critical mass",
7
+ "main": "./dist/index.js",
7
8
  "exports": {
8
9
  ".": {
9
10
  "import": {
@@ -243,7 +243,7 @@ describe('Async Signal functionality', () => {
243
243
  resolve: 0,
244
244
  });
245
245
 
246
- await sleep(10);
246
+ await sleep(20);
247
247
 
248
248
  // Check to make sure we don't resolve early after the first task completes
249
249
  expect(compC).toHaveValueAndCounts(result(undefined, 'pending', 'initial'), {
@@ -251,7 +251,7 @@ describe('Async Signal functionality', () => {
251
251
  resolve: 0,
252
252
  });
253
253
 
254
- await sleep(10);
254
+ await sleep(20);
255
255
 
256
256
  expect(compC).toHaveValueAndCounts(result(3, 'success', 'resolved'), {
257
257
  compute: 3,
@@ -285,7 +285,7 @@ describe('Async Signal functionality', () => {
285
285
  resolve: 0,
286
286
  });
287
287
 
288
- await sleep(10);
288
+ await sleep(15);
289
289
 
290
290
  expect(compC).toHaveValueAndCounts(result(undefined, 'error', 'initial', 'error'), {
291
291
  compute: 2,
@@ -322,7 +322,7 @@ describe('Async Signal functionality', () => {
322
322
  resolve: 0,
323
323
  });
324
324
 
325
- await sleep(30);
325
+ await sleep(40);
326
326
 
327
327
  expect(compC).toHaveValueAndCounts(result(undefined, 'error', 'initial', 'error'), {
328
328
  compute: 2,
@@ -368,7 +368,7 @@ describe('Async Signal functionality', () => {
368
368
  await sleep(30);
369
369
 
370
370
  expect(compD).toHaveValueAndCounts(result(3, 'success', 'resolved'), {
371
- compute: 3,
371
+ compute: 2,
372
372
  resolve: 1,
373
373
  });
374
374
  });
package/src/config.ts CHANGED
@@ -1,27 +1,27 @@
1
- let currentWatcherFlush: ReturnType<typeof setTimeout> | null = null;
2
- let currentDisconnectFlush: ReturnType<typeof setTimeout> | ReturnType<typeof requestIdleCallback> | null = null;
1
+ let currentFlush: ReturnType<typeof setTimeout> | null = null;
3
2
 
4
3
  export type FlushCallback = () => Promise<void>;
5
4
 
6
- const idleCallback =
7
- typeof requestIdleCallback === 'function' ? requestIdleCallback : (cb: () => void) => setTimeout(cb, 0);
5
+ export type FlushFn = (fn: () => Promise<void>) => void;
8
6
 
9
- export let scheduleWatchers: (flushWatchers: FlushCallback) => void = flushWatchers => {
10
- if (currentWatcherFlush !== null) return;
7
+ export let scheduleFlush: FlushFn = flushWatchers => {
8
+ if (currentFlush !== null) return;
11
9
 
12
- currentWatcherFlush = setTimeout(() => {
13
- currentWatcherFlush = null;
10
+ currentFlush = setTimeout(() => {
11
+ currentFlush = null;
14
12
 
15
13
  flushWatchers();
16
14
  }, 0);
17
15
  };
18
16
 
19
- export let scheduleDisconnects: (flushDisconnects: FlushCallback) => void = flushDisconnects => {
20
- if (currentDisconnectFlush !== null) return;
17
+ export const setScheduleFlush = (flushFn: FlushFn) => {
18
+ scheduleFlush = flushFn;
19
+ };
20
+
21
+ export type BatchFn = (fn: () => void) => void;
21
22
 
22
- currentDisconnectFlush = idleCallback(() => {
23
- currentDisconnectFlush = null;
23
+ export let runBatch: BatchFn = fn => fn();
24
24
 
25
- flushDisconnects();
26
- });
25
+ export const setRunBatch = (batchFn: BatchFn) => {
26
+ runBatch = batchFn;
27
27
  };
package/src/scheduling.ts CHANGED
@@ -1,39 +1,26 @@
1
- import type { ComputedSignal } from './signals.js';
2
- import { scheduleWatchers, scheduleDisconnects } from './config.js';
1
+ import { ComputedSignal } from './signals.js';
2
+ import { scheduleFlush, runBatch } from './config.js';
3
3
 
4
- let PENDING_FLUSH_WATCHERS: {
5
- promise: Promise<void>;
6
- resolve: () => void;
7
- } | null = null;
4
+ let PENDING_DIRTIES: ComputedSignal<any>[] = [];
5
+ let PENDING_PULLS: ComputedSignal<any>[] = [];
6
+ let PENDING_WATCHERS: ComputedSignal<any>[] = [];
7
+ let PENDING_DISCONNECTS = new Map<ComputedSignal<any>, number>();
8
8
 
9
- const PENDING_WATCHERS: ComputedSignal<any>[] = [];
10
- const PENDING_DISCONNECTS: Map<ComputedSignal<any>, number> = new Map();
9
+ const microtask = () => Promise.resolve();
11
10
 
12
11
  export const scheduleWatcher = (watcher: ComputedSignal<any>) => {
13
12
  PENDING_WATCHERS.push(watcher);
14
-
15
- if (PENDING_FLUSH_WATCHERS === null) {
16
- let resolve: () => void;
17
-
18
- const promise = new Promise<void>(r => {
19
- resolve = r;
20
- });
21
-
22
- PENDING_FLUSH_WATCHERS = { promise, resolve: resolve! };
23
- }
24
-
25
- scheduleWatchers(flushWatchers);
13
+ scheduleFlush(flushWatchers);
26
14
  };
27
15
 
28
- const flushWatchers = async () => {
29
- PENDING_FLUSH_WATCHERS!.resolve();
30
- PENDING_FLUSH_WATCHERS = null;
31
- let watcher;
32
- while ((watcher = PENDING_WATCHERS.shift())) {
33
- watcher._check();
34
- }
16
+ export const scheduleDirty = (signal: ComputedSignal<any>) => {
17
+ PENDING_DIRTIES.push(signal);
18
+ scheduleFlush(flushWatchers);
19
+ };
35
20
 
36
- PENDING_WATCHERS.length = 0;
21
+ export const schedulePull = (signal: ComputedSignal<any>) => {
22
+ PENDING_PULLS.push(signal);
23
+ scheduleFlush(flushWatchers);
37
24
  };
38
25
 
39
26
  export const scheduleDisconnect = (disconnect: ComputedSignal<any>) => {
@@ -41,14 +28,35 @@ export const scheduleDisconnect = (disconnect: ComputedSignal<any>) => {
41
28
 
42
29
  PENDING_DISCONNECTS.set(disconnect, current + 1);
43
30
 
44
- scheduleDisconnects(flushDisconnects);
31
+ scheduleFlush(flushWatchers);
45
32
  };
46
33
 
47
- const flushDisconnects = async () => {
48
- await PENDING_FLUSH_WATCHERS?.promise;
49
- for (const [signal, count] of PENDING_DISCONNECTS) {
50
- signal._disconnect(count);
34
+ const flushWatchers = async () => {
35
+ while (PENDING_DIRTIES.length > 0 || PENDING_PULLS.length > 0) {
36
+ for (const dirty of PENDING_DIRTIES) {
37
+ dirty._dirtyConsumers();
38
+ }
39
+
40
+ for (const pull of PENDING_PULLS) {
41
+ pull._check();
42
+ }
43
+
44
+ PENDING_DIRTIES = [];
45
+ PENDING_PULLS = [];
46
+
47
+ await microtask();
51
48
  }
52
49
 
53
- PENDING_DISCONNECTS.clear();
50
+ runBatch(() => {
51
+ for (const watcher of PENDING_WATCHERS) {
52
+ watcher._check();
53
+ }
54
+
55
+ for (const [signal, count] of PENDING_DISCONNECTS) {
56
+ signal._disconnect(count);
57
+ }
58
+
59
+ PENDING_WATCHERS = [];
60
+ PENDING_DISCONNECTS.clear();
61
+ });
54
62
  };
package/src/signals.ts CHANGED
@@ -1,4 +1,5 @@
1
- import { scheduleDisconnect, scheduleWatcher } from './scheduling.js';
1
+ import { scheduleDirty, scheduleDisconnect, schedulePull, scheduleWatcher } from './scheduling.js';
2
+ import WeakRef from './weakref.js';
2
3
 
3
4
  let CURRENT_ORD = 0;
4
5
  let CURRENT_CONSUMER: ComputedSignal<any> | undefined;
@@ -125,7 +126,7 @@ export class ComputedSignal<T> {
125
126
 
126
127
  if (value.isPending) {
127
128
  const currentConsumer = CURRENT_CONSUMER;
128
- ACTIVE_ASYNCS.get(this)?.finally(() => currentConsumer._check());
129
+ ACTIVE_ASYNCS.get(this)?.finally(() => schedulePull(currentConsumer));
129
130
 
130
131
  CURRENT_IS_WAITING = true;
131
132
  throw WAITING;
@@ -305,7 +306,7 @@ export class ComputedSignal<T> {
305
306
  value.isSuccess = true;
306
307
 
307
308
  this._version++;
308
- this._dirtyConsumers();
309
+ scheduleDirty(this);
309
310
  },
310
311
  error => {
311
312
  if (currentVersion !== this._version || error === WAITING) {
@@ -316,7 +317,7 @@ export class ComputedSignal<T> {
316
317
  value.isPending = false;
317
318
  value.isError = true;
318
319
  this._version++;
319
- this._dirtyConsumers();
320
+ scheduleDirty(this);
320
321
  },
321
322
  );
322
323
 
package/src/weakref.ts ADDED
@@ -0,0 +1,9 @@
1
+ class WeakRefPolyfill<T extends WeakKey> {
2
+ constructor(private value: T) {}
3
+
4
+ deref(): T {
5
+ return this.value;
6
+ }
7
+ }
8
+
9
+ export default typeof WeakRef === 'function' ? WeakRef : (WeakRefPolyfill as unknown as WeakRefConstructor);