@webqit/observer 3.8.3 → 3.8.5

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 CHANGED
@@ -1,10 +1,14 @@
1
- # Observer
1
+ <div align="center">
2
+
3
+ # The Observer API
2
4
 
3
5
  [![npm version][npm-version-src]][npm-version-href]
4
6
  [![npm downloads][npm-downloads-src]][npm-downloads-href]
5
7
  [![bundle][bundle-src]][bundle-href]
6
8
  [![License][license-src]][license-href]
7
9
 
10
+ </div>
11
+
8
12
  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 unifies that with related APIs like Reflect and Proxy "traps"!
9
13
 
10
14
  The Observer API comes as one little API for all things _object observability_. (Only `~5.8KiB min|zip`)
@@ -172,16 +176,16 @@ Observer.set(user, 'email', 'JOHN@EXAMPLE.COM'); // Becomes 'john@example.com'
172
176
  ## Key Features
173
177
 
174
178
  ### **Core Reactivity**
175
- - **🔄 Real-time Observation**: Watch object and array changes as they happen
176
- - **⚡ Synchronous Updates**: Changes are delivered immediately, not batched
177
- - **🎯 Granular Control**: Watch specific properties, paths, or entire objects
178
- - **🌳 Deep Path Watching**: Observe nested properties with wildcards and subtrees
179
+ - **🔄 Real-time Observability**: Watch object and array changes as they happen
180
+ - **⚡ Synchronous Updates**: Changes are delivered synchronously, not batched
181
+ - **🎯 Granular Control**: Watch specific properties, paths; even wildcards
182
+ - **🌳 Deep Path Watching**: Observe nested properties or entire object tree
179
183
 
180
184
  ### **Advanced Capabilities**
181
185
  - **🛡️ Operation Interception**: Transform, validate, or block operations before execution
182
- - **📦 Atomic Batching**: Group multiple changes into single events
183
- - **🔄 Object Mirroring**: Create reactive synchronization between objects
184
186
  - **🔗 Traps Pipeline**: Compose multiple interceptors for complex behavior
187
+ - **📦 Atomic Batching**: Batch multiple changes into single atomic operation
188
+ - **🔄 Object Mirroring**: Create reactive synchronization between objects
185
189
 
186
190
  ### **Developer Experience**
187
191
  - **🔧 Utility-First API**: Clean, functional design with consistent patterns
@@ -191,19 +195,19 @@ Observer.set(user, 'email', 'JOHN@EXAMPLE.COM'); // Becomes 'john@example.com'
191
195
 
192
196
  ## Ecosystem Integrations
193
197
 
194
- The Observer API is enabling a shared protocol of mutation-based reactivity across the ecosystem:
198
+ The Observer API is enabling a shared protocol for *mutation-based* reactivity across the ecosystem:
195
199
 
196
200
  ### **🚀 [Quantum Runtime](https://github.com/webqit/quantum-js)**
197
201
  Uses Observer API under the hood to operate as a **full-fledged reactive runtime**. Quantum enables Imperative Reactive Programming by leveraging Observer's reactivity foundation to make ordinary JavaScript code reactive.
198
202
 
199
203
  ### **🌐 [OOHTML](https://github.com/webqit/oohtml)**
200
- Uses Observer API to underpin **reactive HTML templating**. OOHTML leverages Observer's reactivity layer to create dynamic, data-driven templates with seamless integration between data and presentation.
204
+ Uses Observer API to underpin **dynamic, reactive UIs**. OOHTML enables live data binding between UI and app state, automatically updating the DOM when your data changes.
201
205
 
202
206
  ### **⚡ [Webflo](https://github.com/webqit/webflo)**
203
- Uses Observer API to underpin **Live Objects** as a first-class concept. Webflo leverages Observer's reactivity foundation to enable real-time data synchronization across client and server.
207
+ Uses Observer API to underpin **Live Objects** as a first-class concept. Live Objects in Webflo lets you send dynamic state from your server to the UI with reactivity over the wire.
204
208
 
205
209
  ### **🔗 [LinkedQL](https://github.com/linked-db/linked-ql)**
206
- Uses Observer API to underpin **Live Objects** as a first-class concept. LinkedQL leverages Observer's reactivity foundation to provide reactive database operations with real-time data synchronization.
210
+ Uses Observer API to underpin **Live Objects** as a first-class concept. Live Objects in LinkedQL lets you have query results as self-updating result sets.
207
211
 
208
212
  ## API Reference
209
213
 
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.5",
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.