@webqit/observer 2.0.7 → 2.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/.gitignore CHANGED
@@ -1,3 +1,3 @@
1
- .*
2
- !/.gitignore
3
- node_modules
1
+ .*
2
+ !/.gitignore
3
+ node_modules
package/LICENSE CHANGED
@@ -1,21 +1,21 @@
1
- The MIT License (MIT)
2
-
3
- Copyright (c) 2013-present, Yuxi (Evan) You
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in
13
- all copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2013-present, Yuxi (Evan) You
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
21
  THE SOFTWARE.
package/README.md CHANGED
@@ -1,202 +1,416 @@
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
- # Overview
10
-
11
- > **Note**
12
- > <br>Major updates coming in this branch!
13
-
14
- Take a one-minute rundown of the Observer API.
15
-
16
- ## Observe
17
-
18
- Observe operations on any object or array...
19
-
20
- ```js
21
- let obj = {};
22
- ```
23
-
24
- ...using the [`Observer.observe()`](https://webqit.io/tooling/observer/docs/api/reactions/observe) method.
25
-
26
- ```js
27
- Observer.observe(obj, mutations => {
28
- mutations.forEach(mutation => {
29
- console.log(mutation.type, mutation.name, mutation.path, mutation.value, mutation.oldValue);
30
- });
31
- });
32
- ```
33
-
34
- Now changes will be delivered *synchronously* - as they happen.
35
-
36
- ## Mutate
37
-
38
- Programmatically make *reactive* changes using the *Reflect-like* [set of operators](https://webqit.io/tooling/observer/docs/api/actions)...
39
-
40
- ```js
41
- // A single set operation
42
- Observer.set(obj, 'prop1', 'value1');
43
- ```
44
- ```js
45
- // A batch set operation
46
- Observer.set(obj, {
47
- prop2: 'value2',
48
- prop3: 'value3',
49
- });
50
- ```
51
-
52
- ...or switch to using *object accessors* - using the [`Observer.accessorize()`](https://webqit.io/tooling/observer/docs/api/actors/accessorize) method...
53
-
54
- ```js
55
- // Accessorize all (existing) properties
56
- Observer.accessorize(obj);
57
- // Accessorize specific properties (existing or new)
58
- Observer.accessorize(obj, ['prop1', 'prop5', 'prop9']);
59
- ```
60
- ```js
61
- // Make reactive operations
62
- obj.prop1 = 'value1';
63
- obj.prop5 = 'value5';
64
- obj.prop9 = 'value9';
65
- ```
66
-
67
- ...or even go with a *reactive Proxy* of your object, and imply properties on the fly.
68
-
69
- ```js
70
- // Obtain a reactive Proxy
71
- let _obj = Observer.proxy(obj);
72
- ```
73
- ```js
74
- // Make reactive operations
75
- _obj.prop1 = 'value1';
76
- _obj.prop4 = 'value4';
77
- _obj.prop8 = 'value8';
78
- ```
79
-
80
- And no problem if you inadvertently cascade the approaches. No bizzare behaviours.
81
-
82
- ```js
83
- // Accessorized properties are already reactive
84
- Observer.accessorize(obj, ['prop1', 'prop6', 'prop10']);
85
-
86
- // But no problem if you inadvertently proxy an accessorized object
87
- let _obj = Observer.proxy(obj);
88
-
89
- // And yet no problem if you inadvertently made a programmatic call over an already reactive Proxy
90
- Observer.set(_obj, 'prop1', 'value1');
91
- ```
92
-
93
- ## Intercept
94
-
95
- How about some level of indirection - the ability to hook into operators like `Observer.set()` and `Observer.deleteProperty()` to repurpose their operation? That's all possible using the [`Observer.intercept()`](https://webqit.io/tooling/observer/docs/api/reactions/intercept) method!
96
-
97
- Below, we catch any attempt to set an HTTP URL and force it to an HTTPS URL.
98
-
99
- ```js
100
- Observer.intercept(obj, 'set', (action, previous, next) => {
101
- if (action.name === 'url' && action.value.startsWith('http:')) {
102
- return next(action.value.replace('http:', 'https:'));
103
- }
104
- return next();
105
- });
106
- ```
107
-
108
- Now, only the first of the following will fly as-is.
109
-
110
- ```js
111
- Observer.set(obj, 'url', 'https://webqit.io');
112
- ```
113
- ```js
114
- Observer.set(obj, 'url', 'http://webqit.io');
115
- ```
116
-
117
- ## Pass Some Detail to Observers
118
-
119
- Operators, like `Observer.set()`, can pass arbitrary value to observers via a `params.detail` property.
120
-
121
- ```js
122
- // A set operation with detail
123
- Observer.set(obj, {
124
- prop2: 'value2',
125
- prop3: 'value3',
126
- }, { detail: 'Certain detail' });
127
- ```
128
-
129
- Observers will recieve this value in a `mutation.detail` property.
130
-
131
- ```js
132
- // An observer with detail
133
- Observer.observe(obj, 'prop1', mutation => {
134
- console.log('An operation has been made with detail:' + mutation.detail);
135
- });
136
- ```
137
-
138
- ## Negotiate with Observers
139
-
140
- Observers can access and *act* on a special object called the *Response Object*.
141
-
142
- ```js
143
- // An observer and the response object
144
- Observer.observe(obj, 'prop1', (mutation, event) => {
145
- if (1) {
146
- event.preventDefault(); // Or return false
147
- } else if (2) {
148
- event.stopPropagation(); // Or return false
149
- } else if (3) {
150
- event.waitUntil(new Promise); // Or return new Promise
151
- }
152
- });
153
- ```
154
-
155
- Operators can access and honour the event's state.
156
-
157
- ```js
158
- // A set operation that returns the eventTypeReturn
159
- let event = Observer.set(obj, {
160
- prop2: 'value2',
161
- prop3: 'value3',
162
- }, { eventTypeReturn: true });
163
- ```
164
-
165
- ```js
166
- if (event.defaultPrevented) {
167
- // event.preventDefault() was called
168
- } else if (event.propagationStopped) {
169
- // event.stopPropagation() was called
170
- } else if (event.promises) {
171
- // event.waitUntil() was called
172
- event.promises.then(() => {
173
-
174
- });
175
- }
176
- ```
177
-
178
- *Learn more about negotiation [here](https://webqit.io/tooling/observer/docs/api/core/Event#negotiating-with-operators)*
179
-
180
- ## Clean Up Anytime
181
-
182
- Need to undo certain bindings? There are the methods for that!
183
-
184
- + [`Observer.unobserve()`](https://webqit.io/tooling/observer/docs/api/reactions/unobserve)
185
- + [`Observer.unintercept()`](https://webqit.io/tooling/observer/docs/api/reactions/unintercept)
186
- + [`Observer.unproxy()`](https://webqit.io/tooling/observer/docs/api/actors/unproxy)
187
- + [`Observer.unaccessorize()`](https://webqit.io/tooling/observer/docs/api/actors/unaccessorize)
188
-
189
- ## The End?
190
-
191
- Certainly not! But this rundown should be a good start. Next:
192
-
193
- + Visit the [download](https://webqit.io/tooling/observer/docs/getting-started/download) page to obtain the Observer API.
194
- + Explore the [API Reference](https://webqit.io/tooling/observer/docs/api).
195
-
196
- ## Issues
197
-
198
- To report bugs or request features, please submit an [issue](https://github.com/webqit/observer/issues).
199
-
200
- ## License
201
-
202
- MIT.
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
+ # Overview
10
+
11
+ > **Note**
12
+ > <br>This is documentation for `Observer@2.x`. (Looking for [`Observer@1.x`](https://github.com/webqit/observer/tree/v1.7.6)?)
13
+
14
+ ## Table of Contents
15
+
16
+ + [Download Options](#download-options)
17
+ + [Overview](#overview)
18
+ + [Method: `Observer.observe()`](#method-observerobserve)
19
+ + [Concept: *Observers*](#concept-observers)
20
+ + [Concept: *Mutations*](#concept-mutations)
21
+ + [Concept: *Batch Mutations*](#concept-batch-mutations)
22
+ + [Concept: *Custom Details*](#concept-custom-details)
23
+ + [Concept: *Diffing*](#concept-diffing)
24
+ + [Method: `Observer.intercept()`](#method-observerintercept)
25
+ + [Concept: *Traps*](#concept-traps)
26
+ + [Issues](#issues)
27
+ + [License](#license)
28
+
29
+ ## Download Options
30
+
31
+ **_Use as an npm package:_**
32
+
33
+ ```bash
34
+ npm i @webqit/observer
35
+ ```
36
+
37
+ ```js
38
+ // Import
39
+ import Observer from '@webqit/observer';;
40
+ ```
41
+
42
+ **_Use as a script:_**
43
+
44
+ ```html
45
+ <script src="https://unpkg.com/@webqit/observer/dist/main.js"></script>
46
+ ```
47
+
48
+ ```js
49
+ // Obtain the APIs
50
+ const Observer = window.webqit.Observer;
51
+ ```
52
+
53
+ ## Method: `Observer.observe()`
54
+
55
+ Observe mutations on any object or array!
56
+
57
+ ```js
58
+ // Signature 1
59
+ Observer.observe( obj, callback[, options = {} ]);
60
+ ```
61
+
62
+ ```js
63
+ // Signature 2
64
+ Observer.observe( obj, props, callback[, options = {} ]);
65
+ ```
66
+
67
+ ### Concept: *Observers*
68
+
69
+ Observe arbitrary objects and arrays:
70
+
71
+ ```js
72
+ // An object
73
+ const obj = {};
74
+ // Mtation observer on an object
75
+ const abortController = Observer.observe( obj, handleChanges );
76
+ ```
77
+
78
+ ```js
79
+ // An array
80
+ const arr = [];
81
+ // Mtation observer on an array
82
+ const abortController = Observer.observe( arr, handleChanges );
83
+ ```
84
+
85
+ *Now changes will be delivered **synchronously** - as they happen. (The *sync* design is discussed shortly.)*
86
+
87
+ ```js
88
+ // The change handler
89
+ function handleChanges( mutations ) {
90
+ mutations.forEach( mutation => {
91
+ console.log( mutation.type, mutation.key, mutation.value, mutation.oldValue );
92
+ } );
93
+ }
94
+ ```
95
+
96
+ **-->** Stop observing at any time by calling `abort()` on the returned *abortController*...
97
+
98
+ ```js
99
+ // Remove listener
100
+ abortController.abort();
101
+ ```
102
+
103
+ ...or by using an [Abort Signal](https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal) instance:
104
+
105
+ ```js
106
+ // Providing an AbortSignal
107
+ const abortController = new AbortController;
108
+ Observer.observe( obj, mutations => {
109
+ // Handle...
110
+ }, { signal: abortController.signal } );
111
+ ```
112
+
113
+ ```js
114
+ // Abort at any time
115
+ abortController.abort();
116
+ ```
117
+
118
+ ### Concept: *Mutations*
119
+
120
+ 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:
121
+
122
+ ```js
123
+ // A single "set" operation on an object
124
+ Observer.set( obj, 'prop0', 'value0' );
125
+ Observer.defineProperty( obj, 'prop1', { get: () => 'value1' } );
126
+ Observer.deleteProperty( obj, 'prop2' );
127
+ ```
128
+
129
+ ```js
130
+ // A single "set" operation on an array
131
+ Observer.set( arr, 0, 'item0' ); // Array [ 'item0' ]
132
+ Observer.deleteProperty( arr, 0 ); // Array [ <1 empty slot> ]
133
+ ```
134
+
135
+ *Beware non-reactive operations*:
136
+
137
+ ```js
138
+ // Literal object accessors
139
+ delete obj.prop0;
140
+ obj.prop3 = 'value3';
141
+ ```
142
+
143
+ ```js
144
+ // Array methods
145
+ arr.push( 'item3' );
146
+ arr.pop();
147
+ ```
148
+
149
+ **-->** Enable reactivity on *specific* properties with literal *object accessors* - using the `Observer.accessorize()` method:
150
+
151
+ ```js
152
+ // Accessorize all (existing) properties
153
+ Observer.accessorize( obj );
154
+ // Accessorize specific properties (existing or new)
155
+ Observer.accessorize( obj, [ 'prop0', 'prop1', 'prop2' ] );
156
+
157
+ // Make reactive UPDATES
158
+ obj.prop0 = 'value0';
159
+ obj.prop1 = 'value1';
160
+ obj.prop2 = 'value2';
161
+ ```
162
+
163
+ ```js
164
+ // Accessorize all (existing) indexes
165
+ Observer.accessorize( arr );
166
+ // Accessorize specific indexes (existing or new)
167
+ Observer.accessorize( arr, [ 0, 1, 2 ] );
168
+
169
+ // Make reactive UPDATES
170
+ arr[ 0 ] = 'item0';
171
+ arr[ 1 ] = 'item1';
172
+ arr[ 2 ] = 'item2';
173
+
174
+ // Bonus reactivity: array methods that re-index existing items
175
+ arr.unshift( 'new-item0' );
176
+ arr.shift();
177
+ ```
178
+
179
+ *Beware non-reactive operations*:
180
+
181
+ ```js
182
+ // The delete operator and object properties that haven't been accessorized
183
+ delete obj.prop0;
184
+ obj.prop3 = 'value3';
185
+ ```
186
+
187
+ ```js
188
+ // Array methods that do not re-index existing items
189
+ arr.push( 'item0' );
190
+ arr.pop();
191
+ ```
192
+
193
+ **-->** Enable reactivity on *arbitray* properties with *Proxies* - using the `Observer.proxy()` method:
194
+
195
+ ```js
196
+ // Obtain a reactive Proxy for an object
197
+ const _obj = Observer.proxy( obj );
198
+
199
+ // Make reactive operations
200
+ _obj.prop1 = 'value1';
201
+ _obj.prop4 = 'value4';
202
+ _obj.prop8 = 'value8';
203
+
204
+ // With the delete operator
205
+ delete _obj.prop0;
206
+ ```
207
+
208
+ ```js
209
+ // Obtain a reactive Proxy for an array
210
+ const _arr = Observer.proxy( arr );
211
+
212
+ // Make reactive operations
213
+ _arr[ 0 ] = 'item0';
214
+ _arr[ 1 ] = 'item1';
215
+ _arr[ 2 ] = 'item2';
216
+
217
+ // With an instance method
218
+ _arr.push( 'item3' );
219
+ ```
220
+
221
+ *And no problem if you end up nesting the approaches.*
222
+
223
+ ```js
224
+ // 'value1'-->obj
225
+ Observer.accessorize( obj, [ 'prop0', 'prop1', 'prop2', ] );
226
+ obj.prop1 = 'value1';
227
+
228
+ // 'value1'-->_obj-->obj
229
+ let _obj = Observer.proxy( obj );
230
+ _obj.prop1 = 'value1';
231
+
232
+ // 'value1'-->set()-->_obj-->obj
233
+ Observer.set( _obj, 'prop1', 'value1' );
234
+ ```
235
+
236
+ **-->** "Restore" accessorized properties to normal by calling `unaccessorize()`:
237
+
238
+ ```js
239
+ Observer.unaccessorize( obj, [ 'prop1', 'prop6', 'prop10' ] );
240
+ ```
241
+
242
+ **-->** "Reproduce" original objects from Proxies obtained via `Observer.proxy()` by using the `unproxy()` method:
243
+
244
+ ```js
245
+ obj = Observer.unproxy( _obj );
246
+ ```
247
+
248
+ ### Concept: *Batch Mutations*
249
+
250
+ Make multiple mutations at a go, and they'll be correctly delivered in batch to observers!
251
+
252
+ ```js
253
+ // Batch operations on an object
254
+ Observer.set( obj, {
255
+ prop0: 'value0',
256
+ prop1: 'value1',
257
+ prop2: 'value2',
258
+ } );
259
+ Observer.defineProperties( obj, {
260
+ prop0: { value: 'value0' },
261
+ prop1: { value: 'value1' },
262
+ prop2: { get: () => 'value2' },
263
+ } );
264
+ Observer.deleteProperties( obj, [ 'prop0', 'prop1', 'prop2' ] );
265
+ ```
266
+
267
+ ```js
268
+ // Batch operations on an array
269
+ Observer.set( arr, {
270
+ '0': 'item0',
271
+ '1': 'item1',
272
+ '2': 'item2',
273
+ } );
274
+ Object.proxy( arr ).push( 'item3', 'item4', 'item5', );
275
+ Object.proxy( arr ).unshift( 'new-item0' );
276
+ Object.proxy( arr ).splice( 0 );
277
+ ```
278
+
279
+ **-->** Use the `Observer.batch()` to batch multiple arbitrary mutations - whether related or not:
280
+
281
+ ```js
282
+ Observer.batch( arr, async () => {
283
+ Observer.set( arr, 0, 'item0' ); // Array [ 'item0' ]
284
+ await somePromise();
285
+ Observer.set( arr, 2, 'item2' ); // Array [ 'item0', <1 empty slot>, 'item2' ]
286
+ } );
287
+ ```
288
+
289
+ > Method calls on a proxied instance - e.g. `Object.proxy( arr ).splice( 0 )` - also follow this strategy.
290
+
291
+ ### Concept: *Custom Details*
292
+
293
+ Pass some custom detail - an arbitrary value - to observers via a `params.detail` property.
294
+
295
+ ```js
296
+ // A set operation with detail
297
+ Observer.set( obj, {
298
+ prop2: 'value2',
299
+ prop3: 'value3',
300
+ }, { detail: 'Certain detail' } );
301
+ ```
302
+
303
+ *Observers recieve this value on their `mutation.detail` property.*
304
+
305
+ ```js
306
+ // An observer with detail
307
+ Observer.observe( obj, 'prop1', mutation => {
308
+ console.log( 'A mutation has been made with detail:' + mutation.detail );
309
+ } );
310
+ ```
311
+
312
+ ### Concept: *Diffing*
313
+
314
+ Receive notifications only for mutations that actually change property state, and ignore those that don't.
315
+
316
+ ```js
317
+ // Responding to state changes only
318
+ Observer.observe( obj, handleChanges, { diff: true } );
319
+ ```
320
+
321
+ ```js
322
+ // Recieved
323
+ Observer.set( obj, 'prop0', 'value' );
324
+ ```
325
+
326
+ ```js
327
+ // Ignored
328
+ Observer.set( obj, 'prop0', 'value' );
329
+ ```
330
+
331
+ <!--
332
+ ### Concept: *Live*
333
+ descripted
334
+ namespace
335
+ -->
336
+
337
+ ## Method: `Observer.intercept()`
338
+
339
+ Intercept operations on any object or array before they happen!
340
+
341
+ ```js
342
+ // Signature 1
343
+ Observer.intercept( obj, prop, handler[, options = {} ]);
344
+ ```
345
+
346
+ ```js
347
+ // Signature 2
348
+ Observer.intercept( obj, traps[, options = {} ]);
349
+ ```
350
+
351
+ ### Concept: *Traps*
352
+
353
+ 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!
354
+
355
+ *Below, we intercept all "set" operations for an HTTP URL then transform it to an HTTPS URL.*
356
+
357
+ ```js
358
+ const setTrap = ( operation, previous, next ) => {
359
+ if ( operation.key === 'url' && operation.value.startsWith( 'http:' ) ) {
360
+ operation.value = operation.value.replace( 'http:', 'https:' );
361
+ }
362
+ return next();
363
+ };
364
+ Observer.intercept( obj, 'set', setTrap );
365
+ ```
366
+
367
+ *Now, only the first of the following will fly as-is.*
368
+
369
+ ```js
370
+ // Not transformed
371
+ Observer.set( obj, 'url', 'https://webqit.io' );
372
+
373
+ // Transformed
374
+ Observer.set( obj, 'url', 'http://webqit.io' );
375
+ ```
376
+
377
+ *And below, we intercept all "get" operations for a certain value to trigger a network fetch behind the scenes.*
378
+
379
+ ```js
380
+ const getTrap = ( operation, previous, next ) => {
381
+ if ( operation.key === 'token' ) {
382
+ return next( fetch( tokenUrl ) );
383
+ }
384
+ return next();
385
+ };
386
+ Observer.intercept( obj, 'get', getTrap );
387
+ ```
388
+
389
+ *And all of that can go into one "traps" object:*
390
+
391
+ ```js
392
+ Observer.intercept( obj, {
393
+ get: getTrap,
394
+ set: setTrap,
395
+ deleteProperty: deletePropertyTrap,
396
+ defineProperty: definePropertyTrap,
397
+ ownKeys: ownKeysTrap,
398
+ has: hasTrap,
399
+ // etc
400
+ } );
401
+ ```
402
+
403
+ ## The End?
404
+
405
+ Certainly not! But this rundown should be a good start. Next:
406
+
407
+ + Visit the [download](https://webqit.io/tooling/observer/docs/getting-started/download) page to obtain the Observer API.
408
+ + Explore the [API Reference](https://webqit.io/tooling/observer/docs/api).
409
+
410
+ ## Issues
411
+
412
+ To report bugs or request features, please submit an [issue](https://github.com/webqit/observer/issues).
413
+
414
+ ## License
415
+
416
+ MIT.