statebus 7.0.33 → 7.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/statebus.js CHANGED
@@ -92,6 +92,10 @@
92
92
  var gets_in = new One_To_Many() // Maps `key' to `pub_funcs' subscribed to our key
93
93
 
94
94
  var currently_saving
95
+
96
+ // Two forms:
97
+ // - set(obj, t) with snapshot
98
+ // - set(key, t) with patches
95
99
  function set (obj, t) {
96
100
  // First let's handle patches. We receive them as:
97
101
  //
@@ -105,11 +109,11 @@
105
109
  var key = obj,
106
110
  obj = bus.cache[key]
107
111
 
108
- console.log('set: applying the patches!', {
109
- key: key,
110
- obj: obj,
111
- patches: t.patches[0]
112
- })
112
+ // console.log('set: applying the patches!', {
113
+ // key: key,
114
+ // obj: obj,
115
+ // patches: t.patches[0]
116
+ // })
113
117
  obj.val = apply_patch(obj.val, t.patches[0])
114
118
  }
115
119
 
@@ -290,8 +294,17 @@
290
294
  return f
291
295
  }
292
296
 
293
- // Called with a key to produce a subspace
294
- else return subspace(arg1, arg2)
297
+ // Called with a key to produce a subspace.
298
+ // We currently have two forms:
299
+ //
300
+ // old_subspace('foo').to_get = ... // etc.
301
+ //
302
+ // new_subspace('foo', {get: ...}) // etc
303
+ //
304
+ else if (arg2 === undefined)
305
+ return old_subspace(arg1)
306
+ else
307
+ return new_subspace(arg1, arg2)
295
308
  }
296
309
  var id = 'bus-' + Math.random().toString(36).substring(7)
297
310
  bus.toString = function () { return bus.label || 'bus'+this_bus_num || id }
@@ -393,6 +406,7 @@
393
406
 
394
407
  return obj
395
408
  }
409
+
396
410
  deep_map(object, update_object)
397
411
  return modified_keys.values()
398
412
  }
@@ -449,6 +463,12 @@
449
463
  // delete backup_cache[key]
450
464
  delete gets_out[key]
451
465
  delete to_be_forgotten[key]
466
+
467
+ if (bus.auto_delete && gets_in.empty()) {
468
+ bus.auto_delete()
469
+ bus.delete_bus()
470
+ }
471
+
452
472
  }, 200)
453
473
  }
454
474
  }
@@ -622,50 +642,49 @@
622
642
 
623
643
  // ****************
624
644
  // Connections
