@webqit/observer 3.8.3 → 3.8.4

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.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/README copy.md +0 -500
package/package.json CHANGED
@@ -12,7 +12,7 @@
12
12
  "events"
13
13
  ],
14
14
  "homepage": "https://webqit.io/tooling/observer",
15
- "version": "3.8.3",
15
+ "version": "3.8.4",
16
16
  "license": "MIT",
17
17
  "repository": {
18
18
  "type": "git",
package/README copy.md DELETED
@@ -1,500 +0,0 @@
1
- # The Observer API
2
-
3
- <!-- BADGES/ -->
4
-
5
- <span class="badge-npmversion"><a href="https://npmjs.org/package/@webqit/observer" title="View this project on NPM"><img src="https://img.shields.io/npm/v/@webqit/observer.svg" alt="NPM version" /></a></span> <span class="badge-npmdownloads"><a href="https://npmjs.org/package/@webqit/observer" title="View this project on NPM"><img src="https://img.shields.io/npm/dm/@webqit/observer.svg" alt="NPM downloads" /></a></span>
6
-
7
- <!-- /BADGES -->
8
-
9
- **[Motivation](#motivation) • [Overview](#an-overview) • [Documentation](#documentation) • [Polyfill](#the-polyfill) • [Getting Involved](#getting-involved) • [License](#license)**
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 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
-
13
- Observer API is an upcoming proposal!
14
-
15
- ## Motivation
16
-
17
- Tracking mutations on JavaScript objects has historically relied on "object wrapping" techniques with [ES6 Proxies](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy), and on "property mangling" techniques with [getters and setters](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty). Besides how the first poses an *object identity* problem and the second, an *interoperability* problem, there is also much inflexibility in the programming model that each enables!
18
-
19
- This is discussed extensively in [the introductory blog post](https://dev.to/oxharris/re-exploring-reactivity-and-introducing-the-observer-api-and-reflex-functions-4h70)
20
-
21
- 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! The idea with the new **Observer API** is to re-explore that unique design with a more wholistic approach that considers the broader subject of Reactive Programming in JavaScript!
22
-
23
- ## Status
24
-
25
- + Working implementation via a polyfill
26
- + Integral to the [Quantum JS project](https://github.com/webqit/quantum-js)
27
- + Actively developed
28
- + Open to contributions
29
-
30
- ## An Overview
31
-
32
- The Observer API is a set of utility functions - notably, the `Observer.observe()` and `Observer.intercept()` methods - for all things object observability.
33
-
34
- <details><summary>This is documentation for Observer@2.x</summary>
35
-
36
- Looking for [`Observer@1.x`](https://github.com/webqit/observer/tree/v1.7.6)?
37
-
38
- </details>
39
-
40
- ### Method: `Observer.observe()`
41
-
42
- Observe mutations on arbitrary objects or arrays!
43
-
44
- ```js
45
- // An object
46
- const obj = {};
47
- // Mtation observer on an object
48
- const abortController = Observer.observe( obj, inspect );
49
- ```
50
-
51
- ```js
52
- // An array
53
- const arr = [];
54
- // Mtation observer on an array
55
- const abortController = Observer.observe( arr, inspect );
56
- ```
57
-
58
- └ *Changes are delivered [**synchronously**](https://github.com/webqit/observer/wiki/#timing-and-batching) - as they happen.*
59
-
60
- ```js
61
- // The change handler
62
- function inspect( mutations ) {
63
- mutations.forEach( mutation => {
64
- console.log( mutation.type, mutation.key, mutation.value, mutation.oldValue );
65
- } );
66
- }
67
- ```
68
-
69
- **-->** Stop observing at any time by calling `abort()` on the returned *abortController*:
70
-
71
- ```js
72
- // Remove listener
73
- abortController.abort();
74
- ```
75
-
76
- └ And you can provide your own [Abort Signal](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal) instance:
77
-
78
- ```js
79
- // Providing an AbortSignal
80
- const abortController = new AbortController;
81
- Observer.observe( obj, inspect, { signal: abortController.signal } );
82
- ```
83
-
84
- ```js
85
- // Abort at any time
86
- abortController.abort();
87
- ```
88
-
89
- **-->** Where listeners initiate nested observers (child observers), leverage "AbortSignal-cascading" to tie child observers to parent observer's lifecycle:
90
-
91
- ```js
92
- // Parent -
93
- const abortController = Observer.observe( obj, ( mutations, flags ) => {
94
-
95
- // Child
96
- Observer.observe( obj, inspect, { signal: flags.signal } ); // <<<---- AbortSignal-cascading
97
-
98
- // Child
99
- Observer.observe( obj, inspect, { signal: flags.signal } ); // <<<---- AbortSignal-cascading
100
-
101
- } );
102
- ```
103
-
104
- └ *"Child" observers get automatically aborted at parent's "next turn", and at parent's own abortion!*
105
-
106
- **-->** Use the `options.diff` parameter to ignore mutation events whose current value is same as previous value:
107
-
108
- ```js
109
- // Parent -
110
- const abortController = Observer.observe( obj, mutations => {
111
- console.log( m.type, m.value, m.oldValue );
112
- }, { diff: true } );
113
- ```
114
-
115
- ```js
116
- obj.property = 'Same value';
117
- ```
118
-
119
- ```js
120
- obj.property = 'Same value';
121
- ```
122
-
123
- └ *Observer is called only on the first update!*
124
-
125
- #### Concept: *Mutation APIs*
126
-
127
- In addition to making literal operations, you can also 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:
128
-
129
- ```js
130
- // A single "set" operation on an object
131
- Observer.set( obj, 'prop0', 'value0' );
132
- Observer.defineProperty( obj, 'prop1', { get: () => 'value1' } );
133
- Observer.deleteProperty( obj, 'prop2' );
134
- ```
135
-
136
- ```js
137
- // A single "set" operation on an array
138
- Observer.set( arr, 0, 'item0' ); // Array [ 'item0' ]
139
- Observer.deleteProperty( arr, 0 ); // Array [ <1 empty slot> ]
140
- ```
141
-
142
- <details><summary>Polyfill limitations</summary>
143
-
144
- In the polyfill, object observability doesn't work with literal operations. **Beware non-reactive operations**:
145
-
146
- ```js
147
- // Literal object operators
148
- delete obj.prop0;
149
- obj.prop3 = 'value3';
150
- ```
151
-
152
- ```js
153
- // Array methods
154
- arr.push( 'item3' );
155
- arr.pop();
156
- ```
157
-
158
- </details>
159
-
160
- **-->** Enable reactivity on *specific* properties with literal *object accessors* - using the `Observer.accessorize()` method:
161
-
162
- ```js
163
- // Accessorize all current enumerable properties
164
- Observer.accessorize( obj );
165
- // Accessorize specific properties (existing or new)
166
- Observer.accessorize( obj, [ 'prop0', 'prop1', 'prop2' ] );
167
-
168
- // Make reactive UPDATES
169
- obj.prop0 = 'value0';
170
- obj.prop1 = 'value1';
171
- obj.prop2 = 'value2';
172
- ```
173
-
174
- ```js
175
- // Accessorize all current indexes
176
- Observer.accessorize( arr );
177
- // Accessorize specific indexes (existing or new)
178
- Observer.accessorize( arr, [ 0, 1, 2 ] );
179
-
180
- // Make reactive UPDATES
181
- arr[ 0 ] = 'item0';
182
- arr[ 1 ] = 'item1';
183
- arr[ 2 ] = 'item2';
184
-
185
- // Bonus reactivity with array methods that re-index existing items
186
- arr.unshift( 'new-item0' );
187
- arr.shift();
188
- ```
189
-
190
- <details><summary>Polyfill limitations</summary>
191
-
192
- In the polyfill, object observability doesn't work with literal operations. **Beware non-reactive operations**:
193
-
194
- ```js
195
- // The delete operator and object properties that haven't been accessorized
196
- delete obj.prop0;
197
- obj.prop3 = 'value3';
198
- ```
199
-
200
- ```js
201
- // Array methods that do not re-index existing items
202
- arr.push( 'item0' );
203
- arr.pop();
204
- ```
205
-
206
- </details>
207
-
208
- **-->** Enable reactivity on *arbitray* properties with *Proxies* - using the `Observer.proxy()` method:
209
-
210
- ```js
211
- // Obtain a reactive Proxy for an object
212
- const $obj = Observer.proxy( obj );
213
-
214
- // Make reactive operations
215
- $obj.prop1 = 'value1';
216
- $obj.prop4 = 'value4';
217
- $obj.prop8 = 'value8';
218
-
219
- // With the delete operator
220
- delete $obj.prop0;
221
- ```
222
-
223
- ```js
224
- // Obtain a reactive Proxy for an array
225
- const $arr = Observer.proxy( arr );
226
-
227
- // Make reactive operations
228
- $arr[ 0 ] = 'item0';
229
- $arr[ 1 ] = 'item1';
230
- $arr[ 2 ] = 'item2';
231
-
232
- // With an instance method
233
- $arr.push( 'item3' );
234
- ```
235
-
236
- └ *And no problem if you end up nesting the approaches.*
237
-
238
- ```js
239
- // 'value1'-->obj
240
- Observer.accessorize( obj, [ 'prop0', 'prop1', 'prop2', ] );
241
- obj.prop1 = 'value1';
242
-
243
- // 'value1'-->$obj-->obj
244
- let $obj = Observer.proxy( obj );
245
- $obj.prop1 = 'value1';
246
-
247
- // 'value1'-->set()-->$obj-->obj
248
- Observer.set( $obj, 'prop1', 'value1' );
249
- ```
250
-
251
- **-->** "Restore" accessorized properties to their normal state by calling the `unaccessorize()` method:
252
-
253
- ```js
254
- Observer.unaccessorize( obj, [ 'prop1', 'prop6', 'prop10' ] );
255
- ```
256
-
257
- **-->** "Reproduce" original objects from Proxies obtained via `Observer.proxy()` by calling the `unproxy()` method:
258
-
259
- ```js
260
- obj = Observer.unproxy( $obj );
261
- ```
262
-
263
- #### Concept: *Paths*
264
-
265
- Observe "a property" at a path in an object tree:
266
-
267
- ```js
268
- const obj = {
269
- level1: {
270
- level2: 'level2-value',
271
- },
272
- };
273
- ```
274
-
275
- ```js
276
- const path = Observer.path( 'level1', 'level2' );
277
- Observer.observe( obj, path, m => {
278
- console.log( m.type, m.path, m.value, m.isUpdate );
279
- } );
280
- ```
281
-
282
- ```js
283
- Observer.set( obj.level1, 'level2', 'level2-new-value' );
284
- ```
285
-
286
- <details><summary>Console</summary>
287
-
288
- | type | path | value | isUpdate |
289
- | ---- | ---- | ----- | -------- |
290
- | `set` | [ `level1`, `level2`, ] | `level2-new-value` | `true` |
291
-
292
- </details>
293
-
294
- └ *And the initial tree structure can be whatever*:
295
-
296
- ```js
297
- // A tree structure that is yet to be built
298
- const obj = {};
299
- ```
300
-
301
- ```js
302
- const path = Observer.path( 'level1', 'level2', 'level3', 'level4' );
303
- Observer.observe( obj, path, m => {
304
- console.log( m.type, m.path, m.value, m.isUpdate );
305
- } );
306
- ```
307
-
308
- └ *Now, any operation that changes what "the value" at the path resolves to - either by tree extension or tree truncation - will fire our listener*:
309
-
310
- ```js
311
- Observer.set( obj, 'level1', { level2: {}, } );
312
- ```
313
-
314
- <details>
315
- <summary>Console</summary>
316
-
317
- | type | path | value | isUpdate |
318
- | ---- | ---- | ----- | -------- |
319
- | `set` | [ `level1`, `level2`, `level3`, `level4`, ] | `undefined` | `false` |
320
-
321
- </details>
322
-
323
- └ *Meanwhile, this next one completes the tree, and the listener reports a value at its observed path*:
324
-
325
- ```js
326
- Observer.set( obj.level1, 'level2', { level3: { level4: 'level4-value', }, } );
327
- ```
328
-
329
- <details>
330
- <summary>Console</summary>
331
-
332
- | type | path | value | isUpdate |
333
- | ---- | ---- | ----- | -------- |
334
- | `set` | [ `level1`, `level2`, `level3`, `level4`, ] | `level4-value` | `false` |
335
-
336
- </details>
337
-
338
- **-->** 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:
339
-
340
- ```js
341
- let context = m.context;
342
- console.log(context);
343
- ```
344
-
345
- └ *And up again one level until the root event*:
346
-
347
- ```js
348
- let parentContext = context.context;
349
- console.log(parentContext);
350
- ```
351
-
352
- **-->** Observe trees that are built *asynchronously*! Where a promise is encountered along the path, further access is paused until promise resolves:
353
-
354
- ```js
355
- Observer.set( obj.level1, 'level2', Promise.resolve( { level3: { level4: 'level4-value', }, } ) );
356
- ```
357
-
358
- #### Concept: *Batch Mutations*
359
-
360
- Make multiple mutations at a go, and they'll be correctly delivered as a batch to observers!
361
-
362
- ```js
363
- // Batch operations on an object
364
- Observer.set( obj, {
365
- prop0: 'value0',
366
- prop1: 'value1',
367
- prop2: 'value2',
368
- } );
369
- Observer.defineProperties( obj, {
370
- prop0: { value: 'value0' },
371
- prop1: { value: 'value1' },
372
- prop2: { get: () => 'value2' },
373
- } );
374
- Observer.deleteProperties( obj, [ 'prop0', 'prop1', 'prop2' ] );
375
- ```
376
-
377
- ```js
378
- // Batch operations on an array
379
- Observer.set( arr, {
380
- '0': 'item0',
381
- '1': 'item1',
382
- '2': 'item2',
383
- } );
384
- Object.proxy( arr ).push( 'item3', 'item4', 'item5', );
385
- Object.proxy( arr ).unshift( 'new-item0' );
386
- Object.proxy( arr ).splice( 0 );
387
- ```
388
-
389
- **-->** Use the `Observer.batch()` to batch multiple arbitrary mutations - whether related or not:
390
-
391
- ```js
392
- Observer.batch( arr, async () => {
393
- Observer.set( arr, 0, 'item0' ); // Array [ 'item0' ]
394
- await somePromise();
395
- Observer.set( arr, 2, 'item2' ); // Array [ 'item0', <1 empty slot>, 'item2' ]
396
- } );
397
- ```
398
-
399
- > Method calls on a proxied instance - e.g. `Object.proxy( arr ).splice( 0 )` - also follow this strategy.
400
-
401
- ### Method: `Observer.intercept()`
402
-
403
- Intercept operations on any object or array before they happen! This helps you extend standard operations on an object - `Observer.set()`, `Observer.deleteProperty()`, etc - using Proxy-like traps.
404
-
405
- └ *Below, we intercept all "set" operations for an HTTP URL then transform it to an HTTPS URL.*
406
-
407
- ```js
408
- const setTrap = ( operation, previous, next ) => {
409
- if ( operation.key === 'url' && operation.value.startsWith( 'http:' ) ) {
410
- operation.value = operation.value.replace( 'http:', 'https:' );
411
- }
412
- return next();
413
- };
414
- Observer.intercept( obj, 'set', setTrap );
415
- ```
416
-
417
- └ *Now, only the first of the following will fly as-is.*
418
-
419
- ```js
420
- // Not transformed
421
- Observer.set( obj, 'url', 'https://webqit.io' );
422
-
423
- // Transformed
424
- Observer.set( obj, 'url', 'http://webqit.io' );
425
- ```
426
-
427
- └ *And below, we intercept all "get" operations for a certain value to trigger a network fetch behind the scenes.*
428
-
429
- ```js
430
- const getTrap = ( operation, previous, next ) => {
431
- if ( operation.key === 'token' ) {
432
- return next( fetch( tokenUrl ) );
433
- }
434
- return next();
435
- };
436
- Observer.intercept( obj, 'get', getTrap );
437
- ```
438
-
439
- └ *And all of that can go into one "traps" object:*
440
-
441
- ```js
442
- Observer.intercept( obj, {
443
- get: getTrap,
444
- set: setTrap,
445
- deleteProperty: deletePropertyTrap,
446
- defineProperty: definePropertyTrap,
447
- ownKeys: ownKeysTrap,
448
- has: hasTrap,
449
- // etc
450
- } );
451
- ```
452
-
453
- ## Documentation
454
-
455
- Visit the [docs](https://github.com/webqit/observer/wiki) for full details - including [Reflect API Supersets](https://github.com/webqit/observer/wiki#featuring-reflect-api-supersets), [Timing and Batching](https://github.com/webqit/observer/wiki#timing-and-batching), [API Reference](https://github.com/webqit/observer/wiki#putting-it-all-together), etc.
456
-
457
- ## The Polyfill
458
-
459
- The Observer API is being developed as something to be used today - via a polyfill. The polyfill features all of what's documented - with limitations in the area of making mutations: you can only make mutations using the [Mutation APIs](#concept-mutation-apis).
460
-
461
- <details><summary>Load from a CDN</summary>
462
-
463
- ```html
464
- <script src="https://unpkg.com/@webqit/observer/dist/main.js"></script>
465
- ```
466
-
467
- > `4.4` kB min + gz | `13.9` KB min [↗](https://bundlephobia.com/package/@webqit/observer)
468
-
469
- ```js
470
- // Obtain the APIs
471
- const Observer = window.webqit.Observer;
472
- ```
473
-
474
- </details>
475
-
476
- <details><summary>Install from NPM</summary>
477
-
478
- ```bash
479
- npm i @webqit/observer
480
- ```
481
-
482
- ```js
483
- // Import
484
- import Observer from '@webqit/observer';;
485
- ```
486
-
487
- </details>
488
-
489
- ## Getting Involved
490
-
491
- All forms of contributions are welcome at this time. For example, implementation details are all up for discussion. And here are specific links:
492
-
493
- + [Project](https://github.com/webqit/observer)
494
- + [Documentation](https://github.com/webqit/observer/wiki)
495
- + [Discusions](https://github.com/webqit/observer/discussions)
496
- + [Issues](https://github.com/webqit/observer/issues)
497
-
498
- ## License
499
-
500
- MIT.