statebus 7.1.2 → 7.2.3

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 (3) hide show
  1. package/extras/tests.js +162 -2
  2. package/package.json +1 -1
  3. package/statebus.js +204 -57
package/extras/tests.js CHANGED
@@ -81,12 +81,13 @@ test(function validation (done) {
81
81
  [{a: false}, {a: 'boolean'}, true],
82
82
  [{a: 1, b: 2}, {a: 1}, false],
83
83
  [{a: 1, b: 2}, {a: 1, '*':'*'}, true],
84
- [{a: 2, b: 2}, {a: 1, '*':'*'}, false]
84
+ [{a: 2, b: 2}, {a: 1, '*':'*'}, false],
85
+ [{a: {link: "foo"}}, {a: 'link'}, true]
85
86
  ]
86
87
 
87
88
  for (var i=0; i<v_tests.length; i++)
88
89
  assert(bus.validate(v_tests[i][0], v_tests[i][1]) === v_tests[i][2],
89
- 'Validation test failed', v_tests[i])
90
+ 'Validation test failed: ' + i + ' ' + JSON.stringify(v_tests[i]))
90
91
 
91
92
  done()
92
93
  })
@@ -319,9 +320,82 @@ test(function proxy_link (done) {
319
320
  done()
320
321
  })
321
322
 
323
+ function verify_no_proxies (bus) {
324
+ bus.deep_map(bus.cache, o => {
325
+ if ((typeof o === 'object' || typeof o === 'function')
326
+ && o[bus.symbols.is_proxy]) {
327
+ console.trace('NOOOO proxy is here', obj)
328
+ throw 'We got a bad one!!!'
329
+ }
330
+ return o
331
+ })
332
+ }
333
+
322
334
  // Disable the version of proxy_links_through where the user does not have to
323
335
  // explicitly traverse links with ._()
324
336
  if (true)