625
- function subspace (key, params) {
645
+ function old_subspace (key, params) {
626
646
  var methods = {getter:null, setter:null, on_set:null, on_set_sync:null,
627
647
  on_delete:null, deleter:null, forgetter:null}
628
648
 
629
- if (params) {
630
- for (var method in params) {
631
- var func = params[method]
632
- var param_names = {get:'getter', set:'setter',
633
- delete: 'deleter', forget: 'forgetter',
634
- on_set:'on_set', on_set_sync:'on_set_sync'}
635
-
636
- // Map it from the API's name to our internal name
637
- console.assert(param_names[method],
638
- 'Method "' + method + '" is invalid')
639
- method = param_names[method]
640
-
641
- autodetect_args(func)
642
- func.defined = func.defined || []
643
- func.defined.push({bus, method, key, as: 'handler'})
644
- func.use_linked_json = true
645
- bind(key, method, func, 'pattern')
646
- }
647
- }
648
- else {
649
- var result = {}
650
- for (var method in methods)
651
- (function (method) {
652
- Object.defineProperty(result, method, {
653
- set: function (func) {
654
- autodetect_args(func)
655
- func.defined = func.defined || []
656
- func.defined.push(
657
- {as:'handler', bus:bus, method:method, key:key})
658
- bind(key, method, func, 'pattern')
659
- },
660
- get: function () {
661
- var result = bindings(key, method)
662
- for (var i=0; i<result.length; i++) result[i] = result[i].func
663
- result.delete = function (func) { unbind (key, method, func, 'pattern') }
664
- return result
665
- }
666
- })
667
- })(method)
668
- return result
649
+ var result = {}
650
+ for (var method in methods)
651
+ (function (method) {
652
+ Object.defineProperty(result, method, {
653
+ set: function (func) {
654
+ autodetect_args(func)
655
+ func.defined = func.defined || []
656
+ func.defined.push(
657
+ {as:'handler', bus:bus, method:method, key:key})
658
+ bind(key, method, func, 'pattern')
659
+ },
660
+ get: function () {
661
+ var result = bindings(key, method)
662
+ for (var i=0; i<result.length; i++) result[i] = result[i].func
663
+ result.delete = function (func) { unbind (key, method, func, 'pattern') }
664
+ return result
665
+ }
666
+ })
667
+ })(method)
668
+ return result
669
+ }
670
+
671
+ function new_subspace (key, params) {
672
+ for (var method in params) {
673
+ var func = params[method]
674
+ var param_names = {get:'getter', set:'setter',
675
+ delete: 'deleter', forget: 'forgetter',
676
+ on_set:'on_set', on_set_sync:'on_set_sync'}
677
+
678
+ // Map it from the API's name to our internal name
679
+ console.assert(param_names[method],
680
+ 'Method "' + method + '" is invalid')
681
+ method = param_names[method]
682
+
683
+ autodetect_args(func)
684
+ func.call_with_proxies = true
685
+ func.defined = func.defined || []
686
+ func.defined.push({bus, method, key, as: 'handler'})
687
+ bind(key, method, func, 'pattern')
669
688
  }
670
689
  }
671
690
 
@@ -684,8 +703,8 @@
684
703
  case 'key':
685
704
  case 'k':
686
705
  handler.args['key'] = i; break
687
- case 'key_parts':
688
- handler.args['key_parts'] = i; break
706
+ case 'path':
707
+ handler.args['path'] = i; break
689
708
  case 'json':
690
709
  case 'vars':
691
710
  handler.args['vars'] = i; break
@@ -704,8 +723,8 @@
704
723
  handler.args['obj'] = i; break
705
724
  case 'old':
706
725
  handler.args['old'] = i; break
707
- case 'kson':
708
- handler.args['kson'] = i; break
726
+ case 'func_args':
727
+ handler.args['func_args'] = i; break
709
728
  }
710
729
  }
711
730
 
@@ -720,20 +739,45 @@
720
739
 
