leaner 0.0.1 → 0.1.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/dist/core.js CHANGED
@@ -11,6 +11,34 @@ function isPlainObjectOrArray( value ) {
11
11
  return isPlainObject( value ) || Array.isArray( value );
12
12
  }
13
13
 
14
+ let scheduled = null;
15
+
16
+ function schedule( callback ) {
17
+ scheduleWatcher( createWatcher( null, callback, true ) );
18
+ }
19
+
20
+ function scheduleWatcher( watcher ) {
21
+ if ( scheduled == null ) {
22
+ scheduled = new Set();
23
+ Promise.resolve().then( runSchedule );
24
+ }
25
+
26
+ scheduled.add( watcher );
27
+ }
28
+
29
+ function runSchedule() {
30
+ if ( scheduled != null ) {
31
+ const watchers = scheduled.values();
32
+ scheduled = null;
33
+
34
+ for ( const watcher of watchers ) {
35
+ const callback = watcher.callback;
36
+ if ( callback != null )
37
+ callback();
38
+ }
39
+ }
40
+ }
41
+
14
42
  let deps = null;
15
43
 
16
44
  function track( record ) {
@@ -19,9 +47,14 @@ function track( record ) {
19
47
  }
20
48
 
21
49
  function react( record ) {
22
- if ( record.watches != null ) {
23
- for ( const watch of record.watches.values() )
24
- watch();
50
+ for ( const watcher of record.values() ) {
51
+ const callback = watcher.callback;
52
+ if ( callback != null ) {
53
+ if ( watcher.async )
54
+ scheduleWatcher( watcher );
55
+ else
56
+ callback();
57
+ }
25
58
  }
26
59
  }
27
60
 
@@ -45,21 +78,63 @@ function addDeps( newDeps ) {
45
78
  }
46
79
  }
47
80
 
48
- function updateDeps( newDeps, deps, callback ) {
49
- for ( const dep of newDeps ) {
50
- if ( dep.watches == null )
51
- dep.watches = new Set();
52
- dep.watches.add( callback );
53
- if ( deps != null )
54
- deps.delete( dep );
81
+ let current = null;
82
+
83
+ function withScope( scope, callback ) {
84
+ const old = current;
85
+ current = scope;
86
+
87
+ try {
88
+ return callback();
89
+ } finally {
90
+ current = old;
55
91
  }
92
+ }
93
+
94
+ function createWatcher( deps, callback, async ) {
95
+ const watcher = {
96
+ deps,
97
+ callback,
98
+ async,
99
+ };
100
+
101
+ if ( current != null )
102
+ current.push( watcher );
56
103
 
57
104
  if ( deps != null ) {
58
- for ( const dep of deps )
59
- dep.watches.delete( callback );
105
+ for ( const dep of deps ) {
106
+ dep.add( watcher );
107
+ }
60
108
  }
61
109
 
62
- return newDeps;
110
+ return watcher;
111
+ }
112
+
113
+ function updateWatcher( watcher, deps ) {
114
+ for ( const dep of deps ) {
115
+ dep.add( watcher );
116
+ if ( watcher.deps != null )
117
+ watcher.deps.delete( dep );
118
+ }
119
+
120
+ if ( watcher.deps != null ) {
121
+ for ( const dep of watcher.deps )
122
+ dep.delete( watcher );
123
+ }
124
+
125
+ watcher.deps = deps;
126
+ }
127
+
128
+ function destroyScope( scope ) {
129
+ for ( const watcher of scope ) {
130
+ watcher.callback = null;
131
+
132
+ if ( watcher.deps != null ) {
133
+ for ( const dep of watcher.deps )
134
+ dep.delete( watcher );
135
+ watcher.deps = null;
136
+ }
137
+ }
63
138
  }
64
139
 
65
140
  const stateGetterHandler = {
@@ -73,94 +148,76 @@ const stateGetterHandler = {
73
148
  },
74
149
  };
75
150
 
76
- const stateGetterRecords = new WeakMap();
151
+ const stateGetterCache = new WeakMap();
77
152
 
78
153
  function stateGetterGet( target, prop ) {
79
154
  if ( prop === 'get' ) {
80
155
  return arg => {
81
156
  if ( typeof arg == 'function' )
82
- return createDynamicStateRecord( target, arg ).proxy;
157
+ return createDynamicStateGetterProxy( target, arg );
83
158
  else
84
- return createChildStateRecord( target, arg ).proxy;
159
+ return createChildStateGetterProxy( target, arg );
85
160
  };
86
161
  }
87
162
 
88
163
  if ( prop === Symbol.toPrimitive )
89
164
  throw new Error( 'Using a state getter as a primitive value is not allowed' );
90
165
 
91
- return createChildStateRecord( target, prop ).proxy;
166
+ return createChildStateGetterProxy( target, prop );
92
167
  }
93
168
 
94
- function stateGetterApply( target ) {
95
- const value = target();
96
-
97
- if ( isPlainObjectOrArray( value ) ) {
98
- const record = stateGetterRecords.get( target );
99
- return createReaderRecord( record, value ).proxy;
100
- }
101
-
102
- return value;
103
- }
104
-
105
- function createRecord( prop, getter ) {
106
- return {
107
- prop,
108
- target: getter,
109
- proxy: new Proxy( getter, stateGetterHandler ),
110
- children: null,
111
- reader: null,
112
- watches: null,
113
- };
169
+ function createRootStateGetterProxy( getter ) {
170
+ return new Proxy( getter, stateGetterHandler );
114
171
  }
115
172
 
116
- function createRootStateRecord( getter ) {
117
- const record = createRecord( '(root)', getter );
173
+ function createChildStateGetterProxy( target, prop ) {
174
+ let cache = stateGetterCache.get( target );
118
175
 
119
- stateGetterRecords.set( getter, record );
120
-
121
- return record;
122
- }
123
-
124
- function createChildStateRecord( target, prop ) {
125
- const record = stateGetterRecords.get( target );
176
+ if ( cache == null ) {
177
+ cache = Object.create( null );
178
+ stateGetterCache.set( target, cache );
179
+ }
126
180
 
127
- if ( record.children != null && record.children[ prop ] != null )
128
- return record.children[ prop ];
181
+ let proxy = cache[ prop ];
129
182
 
130
- if ( record.children == null )
131
- record.children = Object.create( null );
183
+ if ( proxy == null ) {
184
+ proxy = new Proxy( getter, stateGetterHandler );
185
+ cache[ prop ] = proxy;
186
+ }
132
187
 
133
- const childRecord = createRecord( prop, getter );
188
+ return proxy;
134
189
 
135
190
  function getter() {
136
191
  const value = target();
137
- track( childRecord );
138
- return value[ prop ];
139
- }
140
- stateGetterRecords.set( getter, childRecord );
141
192
 
142
- record.children[ prop ] = childRecord;
193
+ if ( isPlainObjectOrArray( value ) )
194
+ trackProperty( value, prop );
143
195
 
144
- return childRecord;
196
+ return value[ prop ];
197
+ }
145
198
  }
146
199
 
147
- function createDynamicStateRecord( target, arg ) {
148
- const dynamicRecord = {
149
- prop: '(dynamic)',
150
- target: getter,
151
- proxy: new Proxy( getter, stateGetterHandler ),
152
- };
200
+ function createDynamicStateGetterProxy( target, arg ) {
201
+ return new Proxy( getter, stateGetterHandler );
153
202
 
154
203
  function getter() {
155
204
  const value = target();
156
- const index = arg();
157
- track( createChildStateRecord( target, index ) );
158
- return value[ index ];
205
+ const prop = arg();
206
+
207
+ if ( isPlainObjectOrArray( value ) )
208
+ trackProperty( value, prop );
209
+
210
+ return value[ prop ];
159
211
  }
212
+ }
213
+
214
+ function stateGetterApply( target ) {
215
+ const value = target();
160
216
 
161
- stateGetterRecords.set( getter, dynamicRecord );
217
+ if ( isPlainObjectOrArray( value ) )
218
+ return createStateReader( value ).proxy;
162
219
 
163
- return dynamicRecord;
220
+ return value;
164
221
  }
165
222
 
166
223
  const stateReaderHandler = {
@@ -173,53 +230,82 @@ const stateReaderHandler = {
173
230
  },
174
231
  };
175
232
 
176
- const stateReaderRecords = new WeakMap();
177
-
178
- const stateReaderProxies = new WeakSet();
233
+ const stateReaders = new WeakMap();
234
+ const stateReaderTargets = new WeakMap();
179
235
 
180
236
  function stateReaderGet( target, prop ) {
181
- const record = stateReaderRecords.get( target );
182
-
183
- const childRecord = createChildStateRecord( record.target, prop );
184
-
185
- track( childRecord );
237
+ trackProperty( target, prop );
186
238
 
187
239
  const value = target[ prop ];
188
240
 
189
241
  if ( isPlainObjectOrArray( value ) )
190
- return createReaderRecord( childRecord, value ).proxy;
242
+ return createStateReader( value ).proxy;
191
243
 
192
244
  return value;
193
245
  }
194
246
 
195
- function createReaderRecord( record, value ) {
196
- if ( record.reader != null && record.reader.value === value )
197
- return record.reader;
247
+ function trackProperty( target, prop ) {
248
+ const reader = createStateReader( target );
249
+
250
+ if ( reader.props == null )
251
+ reader.props = Object.create( null );
252
+
253
+ let record = reader.props[ prop ];
198
254
 
199
- // reuse an existing record; this happens e.g. if an array was rearranged using splice/unshift
200
- const existingRecord = stateReaderRecords.get( value );
201
- if ( existingRecord != null ) {
202
- record.reader = existingRecord;
203
- existingRecord.target = record.target;
204
- return existingRecord;
255
+ if ( record == null ) {
256
+ record = new Set();
257
+ reader.props[ prop ] = record;
205
258
  }
206
259
 
207
- record.reader = {
208
- value,
209
- target: record.target,
210
- proxy: new Proxy( value, stateReaderHandler ),
211
- };
260
+ track( record );
261
+ }
212
262
 
213
- stateReaderRecords.set( value, record.reader );
214
- stateReaderProxies.add( record.reader.proxy );
263
+ function createStateReader( value ) {
264
+ let reader = stateReaders.get( value );
215
265
 
216
- return record.reader;
266
+ if ( reader == null ) {
267
+ reader = {
268
+ proxy: new Proxy( value, stateReaderHandler ),
269
+ props: null,
270
+ };
271
+ stateReaders.set( value, reader );
272
+ stateReaderTargets.set( reader.proxy, value );
273
+ }
274
+
275
+ return reader;
217
276
  }
218
277
 
219
- function isStateReader( value ) {
220
- if ( stateReaderProxies.has( value ) )
221
- return true;
222
- return false;
278
+ function getStateReader( value ) {
279
+ return stateReaders.get( value );
280
+ }
281
+
282
+ function isStateReaderProxy( value ) {
283
+ return stateReaderTargets.has( value );
284
+ }
285
+
286
+ function unwrapStateReaderProxy( value ) {
287
+ const valueTarget = stateReaderTargets.get( value );
288
+ if ( valueTarget != null )
289
+ return valueTarget;
290
+
291
+ unwrapProperties( value, stateReaderTargets, new WeakSet() );
292
+
293
+ return value;
294
+ }
295
+
296
+ function unwrapProperties( value, map, visited ) {
297
+ visited.add( value );
298
+
299
+ for ( const key in value ) {
300
+ const prop = value[ key ];
301
+ if ( isPlainObjectOrArray( prop ) && !visited.has( prop ) ) {
302
+ const valueTarget = map.get( prop );
303
+ if ( valueTarget != null )
304
+ value[ key ] = valueTarget;
305
+ else
306
+ unwrapProperties( prop, map, visited );
307
+ }
308
+ }
223
309
  }
224
310
 
225
311
  const computedGetterHandler = {
@@ -272,7 +358,7 @@ function createDynamicComputedProxy( target, arg ) {
272
358
  function computedGetterApply( target ) {
273
359
  const value = target();
274
360
 
275
- if ( isPlainObjectOrArray( value ) && !isStateReader( value ) )
361
+ if ( isPlainObjectOrArray( value ) && !isStateReaderProxy( value ) )
276
362
  return new Proxy( value, computedReaderHandler );
277
363
 
278
364
  return value;
@@ -291,7 +377,7 @@ const computedReaderHandler = {
291
377
  function computedReaderGet( target, prop ) {
292
378
  const value = target[ prop ];
293
379
 
294
- if ( isPlainObjectOrArray( value ) && !isStateReader( value ) )
380
+ if ( isPlainObjectOrArray( value ) && !isStateReaderProxy( value ) )
295
381
  return new Proxy( value, computedReaderHandler );
296
382
 
297
383
  return value;
@@ -303,10 +389,11 @@ class Mutator {
303
389
  }
304
390
  }
305
391
 
306
- function applyMutator( record, value, callback ) {
307
- mutableValues = new WeakMap();
308
- mutableValues.set( value, record );
392
+ function mutate( callback ) {
393
+ return new Mutator( callback );
394
+ }
309
395
 
396
+ function applyMutator( value, callback ) {
310
397
  const proxy = new Proxy( value, stateMutatorHandler );
311
398
 
312
399
  stateMutatorTargets = new WeakMap();
@@ -316,10 +403,8 @@ function applyMutator( record, value, callback ) {
316
403
 
317
404
  try {
318
405
  callback( proxy );
319
-
320
406
  return mutated;
321
407
  } finally {
322
- mutableValues = null;
323
408
  stateMutatorTargets = null;
324
409
  mutated = null;
325
410
  }
@@ -331,7 +416,6 @@ const stateMutatorHandler = {
331
416
  deleteProperty: stateMutatorDelete,
332
417
  };
333
418
 
334
- let mutableValues = null;
335
419
  let stateMutatorTargets = null;
336
420
 
337
421
  let mutated = null;
@@ -340,14 +424,8 @@ function stateMutatorGet( target, prop ) {
340
424
  const value = target[ prop ];
341
425
 
342
426
  if ( isPlainObjectOrArray( value ) ) {
343
- const record = mutableValues.get( target );
344
-
345
- const childRecord = createChildStateRecord( record.target, prop );
346
- mutableValues.set( value, childRecord );
347
-
348
427
  const proxy = new Proxy( value, stateMutatorHandler );
349
428
  stateMutatorTargets.set( proxy, value );
350
-
351
429
  return proxy;
352
430
  }
353
431
 
@@ -355,18 +433,23 @@ function stateMutatorGet( target, prop ) {
355
433
  }
356
434
 
357
435
  function stateMutatorSet( target, prop, value ) {
358
- const record = mutableValues.get( target );
359
-
360
- if ( Array.isArray( target ) && isIntegerKey( prop ) && Number( prop ) >= target.length ) {
361
- // the length of the array is changed implicitly when a new element is added
362
- const lengthRecord = createChildStateRecord( record.target, 'length' );
363
- mutated.add( lengthRecord );
436
+ const reader = getStateReader( target );
437
+
438
+ if ( reader != null && reader.props != null ) {
439
+ if ( Array.isArray( target ) && isIntegerKey( prop ) && Number( prop ) >= target.length ) {
440
+ // the length of the array is changed implicitly when a new element is added
441
+ const record = reader.props.length;
442
+ if ( record != null )
443
+ mutated.add( record );
444
+ }
364
445
  }
365
446
 
366
447
  if ( isPlainObjectOrArray( value ) ) {
367
448
  const valueTarget = stateMutatorTargets.get( value );
368
449
  if ( valueTarget != null )
369
450
  value = valueTarget;
451
+ else
452
+ unwrapProperties( value, stateMutatorTargets, new WeakSet() );
370
453
  }
371
454
 
372
455
  if ( target[ prop ] === value )
@@ -374,8 +457,11 @@ function stateMutatorSet( target, prop, value ) {
374
457
 
375
458
  target[ prop ] = value;
376
459
 
377
- const childRecord = createChildStateRecord( record.target, prop );
378
- mutated.add( childRecord );
460
+ if ( reader != null && reader.props != null ) {
461
+ const record = reader.props[ prop ];
462
+ if ( record != null )
463
+ mutated.add( record );
464
+ }
379
465
 
380
466
  return true;
381
467
  }
@@ -386,10 +472,13 @@ function stateMutatorDelete( target, prop ) {
386
472
 
387
473
  delete target[ prop ];
388
474
 
389
- const record = mutableValues.get( target );
475
+ const reader = getStateReader( target );
390
476
 
391
- const childRecord = createChildStateRecord( record.target, prop );
392
- mutated.add( childRecord );
477
+ if ( reader != null && reader.props != null ) {
478
+ const record = reader.props[ prop ];
479
+ if ( record != null )
480
+ mutated.add( record );
481
+ }
393
482
 
394
483
  return true;
395
484
  }
@@ -398,28 +487,14 @@ function isIntegerKey( key ) {
398
487
  return typeof key == 'string' && key != 'NaN' && key[ 0 ] != '-' && '' + parseInt( key, 10 ) == key;
399
488
  }
400
489
 
401
- let scheduled = null;
402
-
403
- function schedule( callback ) {
404
- if ( scheduled == null ) {
405
- scheduled = new Set();
406
- Promise.resolve().then( () => {
407
- const watches = scheduled.values();
408
- scheduled = null;
409
- for ( const watch of watches )
410
- watch();
411
- } );
412
- }
413
-
414
- scheduled.add( callback );
415
- }
416
-
417
- function useState( initial ) {
490
+ function state( initial ) {
418
491
  let current = initial;
419
492
 
420
- const record = createRootStateRecord( getter );
493
+ const record = new Set();
494
+
495
+ const proxy = createRootStateGetterProxy( getter );
421
496
 
422
- return [ record.proxy, setter ];
497
+ return [ proxy, setter ];
423
498
 
424
499
  function getter() {
425
500
  track( record );
@@ -427,14 +502,21 @@ function useState( initial ) {
427
502
  }
428
503
  function setter( value ) {
429
504
  if ( value instanceof Mutator ) {
430
- const mutated = applyMutator( record, current, value.callback );
505
+ if ( !isPlainObjectOrArray( current ) )
506
+ throw new TypeError( 'Only plain objects and arrays can be mutated' );
507
+
508
+ const mutated = applyMutator( current, value.callback );
431
509
 
432
510
  for ( const record of mutated )
433
511
  react( record );
434
512
  } else {
435
513
  if ( typeof value == 'function' ) {
436
514
  const readOnlyValue = stateGetterApply( getter );
515
+
437
516
  value = value( readOnlyValue );
517
+
518
+ if ( isPlainObjectOrArray( value ) )
519
+ value = unwrapStateReaderProxy( value );
438
520
  }
439
521
 
440
522
  if ( current !== value ) {
@@ -445,27 +527,17 @@ function useState( initial ) {
445
527
  }
446
528
  }
447
529
 
448
- function useWatchEffect( callback ) {
449
- let deps = null;
450
-
451
- update();
452
-
453
- function update() {
454
- const [ _, newDeps ] = withDeps( callback );
455
-
456
- deps = updateDeps( newDeps, deps, update );
457
- }
458
- }
459
-
460
- function useWatch( getter, callback ) {
530
+ function watch( getter, callback ) {
461
531
  let [ value, deps ] = withDeps( getter );
462
532
 
463
- updateDeps( deps, null, update );
533
+ const watcher = createWatcher( deps, update, false );
534
+
535
+ deps = null;
464
536
 
465
537
  function update() {
466
538
  const [ newValue, newDeps ] = withDeps( getter );
467
539
 
468
- deps = updateDeps( newDeps, deps, update );
540
+ updateWatcher( watcher, newDeps );
469
541
 
470
542
  if ( newValue !== value ) {
471
543
  callback( newValue, value );
@@ -474,39 +546,29 @@ function useWatch( getter, callback ) {
474
546
  }
475
547
  }
476
548
 
477
- function useEffect( callback ) {
478
- let deps = null;
549
+ function effect( callback ) {
550
+ const watcher = createWatcher( null, update, true );
479
551
 
480
- schedule( update );
552
+ scheduleWatcher( watcher );
481
553
 
482
554
  function update() {
483
- const [ _, newDeps ] = withDeps( callback );
555
+ const [ _, deps ] = withDeps( callback );
484
556
 
485
- deps = updateDeps( newDeps, deps, () => schedule( update ) );
557
+ updateWatcher( watcher, deps );
486
558
  }
487
559
  }
488
560
 
489
- function useReactive( callback ) {
490
- let deps = null;
491
-
492
- update();
493
-
494
- function update() {
495
- const [ _, newDeps ] = withDeps( callback );
496
-
497
- deps = updateDeps( newDeps, deps, () => schedule( update ) );
498
- }
499
- }
561
+ function reactive( getter, callback ) {
562
+ let value;
500
563
 
501
- function useReactiveWatch( getter, callback ) {
502
- let value = null, deps = null;
564
+ const watcher = createWatcher( null, update, true );
503
565
 
504
566
  update( true );
505
567
 
506
568
  function update( force ) {
507
- const [ newValue, newDeps ] = withDeps( getter );
569
+ const [ newValue, deps ] = withDeps( getter );
508
570
 
509
- deps = updateDeps( newDeps, deps, () => schedule( update ) );
571
+ updateWatcher( watcher, deps );
510
572
 
511
573
  if ( force || newValue !== value ) {
512
574
  callback( newValue, value );
@@ -515,33 +577,38 @@ function useReactiveWatch( getter, callback ) {
515
577
  }
516
578
  }
517
579
 
518
- function useComputed( callback ) {
519
- let deps = null, value = null, valid = false;
580
+ function computed( callback ) {
581
+ let value, valid = false;
582
+
583
+ const watcher = createWatcher( null, invalidate, false );
520
584
 
521
585
  return createRootComputedProxy( execute );
522
586
 
523
587
  function execute() {
588
+ if ( watcher.callback == null )
589
+ throw new Error( 'A destroyed computed value cannot be used' );
590
+
524
591
  if ( !valid ) {
525
- const [ newValue, newDeps ] = withDeps( callback );
592
+ const [ newValue, deps ] = withDeps( callback );
526
593
 
527
- deps = updateDeps( newDeps, deps, invalidate );
594
+ updateWatcher( watcher, deps );
528
595
 
529
596
  value = newValue;
530
597
  valid = true;
531
598
  }
532
599
 
533
- addDeps( deps );
600
+ addDeps( watcher.deps );
534
601
 
535
602
  return value;
536
603
  }
537
604
 
538
605
  function invalidate() {
539
- value = null;
606
+ value = undefined;
540
607
  valid = false;
541
608
  }
542
609
  }
543
610
 
544
- function useConstant( value ) {
611
+ function constant( value ) {
545
612
  return createRootComputedProxy( getter );
546
613
 
547
614
  function getter() {
@@ -549,14 +616,11 @@ function useConstant( value ) {
549
616
  }
550
617
  }
551
618
 
552
- function mutate( callback ) {
553
- return new Mutator( callback );
554
- }
555
-
556
- function current( value ) {
619
+ function transform( value, callback ) {
557
620
  if ( typeof value == 'function' )
558
- return value();
559
- return value;
621
+ return computed( () => callback( value() ) );
622
+ else
623
+ return callback( value );
560
624
  }
561
625
 
562
- export { current, mutate, schedule, useComputed, useConstant, useEffect, useReactive, useReactiveWatch, useState, useWatch, useWatchEffect };
626
+ export { computed, constant, destroyScope, effect, mutate, reactive, schedule, state, transform, watch, withScope };