337
+ test(function proxy_links_through3 (done) {
338
+ var bus = require('../statebus')()
339
+
340
+ // First try to dereference through three links
341
+
342
+ bus.state.a = [bus.link('b')]
343
+ bus.state.b = bus.link('c')
344
+ bus.state.c = 'see'
345
+
346
+ verify_no_proxies(bus)
347
+
348
+ log('state.c is', bus.state.c)
349
+ log('state.b is', bus.state.b, 'from cache', bus.cache.b)
350
+ log('state.a is', bus.state.a)
351
+ log('state.a[0] is', bus.state.a[0])
352
+ log('state.a[0]() is', bus.state.a[0]())
353
+ log('state.a[0]()() is', bus.state.a[0]()())
354
+ assert(bus.state.a[0]()() === 'see')
355
+
356
+ verify_no_proxies(bus)
357
+
358
+ // Add a little more wrapping and try some more
359
+
360
+ bus.state.nested = [99, {a: bus.link('a')}]
361
+ log('nested is', bus.state.nested, bus.state.nested[bus.symbols.is_proxy])
362
+ log('raw nested is', bus.state[bus.symbols.raw].nested)
363
+ log('raw cache is', bus.cache.nested)
364
+
365
+ log('nested[1].a()[0]()() is', bus.state.nested[1].a()[0]()())
366
+ assert(bus.state.nested[1].a()[0]()() === 'see')
367
+
368
+ verify_no_proxies(bus)
369
+
370
+ // Now try to go raw at the top-level
371
+
372
+ log('Raw is', bus.state[bus.symbols.raw].nested)
373
+ log('Raw is', bus.raw(bus.state).nested)
374
+ assert(bus.deep_equals(
375
+ bus.state[bus.symbols.raw].nested,
376
+ bus.raw(bus.state).nested
377
+ ))
378
+
379
+ verify_no_proxies(bus)
380
+
381
+ log('Gonna deep_quals between:',
382
+ bus.raw(bus.state).nested.val,
383
+ [ 99, { a: { link: 'a' } } ])
384
+
385
+ assert(bus.deep_equals(
386
+ bus.raw(bus.state).nested.val,
387
+ [ 99, { a: { link: 'a' } } ]
388
+ ))
389
+
390
+ // Now try to go raw within
391
+
392
+ log('Raw inner is', bus.raw(bus.state.nested[1]))
393
+
394
+ log('Bad raw call is', bus.raw(bus.state.nested[0]))
395
+
396
+ done()
397
+ })
398
+ else if (false)
325
399
  test(function proxy_links_through2 (done) {
326
400
  var bus = require('../statebus')()
327
401
 
@@ -446,6 +520,90 @@ test(function copy_link (done) {
446
520
  })
447
521
 
448
522
 
523
+
524
+ // This was useful test code for figuring out how to set custom formatters on
525
+ // proxies. It's not needed anymore.
526
+ //
527
+ // test(function proxy_printing_in_node (done) {
528
+ // var bus = require('../statebus')(), state = bus.state
529
+ //
530
+ // state.a = [bus.link('b')]
531
+ // state.b = 3
532
+ // state.c = {a: 3, link: 99}
533
+ //
534
+ // log('a in cache is', bus.cache.a,
535
+ // {'typeof': typeof bus.cache.a === 'object',
536
+ // symbolsin: bus.symbols.link in bus.cache.a})
537
+ //
538
+ // log('state:', state)
539
+ // log('state.a:', state.a)
540
+ // log('state.b:', state.b)
541
+ // log('state.c:', state.c)
542
+ //
543
+ // var util = require('util')
544
+ //
545
+ // assert(util.inspect(state.a) === "Proxy [ { link: 'b' } ]")
546
+ // assert(util.inspect(state.b) === "3")
547
+ // assert(util.inspect(state.c) === "Proxy { a: 3, link: 99 }")
548
+ //
549
+ // log('passed!')
550
+ //
551
+ // // This version is abstracted and tested:
552
+ // var util = require('util')
553
+ // function make_proxy(target) {
554
+ // // Don't proxy null or non-objects
555
+ // if (!target || typeof target !== 'object') return target
556
+ //
557
+ // // Create proxies for all nested objects
558
+ // var clean = Array.isArray(target) ? [] : {}
559
+ // for (let prop of Object.getOwnPropertyNames(target)) {
560
+ // clean[prop] = make_proxy(target[prop])
561
+ // }
562
+ //
563
+ // // Create the proxy
564
+ // var proxy = new Proxy(clean, {
565
+ // get(target, prop) {
566
+ // return target[prop]
567
+ // }
568
+ // })
569
+ //
570
+ // // Add custom inspector
571
+ // proxy[util.inspect.custom] = function(depth, opts) {
572
+ // var formatted = Array.isArray(target) ? [] : {}
573
+ // for (let prop of Object.getOwnPropertyNames(target)) {
574
+ // formatted[prop] = target[prop]
575
+ // }
576
+ // return `Proxy ${util.inspect(formatted, { ...opts, customInspect: true })}`
577
+ // }
578
+ //
579
+ // return proxy
580
+ // }
581
+ //
582
+ // // Test various scenarios
583
+ // var data = {
584
+ // name: 'parent',
585
+ // child: {
586
+ // name: 'kid',
587
+ // toy: {
588
+ // name: 'teddy'
589
+ // }
590
+ // },
591
+ // numbers: [1, 2, {x: 3}], // Test with array containing object
592
+ // nullVal: null, // Test with null
593
+ // primitive: 42 // Test with primitive
594
+ // }
595
+ //
596
+ // var proxied = make_proxy(data)
597
+ // console.log('Full object:', proxied)
598
+ // console.log('Child:', proxied.child)
599
+ // console.log('Toy:', proxied.child.toy)
600
+ // console.log('Numbers:', proxied.numbers)
601
+ // console.log('Null value:', proxied.nullVal)
602
+ // console.log('Primitive:', proxied.primitive)
603
+ //
604
+ // done()
605
+ // })
606
+
449
607
  test(function translate_fields (done) {
450
608
  // Translate Statebus -> Proxy format
451
609
  var tests1 = [
@@ -907,6 +1065,8 @@ test(function proxies (done) {
907
1065
  var bus = require('../statebus').serve({file_store: false})
908
1066
  var state = bus.state
909
1067
 
1068
+ log('Top-level proxy looks like:', bus.state)
1069
+
910
1070
  assert(state.array === undefined)
911
1071
  assert(state.bar === undefined)
912
1072
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "statebus",
3
- "version": "7.1.2",
3
+ "version": "7.2.3",
4
4
  "description": "Synchronize state.",
5
5
  "main": "statebus.js",
6
6
  "scripts": {
package/statebus.js CHANGED
@@ -1456,20 +1456,26 @@
1456
1456
  link: Symbol('link')
1457
1457
  }
1458
1458
 
1459
+ if (nodejs)
1460
+ var util = require('util')
1461
+
1459
1462
  // The top-level Proxy object holds HTTP resources
1460
1463
  var top_level_proxy = new Proxy(cache, {
1461
1464
  get: function get(o, k) {
1462
1465
  if (k === symbols.is_proxy)
1463
1466
  return true
1464
1467
  if (k === symbols.raw)
1465
- return raw_proxy()
1466
- if (k === 'inspect' || k === 'valueOf' || typeof k === 'symbol')
1467
- return undefined
1468
+ return bus.cache
1469
+ // if (k === 'inspect' || k === 'valueOf' || typeof k === 'symbol')
1470
+ // return undefined
1468
1471
  bogus_check(k)
1469
1472
  var base = bus.get(k)
1470
1473
  return json_proxy(base, '', base.val)
1471
1474
  },
1472
1475
  set: function set(o, key, val) {
1476
+ if (typeof key === 'symbol')
1477
+ return true
1478
+
1473
1479
  bus.set({
1474
1480
  key: key,
1475
1481
  val: escape_json_to_bus(val)
@@ -1495,42 +1501,59 @@
1495
1501
 
1496
1502
  return o
1497
1503
 
1498
- // We recursively descend through {key: ...} links
1504
+ // Then o must be an object, array, or link
1505
+
1506
+ // First, handle links.
1499
1507
  if (typeof o === 'object' && 'link' in o) {
1500
1508
  // var new_base = bus.get(o.link)
1501
1509
  // return json_proxy(new_base, '', new_base.val)
1502
1510
  return link(o.link)
1511
+
1512
+ // Todo: to allow modifying links, or adding new fields to them,
1513
+ // we'll need to treat them more like a json_proxy, that passes
1514
+ // the base into the proxy creator, allowing edits, etc.
1503
1515
  }
1504
1516
 
1517
+ // Now it must be an array or object. Shallow clone it, so we can set
1518
+ // a custom formatter on it
1519
+ var target = o
1520
+ if (Array.isArray(target)) target = [...o]
1521
+ else target = {...o}
1505
1522
 
1506
- // For function proxies:
1507
- //
1508
- // // Javascript won't let us function call a proxy unless the
1509
- // // "target" is a function. So we make a dummy target, and
1510
- // // don't use it.
1511
- // var dummy = function () {}
1523
+ // Now set the custom formatter
1524
+ if (nodejs)
1525
+ target[util.inspect.custom] = function print_as_proxy (depth, opts) {
1526
+ var formatted = Array.isArray(target) ? [] : {}
1527
+ for (let prop of Object.getOwnPropertyNames(target))
1528
+ formatted[prop] = target[prop]
1529
+ formatted = unescape_bus_to_json(formatted)
1512
1530
 
1531
+ return `Proxy ${util.inspect(formatted, { ...opts, customInspect: true })}`
1532
+ }
1513
1533
 
1514
- return new Proxy(o, {
1534
+ var proxy = new Proxy(target, {
1515
1535
  get: function (o, k) {
1516
1536
  if (k === 'inspect' || k === 'valueOf')
1517
1537
  return undefined
1518
- // if (custom_inspect && k === custom_inspect)
1519
- // return custom_inspect
1520
1538
  if (k === symbols.is_proxy)
1521
1539
  return true
1522
1540
  if (k === symbols.raw)
1523
1541
  return o
1524
- if (typeof k === 'symbol')
1525
- return undefined
1526
1542
 
1527
1543
  // Compute the new path
1528
1544
  var new_path = path + '[' + JSON.stringify(k) + ']'
1529
1545
  return json_proxy(base, new_path, o[escape_field_json_to_bus(k)])
1530
1546
  },
1531
- set: function (o, k, v) {
1547
+ set: function (target, k, v) {
1532
1548
  var value = escape_json_to_bus(v)
1533
- o[escape_field_json_to_bus(k)] = value
1549
+ var escaped_key = escape_field_json_to_bus(k)
1550
+
1551
+ // Now mutate both the shallow-copied target and the original
1552
+ // object because we have two because es6 proxies are stupidly
1553
+ // designed and we had to make a copy to make custom inspect
1554
+ // symbol work
1555
+ o[escaped_key] = value
1556
+ target[escaped_key] = value
1534
1557
  var new_path = path + '[' + JSON.stringify(k) + ']'
1535
1558
  bus.set.sync(
1536
1559
  base,
@@ -1541,20 +1564,20 @@
1541
1564
  )
1542
1565
  return true
1543
1566
  },
1544
- has: function (o, k) {
1545
- // if (custom_inspect && k === custom_inspect)
1546
- // return true
1547
- return o.hasOwnProperty(escape_field_json_to_bus(k))
1548
- },
1549
- ownKeys: function () {
1550
- return Object.keys(o).map(unescape_field_bus_to_json)
1551
- },
1552
- getOwnPropertyDescriptor: function (target, key) {
1553
- return { enumerable: true, configurable: true, value: this.get(o, key) }
1554
- },
1555
- deleteProperty: function del (o, k) {
1567
+ // has: function (o, k) {
1568
+ // return o.hasOwnProperty(escape_field_json_to_bus(k))
1569
+ // },
1570
+ // ownKeys: function () {
1571
+ // return Object.keys(target).map(unescape_field_bus_to_json)
1572
+ // },
1573
+ // getOwnPropertyDescriptor: function (target, key) {
1574
+ // return { enumerable: true, configurable: true, value: this.get(target, key) }
1575
+ // },
1576
+ deleteProperty: function del (target, k) {
1556
1577
  var new_path = path + '[' + JSON.stringify(k) + ']'
1557
- delete o[escape_field_json_to_bus(k)]
1578
+ var escaped_key = escape_field_json_to_bus(k)
1579
+ delete o[escaped_key]
1580
+ delete target[escaped_key]
1558
1581
  bus.set(
1559
1582
  base,
1560
1583
  // Forward the patches too
@@ -1563,17 +1586,32 @@
1563
1586
  content: undefined}]}
1564
1587
  )
1565
1588
  return true // Report success to Proxy
1566
- }
1567
- // For function proxies:
1568
- //
1569
- // apply: function apply (o, This, args) {
1570
- // return translate_fields(o, unescape_field_from_bus)
1571
- // }
1589
+ },
1572
1590
  })
1591
+
1592
+ return proxy
1573
1593
  }
1574
1594
 
1575
1595
  bus.state = top_level_proxy
1576
1596
 
1597
+ // This doesn't work because custom inspectors only work when the
1598
+ // util.inspect.custom symbol is defined on the "target" of the
1599
+ // proxy... which right now is `cache` for the top_level_proxy. I could
1600
+ // shallow-copy cache into a new target variable, and add this symbol to
1601
+ // it, and keep them in sync, but that sounds annoying. I'm just going to
1602
+ // ignore the custom proxy formatter on the top-level-proxy for now.
1603
+
1604
+ // if (nodejs) {
1605
+ // var util = require('util')
1606
+ // top_level_proxy[util.inspect.custom] = function(depth, opts) {
1607
+ // var formatted = {}
1608
+ // for (let prop of Object.getOwnPropertyNames(cache)) {
1609
+ // formatted[prop] = cache[prop].val
1610
+ // }
1611
+ // return `STATE ${util.inspect(formatted, { ...opts, customInspect: true })}`
1612
+ // }
1613
+ // }
1614
+
1577
1615
  // This is temporary code to wrap the cache as a flat key/valuel store
1578
1616
  // that returns raw objects, dereferencing .val. We can remove it once we
1579
1617
  // remove the internal .val stuff from statebus.
@@ -1589,26 +1627,29 @@
1589
1627
  })
1590
1628
  }
1591
1629
 
1592
- // How proxy links work right now:
1593
- // - The user creates a link with bus.link(key)
1594
- // - which produces a {[symbols.link]: key}
1595
- // - which when set() into a proxy, is replaced with a {link: key}, to store internally
1596
- // - Then, when get()ing that internal {link: key} through Proxy:
1597
- // - We auto-dereference the key, with another get()
1598
- // - So the user actually sees the value of the resource on the other side of the link
1599
- function link (url) {
1600
- return {
1601
- link: url,
1602
- [symbols.link]: true,
1603
- _: (args, o) => (o = get(url), json_proxy(o, '', o.val))
1604
- }
1605
- }
1606
1630
 
1607
- // // The proxy object for links. Disabled for now.
1608
- // // This type of link has to be function called, as link(), to dereference.
1631
+ // Old link code:
1632
+
1633
+ // // How proxy links work right now:
1634
+ // // - The user creates a link with bus.link(key)
1635
+ // // - which produces a {[symbols.link]: key}
1636
+ // // - which when set() into a proxy, is replaced with a {link: key}, to store internally
1637
+ // // - Then, when get()ing that internal {link: key} through Proxy:
1638
+ // // - We auto-dereference the key, with another get()
1639
+ // // - So the user actually sees the value of the resource on the other side of the link
1640
+ // function link (url) {
1641
+ // return {
1642
+ // link: url,
1643
+ // [symbols.link]: true,
1644
+ // _: (args, o) => (o = get(url), json_proxy(o, '', o.val))
1645
+ // }
1646
+ // }
1647
+
1648
+ // The proxy object for links. Disabled for now.
1649
+ // This type of link has to be function called, as link(), to dereference.
1609
1650
  // function proxy_link (url) {
1610
1651
  // function follow_link () {}
1611
- // return new Proxy(follow_link, {
1652
+ // var proxy = new Proxy(follow_link, {
1612
1653
  // get: function (o, k) {
1613
1654
  // console.log('get', k)
1614
1655
  // if (k === 'inspect' || k === 'valueOf')
@@ -1686,7 +1727,109 @@
1686
1727
  // return top_level_proxy[url]
1687
1728
  // }
1688
1729
  // })
1730
+
1731
+ // proxy[util.inspect.custom] = function(depth, opts) {
1732
+ // var formatted = Array.isArray(o) ? [] : {}
1733
+ // for (let prop of Object.getOwnPropertyNames(o)) {
1734
+ // formatted[prop] = o[prop]
1735
+ // }
1736
+ // return `*${util.inspect({link: url})}`
1737
+ // }
1738
+
1739
+ // return proxy
1740
+
1741
+ // }
1742
+
1743
+ // function proxy_link2 (url) {
1744
+ // var target = {link: url}
1745
+
1746
+ // Object.setPrototypeOf(obj, Function.prototype);
1747
+ // Object.defineProperty(obj, 'toString', {
1748
+ // value: function() { return `{link: "${this.link}"}`; },
1749
+ // writable: true,
1750
+ // configurable: true
1751
+ // })
1752
+
1753
+ // return new Proxy(target, {
1754
+ // apply(target, this_arg, args) {
1755
+ // return top_level_proxy[url]
1756
+ // }
1757
+ // })
1689
1758
  // }
1759
+
1760
+
1761
+ // New link code
1762
+ function link (url) {
1763
+ var target = () => {}
1764
+
1765
+ // Use non-enumerable properties for function stuff
1766
+ Object.defineProperties(target, {
1767
+ // 'length': { value: 0, enumerable: false },
1768
+ 'name': { value: '', enumerable: false },
1769
+ 'prototype': { value: {}, enumerable: false },
1770
+
1771
+ // Make link enumerable
1772
+ 'link': { value: url, enumerable: true, writable: true, configurable: true },
1773
+ 'toString': {
1774
+ value: function() { return `{link: "${this.link}"}` },
1775
+ enumerable: false
1776
+ },
1777
+
1778
+ // Add toJSON method
1779
+ 'toJSON': {
1780
+ value: function() { return { link: this.link } },
1781
+ enumerable: false
1782
+ }
1783
+ })
1784
+
1785
+ var proxy = new Proxy(target, {
1786
+ get: function (o, k) {
1787
+ // console.log('link getting',k)
1788
+ // if (k === 'inspect' || k === 'valueOf')
1789
+ // return undefined
1790
+
1791
+ // if (custom_inspect && k === custom_inspect)
1792
+ // return custom_inspect
1793
+ if (k === symbols.is_proxy)
1794
+ return true
1795
+ if (k === symbols.link)
1796
+ return true
1797
+ if (k === symbols.raw)
1798
+ return {link: url}
1799
+ // if (typeof k === 'symbol')
1800
+ // return undefined
1801
+
1802
+ // if (k === 'link')
1803
+ // return url
1804
+
1805
+ return target[k]
1806
+
1807
+ // return undefined
1808
+
1809
+ },
1810
+ apply(target, this_arg, args) {
1811
+ return top_level_proxy[url]
1812
+ },
1813
+ has: function (o, k) {
1814
+ if (k === symbols.link)
1815
+ return true
1816
+ return Reflect.has(o, k)
1817
+ },
1818
+ })
1819
+
1820
+ if (nodejs)
1821
+ proxy[util.inspect.custom] = function(depth, opts) {
1822
+ var formatted = Array.isArray(target) ? [] : {}
1823
+ for (let prop of Object.getOwnPropertyNames(target)) {
1824
+ formatted[prop] = target[prop]
1825
+ }
1826
+ formatted = {link: url}
1827
+ return `Proxy ${util.inspect(formatted, { ...opts, customInspect: true })}`
1828
+ }
1829
+
1830
+ return proxy
1831
+ }
1832
+
1690
1833
  function raw (proxy) {
1691
1834
  if (!(typeof proxy === 'object' && proxy[symbols.is_proxy]))
1692
1835
  return proxy
@@ -2099,13 +2242,14 @@
2099
2242
  var unescape_field_bus_to_json = (field) =>
2100
2243
  unescape_field_from_nelson(unescape_field_from_bus(field))
2101
2244
 
2102
- var escape_json_to_bus = (obj) => {
2245
+ var escape_json_to_bus = (obj) => {
2103
2246
  obj = translate_fields(obj, escape_field_json_to_bus)
2104
- return deep_map(obj, o => (typeof o === 'object' && symbols.link in o
2247
+ return deep_map(obj, o => ((typeof o === 'object' || typeof o === 'function')
2248
+ && symbols.link in o
2105
2249
  ? {link: o.link}
2106
2250
  : o))
2107
2251
  }
2108
- var unescape_bus_to_json = (obj) =>
2252
+ var unescape_bus_to_json = (obj) =>
2109
2253
  translate_fields(obj, field => unescape_field_bus_to_json(field))
2110
2254
 
2111
2255
 
@@ -2443,6 +2587,8 @@
2443
2587
 
2444
2588
  if (typeof obj === 'object') {
2445
2589
  if (schema === 'object') return true
2590
+ if (schema === 'link')
2591
+ return validate(obj, {link: 'string'})
2446
2592
 
2447
2593
  if (typeof schema === 'object') {
2448
2594
  for (var k in obj) {
@@ -2599,8 +2745,9 @@
2599
2745
  if (nodejs)
2600
2746
  // Use require.call() instead of require() to fool jsdelivr.net into ignoring the require
2601
2747
  require.call(null, './server-library').import_server(bus, make_bus, options)
2602
-
2748
+
2603
2749
  bus.render_when_loading = true
2750
+
2604
2751
  return bus
2605
2752
  }
2606
2753