721
740
  function pattern_matcher (pattern) {
722
741
  var param_names = []
723
- var regex = new RegExp('^' + pattern.replace(/(?<=^|\/):[^/()]+|\*/g, match => {
724
- // Replace * with .*
725
- if (match === '*') return '.*'
726
742
 
743
+ // console.log('Creating pattern for', pattern)
744
+
745
+ // First, convert the pattern into a regexp.
746
+
747
+ // 1. Handle :foo by swapping it with '([^/*()]+)'
748
+ pattern = pattern.replace(/(?<=^|\/):[^/*()]+/g, match => {
727
749
  // Replace :foo with ([^/]+), and remember the "foo" name
728
750
  param_names.push(match.slice(1))
729
- return '([^/]+)'
730
- }) + '$')
751
+ return '([^/*()]+)'
752
+ })
753
+
754
+ // Now look for * and () at the end and replace with regexps
755
+ if (pattern.slice(-3) === '*()')
756
+ pattern = pattern.slice(0, -3) + '(?<star>[^()]*)(?<fstring>\\(.*\\))?'
757
+ else if (pattern.slice(-1) === '*')
758
+ pattern = pattern.slice(0, -1) + '(?<star>.*)'
759
+ else if (pattern.slice(-2) === '()')
760
+ pattern = pattern.slice(0, -2) + '(?<fstring>\\(.*\\))?'
761
+
762
+ // Construct the regexp
763
+ var regex = new RegExp('^' + pattern + '$')
731
764
 
765
+ // console.log('Pattern became ', regex)
766
+
767
+ // Return a matcher function that runs it
732
768
  return path => {
733
769
  var match = path.match(regex)
734
770
  if (!match) return null
735
771
  var params = {}
736
- match.slice(1).forEach((val, i) => params[param_names[i]] = val)
772
+ params.func_args = match.groups.fstring
773
+ params.star = match.groups.star
774
+
775
+ // Add to params.path all the path parts
776
+ params.path = {}
777
+ match.slice(1, param_names.length + 1).forEach((val, i) =>
778
+ params.path[param_names[i]] = val
779
+ )
780
+
737
781
  return params
738
782
  }
739
783
  }
@@ -797,14 +841,16 @@
797
841
  for (var i=0; i < pattern_handlers.length; i++) {
798
842
  handler = pattern_handlers[i]
799
843
  var matches = handler.matcher(key)
844
+ // console.log('The matching of', pattern_handlers[i], 'to', key, 'is', matches)
845
+
800
846
  if (matches // If the pattern matches
801
847
  && method === handler.method // And it has the right method
802
848
  && !seen[funk_key(handler.funk)]) {
803
849
 
804
850
  handler.funk.statebus_binding = {
805
- key: handler.pattern,
851
+ key: handler.pattern,
806
852
  method: method,
807
- params: matches
853
+ args: matches
808
854
  }
809
855
  result.push({method, key:handler.pattern, func:handler.funk})
810
856
  seen[funk_key(handler.funk)] = true
@@ -815,6 +861,11 @@
815
861
  }
816
862
 
817
863
  function run_handler(funck, method, arg, options) {
864
+ // If method == "set", then arg is an object {key: "foobar", ...}
865
+ // If method == "get", then arg is a key "foobar"
866
+ // If method == "forget", then arg is a key "foobar"
867
+ // If method == "delete", then arg is a key "foobar"
868
+
818
869
  options = options || {}
819
870
  var t = options.t,
820
871
  just_make_it = options.dont_run,
@@ -875,15 +926,30 @@
875
926
  // Fresh get/set/forget/delete handlers will just be regular
876
927
  // functions. We'll store their arg and transaction and let them
877
928
  // re-run until they are done re-running.
878
- function key_arg () { return ((typeof arg.key) == 'string') ? arg.key : arg }
879
- function rest_arg () { return (key_arg()).substr(binding.length-1) }
929
+ function key_arg () { return ((typeof arg.key) === 'string') ? arg.key : arg }
930
+ function rest_arg () {
931
+ return func.statebus_binding && func.statebus_binding.args.star
932
+ // (key_arg()).substr(binding.length-1)
933
+ }
880
934
  function val_arg () {
881
935
  console.assert(method === 'setter' || method === 'on_set' || method === 'on_set_sync',
882
936
  'Bad method for val_arg()')
883
- // Is there a time I am supposed to return bus.cache[arg]? It used to say:
884
- // arg.key ? arg : bus.cache[arg]
885
937
 
886
- return arg.key ? (func.use_linked_json ? arg.val : arg) : undefined
938
+ var value = (typeof arg === 'string'
939
+ // If arg is a key, then semantics of val is "the
940
+ // current value in the cache". I'm doing this
941
+ // because it *might* be useful in a getter ... but
942
+ // let's see if we actually have good examples of use
943
+ // for this feature.
944
+ ? bus.cache[arg]
945
+ // Otherwise, arg must be the actual object
946
+ : arg)
947
+
948
+ // The Proxy API unwraps the .val for us
949
+ if (func.call_with_proxies)
950
+ value = value.val
951
+
952
+ return value
887
953
  }
888
954
  function vars_arg () {
889
955
  var r = rest_arg()
@@ -893,19 +959,19 @@
893
959
  return 'Bad JSON "' + r + '" for key ' + key_arg()
894
960
  }
895
961
  }
896
- // Parse out query params for URLs in the style /foo/bar(param1,param2:val2).
897
- // We're currently calling that (param1,param2:val2) part "kson", and presuming
898
- // that it exists in the * part of the pattern like in /foo/bar*.
899
- function kson_args () {
962
+ // Parse out function params for URLs in the style /foo/bar(param1,param2:val2).
963
+ // We're currently calling that (param1,param2:val2) part "function params", and presuming
964
+ // that it exists in the () part of the pattern like in /foo/bar*().
965
+ function func_args () {
900
966
  // This code is copied from Raphael Walker's parser.coffee
901
967
  function split_once (str, char) {
902
968
  var i = str.indexOf(char)
903
969
  return i === -1 ? [str, ""] : [str.slice(0, i), str.slice(i + 1)]
904
970
  }
905
971
 
906
- // KSON = Kinda Simple Object Notation
907
- // but it rhymes with JSON
908
- function parse_kson (str) {
972
+ // Function Strings -- an alternative to query strings
973
+ //
974
+ function parse_function_string (str) {
909
975
  // Coffeescript doesn't have object comprehensions :(
910
976
  var ret = {}
911
977
 
@@ -916,7 +982,7 @@
916
982
  // Split by commas. TODO: Allow spaces after commas?
917
983
  .split(",")
918
984
  // Delete empty parts (this ensures that empty strings
919
- // will properly result in empty KSON, and allows trailing
985
+ // will properly result in empty func_string, and allows trailing
920
986
  // commas)
921
987
  .filter(part => part.length)
922
988
  .forEach(part => {
@@ -925,13 +991,17 @@
925
991
  var [k, v] = split_once(part, ":")
926
992
 
927
993
  if (v.length === 0) ret[k] = true
928
- // If the value is itself a KSON object, parse it recursively
929
- else if (v.startsWith("(")) ret[k] = parse_kson(v)
994
+ // If the value is itself a func_string params object, parse it recursively
995
+ else if (v.startsWith("(")) ret[k] = parse_function_string(v)
930
996
  else ret[k] = v
931
997
  })
932
998
  return ret
933
999
  }
934
- return parse_kson(rest_arg())
1000
+ // console.log('Getting func_args from', func.statebus_binding.args)
1001
+ if (!(func.statebus_binding.args
1002
+ && func.statebus_binding.args.func_args))
1003
+ return undefined
1004
+ return parse_function_string(func.statebus_binding.args.func_args)
935
1005
  }
936
1006
  var f = reactive(function () {
937
1007
  // Initialize transaction
@@ -951,8 +1021,8 @@
951
1021
  if (method !== 'forgetter')
952
1022
  t.done = function (o) {
953
1023
  var key = method === 'setter' ? arg.key : arg
954
- if (func.use_linked_json)
955
- o = {key, val: o}
1024
+ if (func.call_with_proxies)
1025
+ o = {key, val: raw(o)}
956
1026
  bus.log('We are DONE()ing', method, key, o||arg)
957
1027
 
958
1028
  // We use a simple (and crappy?) heuristic to know if the
@@ -1007,8 +1077,10 @@
1007
1077
  switch (k) {
1008
1078
  case 'key':
1009
1079
  args[func.args[k]] = key_arg(); break
1010
- case 'key_parts':
1011
- args[func.args[k]] = func.statebus_binding && func.statebus_binding.params
1080
+ case 'path':
1081
+ args[func.args[k]] = (func.statebus_binding
1082
+ && func.statebus_binding.args
1083
+ && func.statebus_binding.args.path)
1012
1084
  break
1013
1085
  case 'rest':
1014
1086
  args[func.args[k]] = rest_arg(); break
@@ -1022,9 +1094,11 @@
1022
1094
  case 'old':
1023
1095
  var key = key_arg()
1024
1096
  args[func.args[k]] = bus.cache[key] || (bus.cache[key] = {key:key})
1097
+ if (func.call_with_proxies)
1098
+ args[func.args[k]] = args[func.args[k]].val
1025
1099
  break
1026
- case 'kson':
1027
- args[func.args[k]] = kson_args(rest_arg()); break
1100
+ case 'func_args':
1101
+ args[func.args[k]] = func_args(); break
1028
1102
  }
1029
1103
  }
1030
1104
 
@@ -1045,9 +1119,9 @@
1045
1119
  if (result === 'abort') t.abort()
1046
1120
 
1047
1121
  // For get
1048
- if (func.use_linked_json) {
1122
+ if (func.call_with_proxies) {
1049
1123
  if (method === 'getter' && result !== undefined && !f.loading()) {
1050
- var obj = {key: arg, val: result}
1124
+ var obj = {key: arg, val: raw(result)}
1051
1125
  var new_t = clone(t || {})
1052
1126
  new_t.getter = true
1053
1127
  set.fire(obj, new_t)
@@ -1378,9 +1452,8 @@
1378
1452
 
1379
1453
  var symbols = {
1380
1454
  is_proxy: Symbol('is_proxy'),
1381
- // is_link: Symbol('is_link'),
1382
- // get_json: Symbol('get_json'),
1383
- // get_base: Symbol('get_base')
1455
+ raw: Symbol('raw'),
1456
+ link: Symbol('link')
1384
1457
  }
1385
1458
  function make_proxy () {
1386
1459
  // var custom_inspect_symbol = nodejs && require('util').inspect.custom,
@@ -1401,8 +1474,9 @@
1401
1474
 
1402
1475
  // We recursively descend through {key: ...} links
1403
1476
  if (typeof o === 'object' && 'link' in o) {
1404
- var new_base = bus.get(o.link)
1405
- return item_proxy(new_base, '', new_base.val)
1477
+ // var new_base = bus.get(o.link)
1478
+ // return item_proxy(new_base, '', new_base.val)
1479
+ return link_proxy(o.link)
1406
1480
  }
1407
1481
 
1408
1482
 
@@ -1422,18 +1496,20 @@
1422
1496
  // return custom_inspect
1423
1497
  if (k === symbols.is_proxy)
1424
1498
  return true
1499
+ if (k === symbols.raw)
1500
+ return o
1425
1501
  if (typeof k === 'symbol')
1426
1502
  return undefined
1427
1503
 
1428
1504
  // Compute the new path
1429
1505
  var new_path = path + '[' + JSON.stringify(k) + ']'
1430
- return item_proxy(base, new_path, o[escape_field_to_bus(escape_field_to_nelson(k))])
1506
+ return item_proxy(base, new_path, o[escape_field_json_to_bus(k)])
1431
1507
  },
1432
1508
  set: function (o, k, v) {
1433
- var value = translate_fields(v, field => escape_field_to_bus(escape_field_to_nelson(field)))
1434
- o[escape_field_to_bus(escape_field_to_nelson(k))] = value
1509
+ var value = escape_json_to_bus(v)
1510
+ o[escape_field_json_to_bus(k)] = value
1435
1511
  var new_path = path + '[' + JSON.stringify(k) + ']'
1436
- bus.set(
1512
+ bus.set.sync(
1437
1513
  base,
1438
1514
  // Forward the patches too
1439
1515
  {patches: [{unit: 'json',
@@ -1445,17 +1521,17 @@
1445
1521
  has: function (o, k) {
1446
1522
  // if (custom_inspect && k === custom_inspect)
1447
1523
  // return true
1448
- return o.hasOwnProperty(escape_field_to_bus(escape_field_to_nelson(k)))
1524
+ return o.hasOwnProperty(escape_field_json_to_bus(k))
1449
1525
  },
1450
1526
  ownKeys: function () {
1451
- return Object.keys(o).map(unescape_field_from_nelson).map(unescape_field_from_bus)
1527
+ return Object.keys(o).map(unescape_field_bus_to_json)
1452
1528
  },
1453
1529
  getOwnPropertyDescriptor: function (target, key) {
1454
1530
  return { enumerable: true, configurable: true, value: this.get(o, key) }
1455
1531
  },
1456
1532
  deleteProperty: function del (o, k) {
1457
1533
  var new_path = path + '[' + JSON.stringify(k) + ']'
1458
- delete o[escape_field_to_bus(escape_field_to_nelson(k))]
1534
+ delete o[escape_field_json_to_bus(k)]
1459
1535
  bus.set(
1460
1536
  base,
1461
1537
  // Forward the patches too
@@ -1472,15 +1548,84 @@
1472
1548
  // }
1473
1549
  })}
1474
1550
 
1551
+ function link_proxy (url) {
1552
+ return new Proxy(function follow_link () {}, {
1553
+ get: function (o, k) {
1554
+ if (k === 'inspect' || k === 'valueOf')
1555
+ return undefined
1556
+ // if (custom_inspect && k === custom_inspect)
1557
+ // return custom_inspect
1558
+ if (k === symbols.is_proxy)
1559
+ return true
1560
+ if (k === symbols.raw)
1561
+ return {link: url}
1562
+ if (typeof k === 'symbol')
1563
+ return undefined
1564
+
1565
+ if (k === 'link')
1566
+ return url
1567
+
1568
+ return undefined
1569
+ },
1570
+ set: function (o, k, v) {
1571
+ if (k === 'link') {
1572
+ url = v
1573
+ }
1574
+
1575
+ console.assert(false, "Have not fully implemented setting links yet")
1576
+
1577
+ // Todo: update the containing proxy object with bus.set.sync
1578
+ // else return
1579
+ // var value = escape_json_to_bus(v)
1580
+ // o[escape_field_json_to_bus(k)] = value
1581
+ // var new_path = path + '[' + JSON.stringify(k) + ']'
1582
+ // bus.set.sync(
1583
+ // base,
1584
+ // // Forward the patches too
1585
+ // {patches: [{unit: 'json',
1586
+ // range: new_path,
1587
+ // content: JSON.stringify(v)}]}
1588
+ // )
1589
+ return true
1590
+ },
1591
+ has: function (o, k) {
1592
+ return k === 'link'
1593
+ },
1594
+ ownKeys: function () {
1595
+ return ['link']
1596
+ },
1597
+ // getOwnPropertyDescriptor: function (target, key) {
1598
+ // return { enumerable: true, configurable: true, value: this.get(o, key) }
1599
+ // },
1600
+ // deleteProperty: function del (o, k) {
1601
+ // var new_path = path + '[' + JSON.stringify(k) + ']'
1602
+ // delete o[escape_field_json_to_bus(k)]
1603
+ // bus.set(
1604
+ // base,
1605
+ // // Forward the patches too
1606
+ // {patches: [{unit: 'json',
1607
+ // range: new_path,
1608
+ // content: undefined}]}
1609
+ // )
1610
+ // return true // Report success to Proxy
1611
+ // }
1612
+
1613
+ // For function proxy:
1614
+ apply: function apply (o, This, args) {
1615
+ // console.log('Descending into the link', url, '!')
1616
+ return top_level_proxy[url]
1617
+ }
1618
+ })
1619
+ }
1475
1620
 
1476
- // For function proxies:
1477
- // var dummy = function () {}
1478
1621
 
1479
1622
  // The top-level Proxy object holds HTTP resources
1480
- return new Proxy(cache, {
1623
+ var top_level_proxy = new Proxy(cache, {
1481
1624
  get: function get(o, k) {
1482
1625
  if (k === symbols.is_proxy)
1483
1626
  return true
1627
+ if (k === symbols.raw)
1628
+ return raw_proxy()
1484
1629
  if (k === 'inspect' || k === 'valueOf' || typeof k === 'symbol')
1485
1630
  return undefined
1486
1631
  bogus_check(k)
@@ -1490,7 +1635,7 @@
1490
1635
  set: function set(o, key, val) {
1491
1636
  bus.set({
1492
1637
  key: key,
1493
- val: translate_fields(val, field => escape_field_to_bus(escape_field_to_nelson(field)))
1638
+ val: escape_json_to_bus(val)
1494
1639
  })
1495
1640
  return true
1496
1641
  },
@@ -1503,15 +1648,35 @@
1503
1648
  // return 'fluffy'
1504
1649
  // }
1505
1650
  })
1651
+
1652
+ return top_level_proxy
1506
1653
  }
1507
1654
  bus.state = make_proxy()
1508
1655
 
1509
- // function link (url) {
1510
- // var result = bus.get(url)
1511
- // result[symbols.is_link] = true
1512
- // return result
1513
- // }
1656
+ // This is temporary code to wrap the cache as a flat key/valuel store
1657
+ // that returns raw objects, dereferencing .val. We can remove it once we
1658
+ // remove the internal .val stuff from statebus.
1659
+ function raw_proxy () {
1660
+ return new Proxy(cache, {
1661
+ get: function get(o, k) { return bus.cache[k].val },
1662
+ set: function set(o, key, val) {
1663
+ return false
1664
+ },
1665
+ deleteProperty: function del (o, k) {
1666
+ return false
1667
+ }
1668
+ })
1669
+ }
1514
1670
 
1671
+ function link (url) {
1672
+ return {[symbols.link]: url}
1673
+ }
1674
+ function raw (proxy) {
1675
+ if (!(typeof proxy === 'object' && proxy[symbols.is_proxy]))
1676
+ return proxy
1677
+
1678
+ return proxy[symbols.raw]
1679
+ }
1515
1680
 
1516
1681
  // So chrome can print out proxy objects decently
1517
1682
  if (!nodejs)
@@ -1527,7 +1692,55 @@
1527
1692
  hasBody: function (x) {return false}
1528
1693
  }]
1529
1694
 
1530
- // ******************
1695
+
1696
+ // ************************************************
1697
+ //
1698
+ // Every dialogue we have with a peer gets an ID.
1699
+ // This `bus.dialogues` variable maps:
1700
+ //
1701
+ // dialogue ID -> send_function
1702
+ //
1703
+ // for each peer we are in dialogue with.
1704
+ //
1705
+ // One can use this to make sure we don't send an update
1706
+ // back to the same peer that sent it to us.
1707
+
1708
+ bus.dialogues = {}
1709
+
1710
+
1711
+ // ************************************************
1712
+ // Custom clients
1713
+ var client_counter = 0
1714
+ var client_busses = {}
1715
+ function client_bus_for (client_id) {
1716
+ if (!bus.custom_clients) return bus
1717
+
1718
+ // Do we have a bus for this client yet?
1719
+ if (client_busses[client_id])
1720
+
1721
+ // Return existing bus
1722
+ return client_busses[client_id]
1723
+
1724
+ else {
1725
+
1726
+ // Make a new bus
1727
+ var client_bus = make_bus()
1728
+ client_bus.label = 'client-' + client_counter++
1729
+ client_bus.master = bus
1730
+
1731
+ // Initialize the client-specific handlers
1732
+ bus.custom_clients(client_bus, client_id)
1733
+
1734
+ // Remember it, and auto-delete it when it's no longer being used
1735
+ client_busses[client_id] = client_bus
1736
+ client_bus.auto_delete = () => delete client_busses[client_id]
1737
+
1738
+ return client_bus
1739
+ }
1740
+ }
1741
+
1742
+
1743
+ // ************************************************
1531
1744
  // Network client
1532
1745
  function get_domain (key) { // Returns e.g. "state://foo.com"
1533
1746
  var m = key.match(/^i?statei?\:\/\/(([^:\/?#]*)(?:\:([0-9]+))?)/)
@@ -1543,9 +1756,10 @@
1543
1756
  function ws_mount (prefix, url, client_creds) {
1544
1757
  // Local: state://foo.com/* or /*
1545
1758
  var preprefix = prefix.slice(0,-1)
1759
+ var bus = this
1546
1760
  var is_absolute = /^i?statei?:\/\//
1761
+ var creds = client_creds || (bus.client_creds && bus.client_creds(url))
1547
1762
  var has_prefix = new RegExp('^' + preprefix)
1548
- var bus = this
1549
1763
  var sock
1550
1764
  var attempts = 0
1551
1765
  var outbox = []
@@ -1625,7 +1839,6 @@
1625
1839
  set(peers)
1626
1840
 
1627
1841
  // Login
1628
- var creds = client_creds || (bus.client_creds && bus.client_creds(url))
1629
1842
  if (creds) {
1630
1843
  var i = []
1631
1844
  function intro (o) {i.push(JSON.stringify({set: o}))}
@@ -1831,8 +2044,10 @@
1831
2044
  }
1832
2045
 
1833
2046
  // Recurse through each property on objects
1834
- else if (typeof json === 'object' && json !== null) {
2047
+ else if (typeof json === 'object' && json !== null && !(symbols.link in json)) {
1835
2048
  var new_obj = {}
2049
+
2050
+ // Regular objects get their fields translated
1836
2051
  for (var k in json)
1837
2052
  if (typeof k === 'string')
1838
2053
  new_obj[translate(k, json)] = translate_fields(json[k], translate)
@@ -1863,6 +2078,21 @@
1863
2078
  escape_to_nelson = (obj) => translate_fields(obj, escape_field_to_nelson)
1864
2079
  unescape_from_nelson = (obj) => translate_fields(obj, unescape_field_from_nelson)
1865
2080
 
2081
+ var escape_field_json_to_bus = (field) =>
2082
+ escape_field_to_bus(escape_field_to_nelson(field))
2083
+ var unescape_field_bus_to_json = (field) =>
2084
+ unescape_field_from_nelson(unescape_field_from_bus(field))
2085
+
2086
+ var escape_json_to_bus = (obj) => {
2087
+ obj = translate_fields(obj, escape_field_json_to_bus)
2088
+ return deep_map(obj, o => (typeof o === 'object' && symbols.link in o
2089
+ ? {link: o[symbols.link]}
2090
+ : o))
2091
+ }
2092
+ var unescape_bus_to_json = (obj) =>
2093
+ translate_fields(obj, field => unescape_field_bus_to_jsob(field))
2094
+
2095
+
1866
2096
  var field_replace = (field, pattern, replacement) => (typeof field === 'string'
1867
2097
  ? field.replace(pattern, replacement)
1868
2098
  : field)
@@ -1971,6 +2201,7 @@
1971
2201
  this.has = function (k, v) { return hash[k] && hash[k][v] }
1972
2202
  this.has_any = function (k) { return counts[k] }
1973
2203
  this.del = this.delete // for compatibility; remove this soon
2204
+ this.empty = function () { return !Object.values(counts).some(count => count !== 0) }
1974
2205
  }
1975
2206
  function Set () {
1976
2207
  var hash = {}
@@ -2312,14 +2543,15 @@
2312
2543
 
2313
2544
  // Make these private methods accessible
2314
2545
  var api = ['cache backup_cache get set forget del fire dirty get_once',
2315
- 'subspace bindings run_handler bind unbind reactive uncallback',
2546
+ 'old_subspace new_subspace bindings run_handler bind unbind reactive uncallback',
2316
2547
  'versions new_version',
2317
- 'aget',
2548
+ 'aget client_bus_for',
2318
2549
  'funk_key funk_name funks key_id key_name id',
2319
2550
  'pending_gets gets_in gets_out loading_keys loading once',
2320
2551
  'global_funk busses rerunnable_funks',
2321
- 'translate_keys translate_links apply_patch',
2552
+ 'link raw translate_keys translate_links apply_patch',
2322
2553
  'translate_fields escape_to_bus unescape_from_bus escape_to_nelson unescape_from_nelson',
2554
+ 'escape_field_json_to_bus unescape_field_bus_to_json escape_json_to_bus unescape_bus_to_json',
2323
2555
  'ws_mount net_automount message_method',
2324
2556
  'parse Set One_To_Many clone extend deep_map deep_equals prune validate sorta_diff log deps symbols'
2325
2557
  ].join(' ').split(' ')