signalium 0.2.0 → 0.2.1

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,11 @@
1
1
  # signalium
2
2
 
3
+ ## 0.2.1
4
+
5
+ ### Patch Changes
6
+
7
+ - e8aa91a: Fix async init values
8
+
3
9
  ## 0.2.0
4
10
 
5
11
  ### Minor Changes
package/dist/signals.d.ts CHANGED
@@ -76,6 +76,7 @@ export interface AsyncPending<T> extends AsyncBaseResult<T> {
76
76
  isReady: false;
77
77
  isError: boolean;
78
78
  isSuccess: boolean;
79
+ didResolve: boolean;
79
80
  }
80
81
  export interface AsyncReady<T> extends AsyncBaseResult<T> {
81
82
  result: T;
@@ -84,6 +85,7 @@ export interface AsyncReady<T> extends AsyncBaseResult<T> {
84
85
  isReady: true;
85
86
  isError: boolean;
86
87
  isSuccess: boolean;
88
+ didResolve: boolean;
87
89
  }
88
90
  export type AsyncResult<T> = AsyncPending<T> | AsyncReady<T>;
89
91
  declare class StateSignal<T> implements StateSignal<T> {
package/dist/signals.js CHANGED
@@ -115,8 +115,39 @@ export class ComputedSignal {
115
115
  this._type = type;
116
116
  this._compute = compute;
117
117
  this._equals = equals ?? ((a, b) => a === b);
118
- this._currentValue = initValue;
119
118
  this._connectedCount = type === 3 /* SignalType.Watcher */ ? 1 : 0;
119
+ this._currentValue =
120
+ type !== 2 /* SignalType.Async */
121
+ ? initValue
122
+ : {
123
+ result: initValue,
124
+ error: undefined,
125
+ isReady: initValue !== undefined,
126
+ isPending: true,
127
+ isError: false,
128
+ isSuccess: false,
129
+ didResolve: false,
130
+ invalidate: () => {
131
+ this._state = 2 /* SignalState.Dirty */;
132
+ this._dirty();
133
+ },
134
+ await: () => {
135
+ if (CURRENT_CONSUMER === undefined || CURRENT_CONSUMER._type !== 2 /* SignalType.Async */) {
136
+ throw new Error('Cannot await an async signal outside of an async signal. If you are using an async function, you must use signal.await() for all async signals _before_ the first language-level `await` keyword statement (e.g. it must be synchronous).');
137
+ }
138
+ const value = this._currentValue;
139
+ if (value.isPending) {
140
+ const currentConsumer = CURRENT_CONSUMER;
141
+ ACTIVE_ASYNCS.get(this)?.finally(() => currentConsumer._check());
142
+ CURRENT_IS_WAITING = true;
143
+ throw WAITING;
144
+ }
145
+ else if (value.isError) {
146
+ throw value.error;
147
+ }
148
+ return value.result;
149
+ },
150
+ };
120
151
  }
121
152
  get() {
122
153
  let prevTracked = false;
@@ -226,34 +257,7 @@ export class ComputedSignal {
226
257
  break;
227
258
  }
228
259
  case 2 /* SignalType.Async */: {
229
- const value = this._currentValue ??
230
- (this._currentValue = {
231
- result: undefined,
232
- error: undefined,
233
- isPending: true,
234
- isReady: false,
235
- isError: false,
236
- isSuccess: false,
237
- invalidate: () => {
238
- this._state = 2 /* SignalState.Dirty */;
239
- this._dirty();
240
- },
241
- await: () => {
242
- if (CURRENT_CONSUMER === undefined || CURRENT_CONSUMER._type !== 2 /* SignalType.Async */) {
243
- throw new Error('Cannot await an async signal outside of an async signal. If you are using an async function, you must use signal.await() for all async signals _before_ the first language-level `await` keyword statement (e.g. it must be synchronous).');
244
- }
245
- if (value.isPending) {
246
- const currentConsumer = CURRENT_CONSUMER;
247
- ACTIVE_ASYNCS.get(this)?.finally(() => currentConsumer._check());
248
- CURRENT_IS_WAITING = true;
249
- throw WAITING;
250
- }
251
- else if (value.isError) {
252
- throw value.error;
253
- }
254
- return value.result;
255
- },
256
- });
260
+ const value = this._currentValue;
257
261
  let nextValue;
258
262
  try {
259
263
  CURRENT_IS_WAITING = false;
@@ -294,6 +298,7 @@ export class ComputedSignal {
294
298
  }
295
299
  value.result = result;
296
300
  value.isReady = true;
301
+ value.didResolve = true;
297
302
  value.isPending = false;
298
303
  value.isSuccess = true;
299
304
  this._version++;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "signalium",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "type": "module",
5
5
  "repository": "https://github.com/pzuraq/signalium",
6
6
  "description": "Chain-reactivity at critical mass",
@@ -8,16 +8,19 @@ const nextTick = () => new Promise(r => setTimeout(r, 0));
8
8
  const result = <T>(
9
9
  value: T | undefined,
10
10
  promiseState: 'pending' | 'error' | 'success',
11
- isReady: boolean,
11
+ readyState: 'initial' | 'ready' | 'resolved',
12
12
  error?: any,
13
13
  ): AsyncResult<T> =>
14
14
  ({
15
15
  result: value,
16
16
  error,
17
17
  isPending: promiseState === 'pending',
18
- isReady,
19
18
  isError: promiseState === 'error',
20
19
  isSuccess: promiseState === 'success',
20
+
21
+ isReady: readyState === 'ready' || readyState === 'resolved',
22
+ didResolve: readyState === 'resolved',
23
+
21
24
  await: expect.any(Function),
22
25
  invalidate: expect.any(Function),
23
26
  }) as AsyncResult<T>;
@@ -31,20 +34,20 @@ describe('Async Signal functionality', () => {
31
34
  return a.get() + b.get();
32
35
  });
33
36
 
34
- expect(c).toHaveValueAndCounts(result(undefined, 'pending', false), {
37
+ expect(c).toHaveValueAndCounts(result(undefined, 'pending', 'initial'), {
35
38
  compute: 1,
36
39
  resolve: 0,
37
40
  });
38
41
 
39
42
  await nextTick();
40
43
 
41
- expect(c).toHaveValueAndCounts(result(3, 'success', true), {
44
+ expect(c).toHaveValueAndCounts(result(3, 'success', 'resolved'), {
42
45
  compute: 1,
43
46
  resolve: 1,
44
47
  });
45
48
 
46
49
  // stability
47
- expect(c).toHaveValueAndCounts(result(3, 'success', true), {
50
+ expect(c).toHaveValueAndCounts(result(3, 'success', 'resolved'), {
48
51
  compute: 1,
49
52
  resolve: 1,
50
53
  });
@@ -58,28 +61,28 @@ describe('Async Signal functionality', () => {
58
61
  return a.get() + b.get();
59
62
  });
60
63
 
61
- expect(c).toHaveValueAndCounts(result(undefined, 'pending', false), {
64
+ expect(c).toHaveValueAndCounts(result(undefined, 'pending', 'initial'), {
62
65
  compute: 1,
63
66
  resolve: 0,
64
67
  });
65
68
 
66
69
  await nextTick();
67
70
 
68
- expect(c).toHaveValueAndCounts(result(3, 'success', true), {
71
+ expect(c).toHaveValueAndCounts(result(3, 'success', 'resolved'), {
69
72
  compute: 1,
70
73
  resolve: 1,
71
74
  });
72
75
 
73
76
  a.set(2);
74
77
 
75
- expect(c).toHaveValueAndCounts(result(3, 'pending', true), {
78
+ expect(c).toHaveValueAndCounts(result(3, 'pending', 'resolved'), {
76
79
  compute: 2,
77
80
  resolve: 1,
78
81
  });
79
82
 
80
83
  await nextTick();
81
84
 
82
- expect(c).toHaveValueAndCounts(result(4, 'success', true), {
85
+ expect(c).toHaveValueAndCounts(result(4, 'success', 'resolved'), {
83
86
  compute: 2,
84
87
  resolve: 2,
85
88
  });
@@ -93,28 +96,28 @@ describe('Async Signal functionality', () => {
93
96
  return a.get() + b.get();
94
97
  });
95
98
 
96
- expect(c).toHaveValueAndCounts(result(undefined, 'pending', false), {
99
+ expect(c).toHaveValueAndCounts(result(undefined, 'pending', 'initial'), {
97
100
  compute: 1,
98
101
  resolve: 0,
99
102
  });
100
103
 
101
104
  await nextTick();
102
105
 
103
- expect(c).toHaveValueAndCounts(result(3, 'success', true), {
106
+ expect(c).toHaveValueAndCounts(result(3, 'success', 'resolved'), {
104
107
  compute: 1,
105
108
  resolve: 1,
106
109
  });
107
110
 
108
111
  a.set(1);
109
112
 
110
- expect(c).toHaveValueAndCounts(result(3, 'success', true), {
113
+ expect(c).toHaveValueAndCounts(result(3, 'success', 'resolved'), {
111
114
  compute: 1,
112
115
  resolve: 1,
113
116
  });
114
117
 
115
118
  await nextTick();
116
119
 
117
- expect(c).toHaveValueAndCounts(result(3, 'success', true), {
120
+ expect(c).toHaveValueAndCounts(result(3, 'success', 'resolved'), {
118
121
  compute: 1,
119
122
  resolve: 1,
120
123
  });
@@ -134,40 +137,77 @@ describe('Async Signal functionality', () => {
134
137
  return result;
135
138
  });
136
139
 
137
- expect(c).toHaveValueAndCounts(result(undefined, 'pending', false), {
140
+ expect(c).toHaveValueAndCounts(result(undefined, 'pending', 'initial'), {
138
141
  compute: 1,
139
142
  resolve: 0,
140
143
  });
141
144
 
142
145
  await nextTick();
143
146
 
144
- expect(c).toHaveValueAndCounts(result(3, 'success', true), {
147
+ expect(c).toHaveValueAndCounts(result(3, 'success', 'resolved'), {
145
148
  compute: 1,
146
149
  resolve: 1,
147
150
  });
148
151
 
149
152
  a.set(2);
150
153
 
151
- expect(c).toHaveValueAndCounts(result(3, 'pending', true), {
154
+ expect(c).toHaveValueAndCounts(result(3, 'pending', 'resolved'), {
152
155
  compute: 2,
153
156
  resolve: 1,
154
157
  });
155
158
 
156
159
  a.set(3);
157
160
 
158
- expect(c).toHaveValueAndCounts(result(3, 'pending', true), {
161
+ expect(c).toHaveValueAndCounts(result(3, 'pending', 'resolved'), {
159
162
  compute: 3,
160
163
  resolve: 1,
161
164
  });
162
165
 
163
166
  await sleep(200);
164
167
 
165
- expect(c).toHaveValueAndCounts(result(5, 'success', true), {
168
+ expect(c).toHaveValueAndCounts(result(5, 'success', 'resolved'), {
166
169
  compute: 3,
167
170
  resolve: 3,
168
171
  });
169
172
  });
170
173
 
174
+ test('Can have initial value', async () => {
175
+ const a = state(1);
176
+ const b = state(2);
177
+
178
+ const c = asyncComputed(
179
+ async () => {
180
+ const result = a.get() + b.get();
181
+
182
+ await sleep(50);
183
+
184
+ return result;
185
+ },
186
+ {
187
+ initValue: 5,
188
+ },
189
+ );
190
+
191
+ expect(c).toHaveValueAndCounts(result(5, 'pending', 'ready'), {
192
+ compute: 1,
193
+ resolve: 0,
194
+ });
195
+
196
+ await nextTick();
197
+
198
+ expect(c).toHaveValueAndCounts(result(5, 'pending', 'ready'), {
199
+ compute: 1,
200
+ resolve: 0,
201
+ });
202
+
203
+ await sleep(60);
204
+
205
+ expect(c).toHaveValueAndCounts(result(3, 'success', 'resolved'), {
206
+ compute: 1,
207
+ resolve: 1,
208
+ });
209
+ });
210
+
171
211
  describe('Awaiting', () => {
172
212
  test('Awaiting a computed will resolve the value', async () => {
173
213
  const compA = asyncComputed(async () => {
@@ -190,7 +230,7 @@ describe('Async Signal functionality', () => {
190
230
  });
191
231
 
192
232
  // Pull once to start the computation, trigger the computation
193
- expect(compC).toHaveValueAndCounts(result(undefined, 'pending', false), {
233
+ expect(compC).toHaveValueAndCounts(result(undefined, 'pending', 'initial'), {
194
234
  compute: 1,
195
235
  resolve: 0,
196
236
  });
@@ -198,7 +238,7 @@ describe('Async Signal functionality', () => {
198
238
  await nextTick();
199
239
 
200
240
  // Check after a tick to make sure we didn't resolve early
201
- expect(compC).toHaveValueAndCounts(result(undefined, 'pending', false), {
241
+ expect(compC).toHaveValueAndCounts(result(undefined, 'pending', 'initial'), {
202
242
  compute: 1,
203
243
  resolve: 0,
204
244
  });
@@ -206,14 +246,14 @@ describe('Async Signal functionality', () => {
206
246
  await sleep(30);
207
247
 
208
248
  // Check to make sure we don't resolve early after the first task completes
209
- expect(compC).toHaveValueAndCounts(result(undefined, 'pending', false), {
249
+ expect(compC).toHaveValueAndCounts(result(undefined, 'pending', 'initial'), {
210
250
  compute: 2,
211
251
  resolve: 0,
212
252
  });
213
253
 
214
254
  await sleep(30);
215
255
 
216
- expect(compC).toHaveValueAndCounts(result(3, 'success', true), {
256
+ expect(compC).toHaveValueAndCounts(result(3, 'success', 'resolved'), {
217
257
  compute: 3,
218
258
  resolve: 1,
219
259
  });
@@ -240,14 +280,14 @@ describe('Async Signal functionality', () => {
240
280
  });
241
281
 
242
282
  // Pull once to start the computation, trigger the computation
243
- expect(compC).toHaveValueAndCounts(result(undefined, 'pending', false), {
283
+ expect(compC).toHaveValueAndCounts(result(undefined, 'pending', 'initial'), {
244
284
  compute: 1,
245
285
  resolve: 0,
246
286
  });
247
287
 
248
288
  await sleep(50);
249
289
 
250
- expect(compC).toHaveValueAndCounts(result(undefined, 'error', false, 'error'), {
290
+ expect(compC).toHaveValueAndCounts(result(undefined, 'error', 'initial', 'error'), {
251
291
  compute: 2,
252
292
  resolve: 0,
253
293
  });
package/src/signals.ts CHANGED
@@ -208,8 +208,48 @@ export class ComputedSignal<T> {
208
208
  this._type = type;
209
209
  this._compute = compute;
210
210
  this._equals = equals ?? ((a, b) => a === b);
211
- this._currentValue = initValue;
212
211
  this._connectedCount = type === SignalType.Watcher ? 1 : 0;
212
+
213
+ this._currentValue =
214
+ type !== SignalType.Async
215
+ ? initValue
216
+ : ({
217
+ result: initValue,
218
+ error: undefined,
219
+ isReady: initValue !== undefined,
220
+
221
+ isPending: true,
222
+ isError: false,
223
+ isSuccess: false,
224
+ didResolve: false,
225
+
226
+ invalidate: () => {
227
+ this._state = SignalState.Dirty;
228
+ this._dirty();
229
+ },
230
+
231
+ await: () => {
232
+ if (CURRENT_CONSUMER === undefined || CURRENT_CONSUMER._type !== SignalType.Async) {
233
+ throw new Error(
234
+ 'Cannot await an async signal outside of an async signal. If you are using an async function, you must use signal.await() for all async signals _before_ the first language-level `await` keyword statement (e.g. it must be synchronous).',
235
+ );
236
+ }
237
+
238
+ const value = this._currentValue as AsyncResult<T>;
239
+
240
+ if (value.isPending) {
241
+ const currentConsumer = CURRENT_CONSUMER;
242
+ ACTIVE_ASYNCS.get(this)?.finally(() => currentConsumer._check());
243
+
244
+ CURRENT_IS_WAITING = true;
245
+ throw WAITING;
246
+ } else if (value.isError) {
247
+ throw value.error;
248
+ }
249
+
250
+ return value.result as T;
251
+ },
252
+ } as AsyncResult<T>);
213
253
  }
214
254
 
215
255
  get(): T | AsyncResult<T> {
@@ -350,41 +390,7 @@ export class ComputedSignal<T> {
350
390
  }
351
391
 
352
392
  case SignalType.Async: {
353
- const value: AsyncResult<T> =
354
- (this._currentValue as AsyncResult<T>) ??
355
- (this._currentValue = {
356
- result: undefined,
357
- error: undefined,
358
- isPending: true,
359
- isReady: false,
360
- isError: false,
361
- isSuccess: false,
362
-
363
- invalidate: () => {
364
- this._state = SignalState.Dirty;
365
- this._dirty();
366
- },
367
-
368
- await: () => {
369
- if (CURRENT_CONSUMER === undefined || CURRENT_CONSUMER._type !== SignalType.Async) {
370
- throw new Error(
371
- 'Cannot await an async signal outside of an async signal. If you are using an async function, you must use signal.await() for all async signals _before_ the first language-level `await` keyword statement (e.g. it must be synchronous).',
372
- );
373
- }
374
-
375
- if (value.isPending) {
376
- const currentConsumer = CURRENT_CONSUMER;
377
- ACTIVE_ASYNCS.get(this)?.finally(() => currentConsumer._check());
378
-
379
- CURRENT_IS_WAITING = true;
380
- throw WAITING;
381
- } else if (value.isError) {
382
- throw value.error;
383
- }
384
-
385
- return value.result as T;
386
- },
387
- });
393
+ const value: AsyncResult<T> = this._currentValue as AsyncResult<T>;
388
394
 
389
395
  let nextValue;
390
396
 
@@ -431,6 +437,7 @@ export class ComputedSignal<T> {
431
437
 
432
438
  value.result = result;
433
439
  value.isReady = true;
440
+ value.didResolve = true;
434
441
 
435
442
  value.isPending = false;
436
443
  value.isSuccess = true;
@@ -639,6 +646,7 @@ export interface AsyncPending<T> extends AsyncBaseResult<T> {
639
646
  isReady: false;
640
647
  isError: boolean;
641
648
  isSuccess: boolean;
649
+ didResolve: boolean;
642
650
  }
643
651
 
644
652
  export interface AsyncReady<T> extends AsyncBaseResult<T> {
@@ -648,6 +656,7 @@ export interface AsyncReady<T> extends AsyncBaseResult<T> {
648
656
  isReady: true;
649
657
  isError: boolean;
650
658
  isSuccess: boolean;
659
+ didResolve: boolean;
651
660
  }
652
661
 
653
662
  export type AsyncResult<T> = AsyncPending<T> | AsyncReady<T>;