@webqit/observer 2.1.5 → 2.1.6
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/README.md +71 -27
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
**[Motivation](#motivation) • [Overview](#an-overview) • [Polyfill](#the-polyfill) • [Design Discussion](#design-discussion) • [Getting Involved](#getting-involved) • [License](#license)**
|
|
10
10
|
|
|
11
|
-
Observe and intercept operations on arbitrary JavaScript objects and arrays using a utility-first, general-purpose reactivity API! This API re-explores the unique design of the [`Object.observe()`](https://web.dev/es7-observe/) API and
|
|
11
|
+
Observe and intercept operations on arbitrary JavaScript objects and arrays using a utility-first, general-purpose reactivity API! This API re-explores the unique design of the [`Object.observe()`](https://web.dev/es7-observe/) API and takes a stab at what could be **a unifying API** over *related but disparate* things like `Object.observe()`, [Reflect](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect) APIs, and the "traps" API (proxy traps)!
|
|
12
12
|
|
|
13
13
|
Observer API is an upcoming proposal!
|
|
14
14
|
|
|
@@ -20,13 +20,13 @@ Tracking mutations on JavaScript objects has historically relied on "object wrap
|
|
|
20
20
|
|
|
21
21
|
+ **Programming model**: proxy traps and object accessors only lend themselves to being wired to *one* underlying listenining logic in the entire program. Objects are effectively open to multiple interactions on the outside but "locked" to one observer on the inside, enabling just a "many to one" communication model. This does not correctly reflect the most common usecases where the idea is to have any number of listeners per event, to enable a "many to many" model! It takes yet a non-trivial amount of effort to go from the default model to the one desired.
|
|
22
22
|
|
|
23
|
-
|
|
23
|
+
We find a design precedent to object observability in the [`Object.observe()`](https://web.dev/es7-observe/) API, which at one time checked all the boxes and touched the very pain points we have today! This is the idea with the new **Observer API**!
|
|
24
24
|
|
|
25
|
-
└ [See more in the introductory blog post](https://dev.to/oxharris/reinvestigating-reactivity-22e0-temp-slug-5973064?preview=8afd0f8b156bf0b0b1c08058837fe4986054e52a7450f0a28adbaf07dcb7f5659b724166f553fb98ceab3d080748e86b244684f515d579bcd0f48cbb)<sup>draft</sup>
|
|
25
|
+
└ [See more in the introductory blog post](https://dev.to/oxharris/reinvestigating-reactivity-22e0-temp-slug-5973064?preview=8afd0f8b156bf0b0b1c08058837fe4986054e52a7450f0a28adbaf07dcb7f5659b724166f553fb98ceab3d080748e86b244684f515d579bcd0f48cbb#introducing-the-observer-api)<sup>draft</sup>
|
|
26
26
|
|
|
27
27
|
## An Overview
|
|
28
28
|
|
|
29
|
-
The Observer API
|
|
29
|
+
The Observer API is a set of utility functions.
|
|
30
30
|
|
|
31
31
|
+ [Method: `Observer.observe()`](#method-observerobserve)
|
|
32
32
|
+ [Usage](#usage)
|
|
@@ -53,7 +53,12 @@ Observer.observe( obj, callback[, options = {} ]);
|
|
|
53
53
|
|
|
54
54
|
```js
|
|
55
55
|
// Signature 2
|
|
56
|
-
Observer.observe( obj,
|
|
56
|
+
Observer.observe( obj, [ prop, prop, ... ], callback[, options = {} ]);
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
```js
|
|
60
|
+
// Signature 3
|
|
61
|
+
Observer.observe( obj, prop, callback[, options = {} ]);
|
|
57
62
|
```
|
|
58
63
|
|
|
59
64
|
#### Usage
|
|
@@ -74,7 +79,7 @@ const arr = [];
|
|
|
74
79
|
const abortController = Observer.observe( arr, handleChanges );
|
|
75
80
|
```
|
|
76
81
|
|
|
77
|
-
*
|
|
82
|
+
└ *Changes are delivered [**synchronously**](https://dev.to/oxharris/reinvestigating-reactivity-22e0-temp-slug-5973064?preview=8afd0f8b156bf0b0b1c08058837fe4986054e52a7450f0a28adbaf07dcb7f5659b724166f553fb98ceab3d080748e86b244684f515d579bcd0f48cbb#timing-and-batching) - as they happen.*
|
|
78
83
|
|
|
79
84
|
```js
|
|
80
85
|
// The change handler
|
|
@@ -85,21 +90,19 @@ function handleChanges( mutations ) {
|
|
|
85
90
|
}
|
|
86
91
|
```
|
|
87
92
|
|
|
88
|
-
**-->** Stop observing at any time by calling `abort()` on the returned *abortController
|
|
93
|
+
**-->** Stop observing at any time by calling `abort()` on the returned *abortController*:
|
|
89
94
|
|
|
90
95
|
```js
|
|
91
96
|
// Remove listener
|
|
92
97
|
abortController.abort();
|
|
93
98
|
```
|
|
94
99
|
|
|
95
|
-
|
|
100
|
+
└ And you can provide your own [Abort Signal](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal) instance:
|
|
96
101
|
|
|
97
102
|
```js
|
|
98
103
|
// Providing an AbortSignal
|
|
99
104
|
const abortController = new AbortController;
|
|
100
|
-
Observer.observe( obj,
|
|
101
|
-
// Handle...
|
|
102
|
-
}, { signal: abortController.signal } );
|
|
105
|
+
Observer.observe( obj, handleChanges, { signal: abortController.signal } );
|
|
103
106
|
```
|
|
104
107
|
|
|
105
108
|
```js
|
|
@@ -107,6 +110,20 @@ Observer.observe( obj, mutations => {
|
|
|
107
110
|
abortController.abort();
|
|
108
111
|
```
|
|
109
112
|
|
|
113
|
+
**-->** Where listeners initiate child observers, leverage "AbortSignal-cascading" to tie child observers to parent observer's lifecycle:
|
|
114
|
+
|
|
115
|
+
```js
|
|
116
|
+
// Parent -
|
|
117
|
+
const abortController = Observer.observe( obj, ( mutations, flags ) => {
|
|
118
|
+
|
|
119
|
+
// Child
|
|
120
|
+
Observer.observe( obj, handleChanges, { signal: flags.signal } ); // <<<---- AbortSignal-cascading
|
|
121
|
+
|
|
122
|
+
} );
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
└ *"Child" gets automatically aborted at parent's "next turn", and at parent's own abortion!*
|
|
126
|
+
|
|
110
127
|
#### Concept: *Mutation APIs*
|
|
111
128
|
|
|
112
129
|
Programmatically mutate properties of an object using the *[Reflect](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect#static_methods)-like* set of operators; each operation will be reported by observers:
|
|
@@ -220,7 +237,7 @@ $arr[ 2 ] = 'item2';
|
|
|
220
237
|
$arr.push( 'item3' );
|
|
221
238
|
```
|
|
222
239
|
|
|
223
|
-
*And no problem if you end up nesting the approaches.*
|
|
240
|
+
└ *And no problem if you end up nesting the approaches.*
|
|
224
241
|
|
|
225
242
|
```js
|
|
226
243
|
// 'value1'-->obj
|
|
@@ -249,7 +266,7 @@ obj = Observer.unproxy( $obj );
|
|
|
249
266
|
|
|
250
267
|
#### Concept: *Paths*
|
|
251
268
|
|
|
252
|
-
Observe "
|
|
269
|
+
Observe "the value" at a path in a given tree:
|
|
253
270
|
|
|
254
271
|
```js
|
|
255
272
|
// A tree structure that satisfies the path above
|
|
@@ -280,7 +297,7 @@ Observer.set( obj.level1, 'level2', 'level2-new-value' );
|
|
|
280
297
|
|
|
281
298
|
</details>
|
|
282
299
|
|
|
283
|
-
And
|
|
300
|
+
└ *And the initial tree structure can be whatever*:
|
|
284
301
|
|
|
285
302
|
```js
|
|
286
303
|
// A tree structure that is yet to be built
|
|
@@ -294,7 +311,7 @@ Observer.observe( obj, path, m => {
|
|
|
294
311
|
} );
|
|
295
312
|
```
|
|
296
313
|
|
|
297
|
-
Now, any operation that "modifies" the observed tree - either by extension or truncation - will fire our listener
|
|
314
|
+
└ *Now, any operation that "modifies" the observed tree - either by extension or truncation - will fire our listener*:
|
|
298
315
|
|
|
299
316
|
```js
|
|
300
317
|
Observer.set( obj, 'level1', { level2: {}, } );
|
|
@@ -309,7 +326,7 @@ Observer.set( obj, 'level1', { level2: {}, } );
|
|
|
309
326
|
|
|
310
327
|
</details>
|
|
311
328
|
|
|
312
|
-
Meanwhile, this next one completes the tree, and the listener reports a value at its observed path
|
|
329
|
+
└ *Meanwhile, this next one completes the tree, and the listener reports a value at its observed path*:
|
|
313
330
|
|
|
314
331
|
```js
|
|
315
332
|
Observer.set( obj.level1, 'level2', { level3: { level4: 'level4-value', }, } );
|
|
@@ -324,19 +341,21 @@ Observer.set( obj.level1, 'level2', { level3: { level4: 'level4-value', }, } );
|
|
|
324
341
|
|
|
325
342
|
</details>
|
|
326
343
|
|
|
327
|
-
|
|
344
|
+
**-->** Use the event's `context` property to inspect the parent event if you were to find the exact point at which mutation happened in the path in an audit trail:
|
|
328
345
|
|
|
329
346
|
```js
|
|
330
347
|
let context = m.context;
|
|
348
|
+
console.log(context);
|
|
331
349
|
```
|
|
332
350
|
|
|
333
|
-
|
|
351
|
+
└ *And up again one level until the root event*:
|
|
334
352
|
|
|
335
353
|
```js
|
|
336
354
|
let parentContext = context.context;
|
|
355
|
+
console.log(parentContext);
|
|
337
356
|
```
|
|
338
357
|
|
|
339
|
-
Where a promise is encountered along the path, further access is paused until promise resolves:
|
|
358
|
+
**-->** Observe trees that are built *asynchronously*! Where a promise is encountered along the path, further access is paused until promise resolves:
|
|
340
359
|
|
|
341
360
|
```js
|
|
342
361
|
Observer.set( obj.level1, 'level2', Promise.resolve( { level3: { level4: 'level4-value', }, } ) );
|
|
@@ -344,7 +363,7 @@ Observer.set( obj.level1, 'level2', Promise.resolve( { level3: { level4: 'level4
|
|
|
344
363
|
|
|
345
364
|
#### Concept: *Batch Mutations*
|
|
346
365
|
|
|
347
|
-
Make multiple mutations at a go, and they'll be correctly delivered
|
|
366
|
+
Make multiple mutations at a go, and they'll be correctly delivered as a batch to observers!
|
|
348
367
|
|
|
349
368
|
```js
|
|
350
369
|
// Batch operations on an object
|
|
@@ -397,7 +416,7 @@ Observer.set( obj, {
|
|
|
397
416
|
}, { detail: 'Certain detail' } );
|
|
398
417
|
```
|
|
399
418
|
|
|
400
|
-
*Observers recieve this value on their `mutation.detail` property.*
|
|
419
|
+
└ *Observers recieve this value on their `mutation.detail` property.*
|
|
401
420
|
|
|
402
421
|
```js
|
|
403
422
|
// An observer with detail
|
|
@@ -449,7 +468,7 @@ Observer.intercept( obj, traps[, options = {} ]);
|
|
|
449
468
|
|
|
450
469
|
Extend standard operations on an object - `Observer.set()`, `Observer.deleteProperty()`, etc - with custom traps using the [`Observer.intercept()`](https://webqit.io/tooling/observer/docs/api/reactions/intercept) method!
|
|
451
470
|
|
|
452
|
-
*Below, we intercept all "set" operations for an HTTP URL then transform it to an HTTPS URL.*
|
|
471
|
+
└ *Below, we intercept all "set" operations for an HTTP URL then transform it to an HTTPS URL.*
|
|
453
472
|
|
|
454
473
|
```js
|
|
455
474
|
const setTrap = ( operation, previous, next ) => {
|
|
@@ -461,7 +480,7 @@ const setTrap = ( operation, previous, next ) => {
|
|
|
461
480
|
Observer.intercept( obj, 'set', setTrap );
|
|
462
481
|
```
|
|
463
482
|
|
|
464
|
-
*Now, only the first of the following will fly as-is.*
|
|
483
|
+
└ *Now, only the first of the following will fly as-is.*
|
|
465
484
|
|
|
466
485
|
```js
|
|
467
486
|
// Not transformed
|
|
@@ -471,7 +490,7 @@ Observer.set( obj, 'url', 'https://webqit.io' );
|
|
|
471
490
|
Observer.set( obj, 'url', 'http://webqit.io' );
|
|
472
491
|
```
|
|
473
492
|
|
|
474
|
-
*And below, we intercept all "get" operations for a certain value to trigger a network fetch behind the scenes.*
|
|
493
|
+
└ *And below, we intercept all "get" operations for a certain value to trigger a network fetch behind the scenes.*
|
|
475
494
|
|
|
476
495
|
```js
|
|
477
496
|
const getTrap = ( operation, previous, next ) => {
|
|
@@ -483,7 +502,7 @@ const getTrap = ( operation, previous, next ) => {
|
|
|
483
502
|
Observer.intercept( obj, 'get', getTrap );
|
|
484
503
|
```
|
|
485
504
|
|
|
486
|
-
*And all of that can go into one "traps" object:*
|
|
505
|
+
└ *And all of that can go into one "traps" object:*
|
|
487
506
|
|
|
488
507
|
```js
|
|
489
508
|
Observer.intercept( obj, {
|
|
@@ -525,7 +544,32 @@ const Observer = window.webqit.Observer;
|
|
|
525
544
|
|
|
526
545
|
## API Reference
|
|
527
546
|
|
|
528
|
-
|
|
547
|
+
<!--
|
|
548
|
+
|
|
549
|
+
| Observer API | Reflect API | Description | Trap |
|
|
550
|
+
| -------------- | ------------ | ----------- | --------------- |
|
|
551
|
+
| `apply()` | ✓ | Invokes a function [↗](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect/apply) | `apply() {}` |
|
|
552
|
+
| `batch()` | `×` | Creates a batching context [↗](https://github.com/webqit/observer#:~:text=use%20the%20observer.batch()%20to%20batch%20multiple%20arbitrary%20mutations%20-%20whether%20related%20or%20not) | `-` |
|
|
553
|
+
| `construct()` | ✓ | Initializes a constructor [↗](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect/construct) | `construct() {}` |
|
|
554
|
+
| `defineProperty()` | ✓ | Defines a property [↗](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect/defineProperty) | `defineProperty() {}` |
|
|
555
|
+
| `deleteProperty()` | ✓ | Deletes a property [↗](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect/deleteProperty) | `deleteProperty() {}` |
|
|
556
|
+
| `get()` | ✓ | Reads a property [↗](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect/get) | `get() {}` |
|
|
557
|
+
| `getOwnPropertyDescriptor()` | ✓ | Obtains property descriptor [↗](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect/getOwnPropertyDescriptor) | `getOwnPropertyDescriptor() {}` |
|
|
558
|
+
| `getPrototypeOf()` | ✓ | Obtains object prototype [↗](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect/getPrototypeOf) | `getPrototypeOf() {}` |
|
|
559
|
+
| `has()` | ✓ | Checks property existence [↗](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect/has) | `has() {}` |
|
|
560
|
+
| `intercept()` | `×` | Binds a "traps" object [↗](https://github.com/webqit/observer#method-observerintercept) | `-` |
|
|
561
|
+
| `isExtensible()` | ✓ | Checks object extensibility [↗](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect/isExtensible) | `isExtensible() {}` |
|
|
562
|
+
| `observe()` | `×` | Binds a mutation observer [↗](https://github.com/webqit/observer#method-observerobserve) | `-` |
|
|
563
|
+
| `ownKeys()` | ✓ | Obtains object keys [↗](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect/ownKeys) | `ownKeys() {}` |
|
|
564
|
+
| `path()` | `×` | Evaluates a path [↗](#) | `-` |
|
|
565
|
+
| `preventExtensions()` | ✓ | Prevents object extensibility [↗](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect/preventExtensions) | `preventExtensions() {}` |
|
|
566
|
+
| `set()` | ✓ | Sets a property [↗](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect/set) | `set() {}` |
|
|
567
|
+
| `setPrototypeOf()` | ✓ | Sets object prototype [↗](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect/setPrototypeOf) | `setPrototypeOf() {}` |
|
|
568
|
+
| . | . | . | . |
|
|
569
|
+
| `accessorize()` | `×` | Applies pre-intercepted getters/setters to properties [↗](https://github.com/webqit/observer#:~:text=enable%20reactivity%20on%20specific%20properties%20with%20literal%20object%20accessors%20-%20using%20the%20observer.accessorize()%20method) | `-` |
|
|
570
|
+
| `proxy()` | `×` | Creates a pre-intercepted proxy object [↗](https://github.com/webqit/observer#:~:text=enable%20reactivity%20on%20arbitray%20properties%20with%20proxies%20-%20using%20the%20observer.proxy()%20method) | `-` |
|
|
571
|
+
|
|
572
|
+
-->
|
|
529
573
|
|
|
530
574
|
| Observer API | Reflect API | Trap |
|
|
531
575
|
| -------------- | ------------ | ----------- |
|
|
@@ -552,7 +596,7 @@ const Observer = window.webqit.Observer;
|
|
|
552
596
|
|
|
553
597
|
## Design Discussion
|
|
554
598
|
|
|
555
|
-
|
|
599
|
+
[See more in the introductory blog post](https://dev.to/oxharris/reinvestigating-reactivity-22e0-temp-slug-5973064?preview=8afd0f8b156bf0b0b1c08058837fe4986054e52a7450f0a28adbaf07dcb7f5659b724166f553fb98ceab3d080748e86b244684f515d579bcd0f48cbb#introducing-the-observer-api)<sup>draft</sup>
|
|
556
600
|
|
|
557
601
|
## Getting Involved
|
|
558
602
|
|