@webqit/observer 2.1.5 → 2.1.7
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 -28
- 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 nested observers (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,10 +266,9 @@ 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
|
-
// A tree structure that satisfies the path above
|
|
256
272
|
const obj = {
|
|
257
273
|
level1: {
|
|
258
274
|
level2: 'level2-value',
|
|
@@ -280,7 +296,7 @@ Observer.set( obj.level1, 'level2', 'level2-new-value' );
|
|
|
280
296
|
|
|
281
297
|
</details>
|
|
282
298
|
|
|
283
|
-
And
|
|
299
|
+
└ *And the initial tree structure can be whatever*:
|
|
284
300
|
|
|
285
301
|
```js
|
|
286
302
|
// A tree structure that is yet to be built
|
|
@@ -294,7 +310,7 @@ Observer.observe( obj, path, m => {
|
|
|
294
310
|
} );
|
|
295
311
|
```
|
|
296
312
|
|
|
297
|
-
Now, any operation that "modifies" the observed tree - either by extension or truncation - will fire our listener
|
|
313
|
+
└ *Now, any operation that "modifies" the observed tree - either by extension or truncation - will fire our listener*:
|
|
298
314
|
|
|
299
315
|
```js
|
|
300
316
|
Observer.set( obj, 'level1', { level2: {}, } );
|
|
@@ -309,7 +325,7 @@ Observer.set( obj, 'level1', { level2: {}, } );
|
|
|
309
325
|
|
|
310
326
|
</details>
|
|
311
327
|
|
|
312
|
-
Meanwhile, this next one completes the tree, and the listener reports a value at its observed path
|
|
328
|
+
└ *Meanwhile, this next one completes the tree, and the listener reports a value at its observed path*:
|
|
313
329
|
|
|
314
330
|
```js
|
|
315
331
|
Observer.set( obj.level1, 'level2', { level3: { level4: 'level4-value', }, } );
|
|
@@ -324,19 +340,21 @@ Observer.set( obj.level1, 'level2', { level3: { level4: 'level4-value', }, } );
|
|
|
324
340
|
|
|
325
341
|
</details>
|
|
326
342
|
|
|
327
|
-
|
|
343
|
+
**-->** 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
344
|
|
|
329
345
|
```js
|
|
330
346
|
let context = m.context;
|
|
347
|
+
console.log(context);
|
|
331
348
|
```
|
|
332
349
|
|
|
333
|
-
|
|
350
|
+
└ *And up again one level until the root event*:
|
|
334
351
|
|
|
335
352
|
```js
|
|
336
353
|
let parentContext = context.context;
|
|
354
|
+
console.log(parentContext);
|
|
337
355
|
```
|
|
338
356
|
|
|
339
|
-
Where a promise is encountered along the path, further access is paused until promise resolves:
|
|
357
|
+
**-->** Observe trees that are built *asynchronously*! Where a promise is encountered along the path, further access is paused until promise resolves:
|
|
340
358
|
|
|
341
359
|
```js
|
|
342
360
|
Observer.set( obj.level1, 'level2', Promise.resolve( { level3: { level4: 'level4-value', }, } ) );
|
|
@@ -344,7 +362,7 @@ Observer.set( obj.level1, 'level2', Promise.resolve( { level3: { level4: 'level4
|
|
|
344
362
|
|
|
345
363
|
#### Concept: *Batch Mutations*
|
|
346
364
|
|
|
347
|
-
Make multiple mutations at a go, and they'll be correctly delivered
|
|
365
|
+
Make multiple mutations at a go, and they'll be correctly delivered as a batch to observers!
|
|
348
366
|
|
|
349
367
|
```js
|
|
350
368
|
// Batch operations on an object
|
|
@@ -397,7 +415,7 @@ Observer.set( obj, {
|
|
|
397
415
|
}, { detail: 'Certain detail' } );
|
|
398
416
|
```
|
|
399
417
|
|
|
400
|
-
*Observers recieve this value on their `mutation.detail` property.*
|
|
418
|
+
└ *Observers recieve this value on their `mutation.detail` property.*
|
|
401
419
|
|
|
402
420
|
```js
|
|
403
421
|
// An observer with detail
|
|
@@ -449,7 +467,7 @@ Observer.intercept( obj, traps[, options = {} ]);
|
|
|
449
467
|
|
|
450
468
|
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
469
|
|
|
452
|
-
*Below, we intercept all "set" operations for an HTTP URL then transform it to an HTTPS URL.*
|
|
470
|
+
└ *Below, we intercept all "set" operations for an HTTP URL then transform it to an HTTPS URL.*
|
|
453
471
|
|
|
454
472
|
```js
|
|
455
473
|
const setTrap = ( operation, previous, next ) => {
|
|
@@ -461,7 +479,7 @@ const setTrap = ( operation, previous, next ) => {
|
|
|
461
479
|
Observer.intercept( obj, 'set', setTrap );
|
|
462
480
|
```
|
|
463
481
|
|
|
464
|
-
*Now, only the first of the following will fly as-is.*
|
|
482
|
+
└ *Now, only the first of the following will fly as-is.*
|
|
465
483
|
|
|
466
484
|
```js
|
|
467
485
|
// Not transformed
|
|
@@ -471,7 +489,7 @@ Observer.set( obj, 'url', 'https://webqit.io' );
|
|
|
471
489
|
Observer.set( obj, 'url', 'http://webqit.io' );
|
|
472
490
|
```
|
|
473
491
|
|
|
474
|
-
*And below, we intercept all "get" operations for a certain value to trigger a network fetch behind the scenes.*
|
|
492
|
+
└ *And below, we intercept all "get" operations for a certain value to trigger a network fetch behind the scenes.*
|
|
475
493
|
|
|
476
494
|
```js
|
|
477
495
|
const getTrap = ( operation, previous, next ) => {
|
|
@@ -483,7 +501,7 @@ const getTrap = ( operation, previous, next ) => {
|
|
|
483
501
|
Observer.intercept( obj, 'get', getTrap );
|
|
484
502
|
```
|
|
485
503
|
|
|
486
|
-
*And all of that can go into one "traps" object:*
|
|
504
|
+
└ *And all of that can go into one "traps" object:*
|
|
487
505
|
|
|
488
506
|
```js
|
|
489
507
|
Observer.intercept( obj, {
|
|
@@ -525,7 +543,32 @@ const Observer = window.webqit.Observer;
|
|
|
525
543
|
|
|
526
544
|
## API Reference
|
|
527
545
|
|
|
528
|
-
|
|
546
|
+
<!--
|
|
547
|
+
|
|
548
|
+
| Observer API | Reflect API | Description | Trap |
|
|
549
|
+
| -------------- | ------------ | ----------- | --------------- |
|
|
550
|
+
| `apply()` | ✓ | Invokes a function [↗](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect/apply) | `apply() {}` |
|
|
551
|
+
| `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) | `-` |
|
|
552
|
+
| `construct()` | ✓ | Initializes a constructor [↗](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect/construct) | `construct() {}` |
|
|
553
|
+
| `defineProperty()` | ✓ | Defines a property [↗](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect/defineProperty) | `defineProperty() {}` |
|
|
554
|
+
| `deleteProperty()` | ✓ | Deletes a property [↗](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect/deleteProperty) | `deleteProperty() {}` |
|
|
555
|
+
| `get()` | ✓ | Reads a property [↗](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect/get) | `get() {}` |
|
|
556
|
+
| `getOwnPropertyDescriptor()` | ✓ | Obtains property descriptor [↗](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect/getOwnPropertyDescriptor) | `getOwnPropertyDescriptor() {}` |
|
|
557
|
+
| `getPrototypeOf()` | ✓ | Obtains object prototype [↗](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect/getPrototypeOf) | `getPrototypeOf() {}` |
|
|
558
|
+
| `has()` | ✓ | Checks property existence [↗](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect/has) | `has() {}` |
|
|
559
|
+
| `intercept()` | `×` | Binds a "traps" object [↗](https://github.com/webqit/observer#method-observerintercept) | `-` |
|
|
560
|
+
| `isExtensible()` | ✓ | Checks object extensibility [↗](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect/isExtensible) | `isExtensible() {}` |
|
|
561
|
+
| `observe()` | `×` | Binds a mutation observer [↗](https://github.com/webqit/observer#method-observerobserve) | `-` |
|
|
562
|
+
| `ownKeys()` | ✓ | Obtains object keys [↗](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect/ownKeys) | `ownKeys() {}` |
|
|
563
|
+
| `path()` | `×` | Evaluates a path [↗](#) | `-` |
|
|
564
|
+
| `preventExtensions()` | ✓ | Prevents object extensibility [↗](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect/preventExtensions) | `preventExtensions() {}` |
|
|
565
|
+
| `set()` | ✓ | Sets a property [↗](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect/set) | `set() {}` |
|
|
566
|
+
| `setPrototypeOf()` | ✓ | Sets object prototype [↗](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect/setPrototypeOf) | `setPrototypeOf() {}` |
|
|
567
|
+
| . | . | . | . |
|
|
568
|
+
| `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) | `-` |
|
|
569
|
+
| `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) | `-` |
|
|
570
|
+
|
|
571
|
+
-->
|
|
529
572
|
|
|
530
573
|
| Observer API | Reflect API | Trap |
|
|
531
574
|
| -------------- | ------------ | ----------- |
|
|
@@ -552,7 +595,7 @@ const Observer = window.webqit.Observer;
|
|
|
552
595
|
|
|
553
596
|
## Design Discussion
|
|
554
597
|
|
|
555
|
-
|
|
598
|
+
[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
599
|
|
|
557
600
|
## Getting Involved
|
|
558
601
|
|