statebus 7.0.32 → 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,16 +294,25 @@
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 }
298
311
  bus.delete_bus = function () {
299
- // // Forget all wildcard handlers
300
- // for (var i=0; i<wildcard_handlers.length; i++) {
301
- // console.log('Forgetting', funk_name(wildcard_handlers[i].funk))
302
- // wildcard_handlers[i].funk.forget()
312
+ // // Forget all pattern handlers
313
+ // for (var i=0; i<pattern_handlers.length; i++) {
314
+ // console.log('Forgetting', funk_name(pattern_handlers[i].funk))
315
+ // pattern_handlers[i].funk.forget()
303
316
  // }
304
317
 
305
318
  // // Forget all handlers
@@ -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, 'allow_wildcards')
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, 'allow_wildcards')
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, 'allow_wildcards') }
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,6 +703,8 @@
684
703
  case 'key':
685
704
  case 'k':
686
705
  handler.args['key'] = i; break
706
+ case 'path':
707
+ handler.args['path'] = i; break
687
708
  case 'json':
688
709
  case 'vars':
689
710
  handler.args['vars'] = i; break
@@ -702,44 +723,96 @@
702
723
  handler.args['obj'] = i; break
703
724
  case 'old':
704
725
  handler.args['old'] = i; break
726
+ case 'func_args':
727
+ handler.args['func_args'] = i; break
705
728
  }
706
729
  }
707
730
 
708
731
  // The funks attached to each key, maps e.g. 'get /point/3' to '/30'
709
732
  var handlers = new One_To_Many()
710
- var wildcard_handlers = [] // An array of {prefix, method, funk}
733
+ var pattern_handlers = [] // An array of {pattern, method, funk}
711
734
 
712
735
  // A set of timers, for keys to send forgets on
713
736
  var to_be_forgotten = {}
714
- function bind (key, method, func, allow_wildcards) {
715
- bogus_check(key)
716
- if (allow_wildcards && key[key.length-1] === '*')
717
- wildcard_handlers.push({prefix: key,
718
- method: method,
719
- funk: func})
737
+ function bind (pattern, method, func, type) {
738
+ bogus_check(pattern)
739
+
740
+ function pattern_matcher (pattern) {
741
+ var param_names = []
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 => {
749
+ // Replace :foo with ([^/]+), and remember the "foo" name
750
+ param_names.push(match.slice(1))
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 + '$')
764
+
765
+ // console.log('Pattern became ', regex)
766
+
767
+ // Return a matcher function that runs it
768
+ return path => {
769
+ var match = path.match(regex)
770
+ if (!match) return null
771
+ var params = {}
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
+
781
+ return params
782
+ }
783
+ }
784
+
785
+ // Add patterns to the list, not the hash
786
+ if (type === 'pattern' && /[:*]/.test(pattern)) // Is it a pattern?
787
+ pattern_handlers.push({pattern: pattern,
788
+ matcher: pattern_matcher(pattern),
789
+ method: method,
790
+ funk: func})
791
+
792
+ // Add simple keys to the hash
720
793
  else
721
- handlers.add(method + ' ' + key, funk_key(func))
794
+ handlers.add(method + ' ' + pattern, funk_key(func))
722
795
 
723
796
  // Now check if the method is a get and there's a getted
724
797
  // key in this space, and if so call the handler.
725
798
  }
726
- function unbind (key, method, funk, allow_wildcards) {
727
- bogus_check(key)
728
- if (allow_wildcards && key[key.length-1] === '*')
729
- // Delete wildcard connection
730
- for (var i=0; i<wildcard_handlers.length; i++) {
731
- var handler = wildcard_handlers[i]
732
- if (handler.prefix === key
799
+ function unbind (pattern, method, funk, type) {
800
+ bogus_check(pattern)
801
+ if (type === 'pattern' && /[:*]/.test(pattern)) // Is it a pattern?
802
+ // Delete pattern connection
803
+ for (var i=0; i < pattern_handlers.length; i++) {
804
+ var handler = pattern_handlers[i]
805
+ if (handler.pattern === pattern
733
806
  && handler.method === method
734
807
  && handler.funk === funk) {
735
808
 
736
- wildcard_handlers.splice(i,1) // Splice this element out of the array
737
- i-- // And decrement the counter while we're looping
809
+ pattern_handlers.splice(i,1) // Splice this element out of the array
810
+ i-- // And decrement the counter while we're looping
738
811
  }
739
812
  }
740
813
  else
741
814
  // Delete direct connection
742
- handlers.delete(method + ' ' + key, funk_key(funk))
815
+ handlers.delete(method + ' ' + pattern, funk_key(funk))
743
816
  }
744
817
 
745
818
  function bindings(key, method) {
@@ -764,16 +837,22 @@
764
837
  }
765
838
  }
766
839
 
767
- // Now iterate through prefixes
768
- for (var i=0; i < wildcard_handlers.length; i++) {
769
- handler = wildcard_handlers[i]
840
+ // Now iterate through patterns
841
+ for (var i=0; i < pattern_handlers.length; i++) {
842
+ handler = pattern_handlers[i]
843
+ var matches = handler.matcher(key)
844
+ // console.log('The matching of', pattern_handlers[i], 'to', key, 'is', matches)
770
845
 
771
- var prefix = handler.prefix.slice(0, -1) // Cut off the *
772
- if (prefix === key.substr(0,prefix.length) // If the prefix matches
773
- && method === handler.method // And it has the right method
846
+ if (matches // If the pattern matches
847
+ && method === handler.method // And it has the right method
774
848
  && !seen[funk_key(handler.funk)]) {
775
- handler.funk.statebus_binding = {key:handler.prefix, method:method}
776
- result.push({method:method, key:handler.prefix, func:handler.funk})
849
+
850
+ handler.funk.statebus_binding = {
851
+ key: handler.pattern,
852
+ method: method,
853
+ args: matches
854
+ }
855
+ result.push({method, key:handler.pattern, func:handler.funk})
777
856
  seen[funk_key(handler.funk)] = true
778
857
  }
779
858
  }
@@ -782,6 +861,11 @@
782
861
  }
783
862
 
784
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
+
785
869
  options = options || {}
786
870
  var t = options.t,
787
871
  just_make_it = options.dont_run,
@@ -842,15 +926,30 @@
842
926
  // Fresh get/set/forget/delete handlers will just be regular
843
927
  // functions. We'll store their arg and transaction and let them
844
928
  // re-run until they are done re-running.
845
- function key_arg () { return ((typeof arg.key) == 'string') ? arg.key : arg }
846
- 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
+ }
847
934
  function val_arg () {
848
935
  console.assert(method === 'setter' || method === 'on_set' || method === 'on_set_sync',
849
936
  'Bad method for val_arg()')
850
- // Is there a time I am supposed to return bus.cache[arg]? It used to say:
851
- // arg.key ? arg : bus.cache[arg]
852
937
 
853
- 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
854
953
  }
855
954
  function vars_arg () {
856
955
  var r = rest_arg()
@@ -860,6 +959,50 @@
860
959
  return 'Bad JSON "' + r + '" for key ' + key_arg()
861
960
  }
862
961
  }
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 () {
966
+ // This code is copied from Raphael Walker's parser.coffee
967
+ function split_once (str, char) {
968
+ var i = str.indexOf(char)
969
+ return i === -1 ? [str, ""] : [str.slice(0, i), str.slice(i + 1)]
970
+ }
971
+
972
+ // Function Strings -- an alternative to query strings
973
+ //
974
+ function parse_function_string (str) {
975
+ // Coffeescript doesn't have object comprehensions :(
976
+ var ret = {}
977
+
978
+ // Now process the string:
979
+ str
980
+ // Pull out the parentheses
981
+ .slice(1, -1)
982
+ // Split by commas. TODO: Allow spaces after commas?
983
+ .split(",")
984
+ // Delete empty parts (this ensures that empty strings
985
+ // will properly result in empty func_string, and allows trailing
986
+ // commas)
987
+ .filter(part => part.length)
988
+ .forEach(part => {
989
+ // If the part has a comma, its a key:value, otherwise
990
+ // it's just a singleton
991
+ var [k, v] = split_once(part, ":")
992
+
993
+ if (v.length === 0) ret[k] = true
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)
996
+ else ret[k] = v
997
+ })
998
+ return ret
999
+ }
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)
1005
+ }
863
1006
  var f = reactive(function () {
864
1007
  // Initialize transaction
865
1008
  t = clone(t || {})
@@ -878,8 +1021,8 @@
878
1021
  if (method !== 'forgetter')
879
1022
  t.done = function (o) {
880
1023
  var key = method === 'setter' ? arg.key : arg
881
- if (func.use_linked_json)
882
- o = {key, val: o}
1024
+ if (func.call_with_proxies)
1025
+ o = {key, val: raw(o)}
883
1026
  bus.log('We are DONE()ing', method, key, o||arg)
884
1027
 
885
1028
  // We use a simple (and crappy?) heuristic to know if the
@@ -934,6 +1077,11 @@
934
1077
  switch (k) {
935
1078
  case 'key':
936
1079
  args[func.args[k]] = key_arg(); break
1080
+ case 'path':
1081
+ args[func.args[k]] = (func.statebus_binding
1082
+ && func.statebus_binding.args
1083
+ && func.statebus_binding.args.path)
1084
+ break
937
1085
  case 'rest':
938
1086
  args[func.args[k]] = rest_arg(); break
939
1087
  case 'vars':
@@ -946,7 +1094,11 @@
946
1094
  case 'old':
947
1095
  var key = key_arg()
948
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
949
1099
  break
1100
+ case 'func_args':
1101
+ args[func.args[k]] = func_args(); break
950
1102
  }
951
1103
  }
952
1104
 
@@ -967,9 +1119,9 @@
967
1119
  if (result === 'abort') t.abort()
968
1120
 
969
1121
  // For get
970
- if (func.use_linked_json) {
1122
+ if (func.call_with_proxies) {
971
1123
  if (method === 'getter' && result !== undefined && !f.loading()) {
972
- var obj = {key: arg, val: result}
1124
+ var obj = {key: arg, val: raw(result)}
973
1125
  var new_t = clone(t || {})
974
1126
  new_t.getter = true
975
1127
  set.fire(obj, new_t)
@@ -1300,9 +1452,8 @@
1300
1452
 
1301
1453
  var symbols = {
1302
1454
  is_proxy: Symbol('is_proxy'),
1303
- // is_link: Symbol('is_link'),
1304
- // get_json: Symbol('get_json'),
1305
- // get_base: Symbol('get_base')
1455
+ raw: Symbol('raw'),
1456
+ link: Symbol('link')
1306
1457
  }
1307
1458
  function make_proxy () {
1308
1459
  // var custom_inspect_symbol = nodejs && require('util').inspect.custom,
@@ -1323,8 +1474,9 @@
1323
1474
 
1324
1475
  // We recursively descend through {key: ...} links
1325
1476
  if (typeof o === 'object' && 'link' in o) {
1326
- var new_base = bus.get(o.link)
1327
- 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)
1328
1480
  }
1329
1481
 
1330
1482
 
@@ -1344,18 +1496,20 @@
1344
1496
  // return custom_inspect
1345
1497
  if (k === symbols.is_proxy)
1346
1498
  return true
1499
+ if (k === symbols.raw)
1500
+ return o
1347
1501
  if (typeof k === 'symbol')
1348
1502
  return undefined
1349
1503
 
1350
1504
  // Compute the new path
1351
1505
  var new_path = path + '[' + JSON.stringify(k) + ']'
1352
- 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)])
1353
1507
  },
1354
1508
  set: function (o, k, v) {
1355
- var value = translate_fields(v, field => escape_field_to_bus(escape_field_to_nelson(field)))
1356
- 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
1357
1511
  var new_path = path + '[' + JSON.stringify(k) + ']'
1358
- bus.set(
1512
+ bus.set.sync(
1359
1513
  base,
1360
1514
  // Forward the patches too
1361
1515
  {patches: [{unit: 'json',
@@ -1367,17 +1521,17 @@
1367
1521
  has: function (o, k) {
1368
1522
  // if (custom_inspect && k === custom_inspect)
1369
1523
  // return true
1370
- return o.hasOwnProperty(escape_field_to_bus(escape_field_to_nelson(k)))
1524
+ return o.hasOwnProperty(escape_field_json_to_bus(k))
1371
1525
  },
1372
1526
  ownKeys: function () {
1373
- 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)
1374
1528
  },
1375
1529
  getOwnPropertyDescriptor: function (target, key) {
1376
1530
  return { enumerable: true, configurable: true, value: this.get(o, key) }
1377
1531
  },
1378
1532
  deleteProperty: function del (o, k) {
1379
1533
  var new_path = path + '[' + JSON.stringify(k) + ']'
1380
- delete o[escape_field_to_bus(escape_field_to_nelson(k))]
1534
+ delete o[escape_field_json_to_bus(k)]
1381
1535
  bus.set(
1382
1536
  base,
1383
1537
  // Forward the patches too
@@ -1394,15 +1548,84 @@
1394
1548
  // }
1395
1549
  })}
1396
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
+ }
1397
1620
 
1398
- // For function proxies:
1399
- // var dummy = function () {}
1400
1621
 
1401
1622
  // The top-level Proxy object holds HTTP resources
1402
- return new Proxy(cache, {
1623
+ var top_level_proxy = new Proxy(cache, {
1403
1624
  get: function get(o, k) {
1404
1625
  if (k === symbols.is_proxy)
1405
1626
  return true
1627
+ if (k === symbols.raw)
1628
+ return raw_proxy()
1406
1629
  if (k === 'inspect' || k === 'valueOf' || typeof k === 'symbol')
1407
1630
  return undefined
1408
1631
  bogus_check(k)
@@ -1412,7 +1635,7 @@
1412
1635
  set: function set(o, key, val) {
1413
1636
  bus.set({
1414
1637
  key: key,
1415
- val: translate_fields(val, field => escape_field_to_bus(escape_field_to_nelson(field)))
1638
+ val: escape_json_to_bus(val)
1416
1639
  })
1417
1640
  return true
1418
1641
  },
@@ -1425,15 +1648,35 @@
1425
1648
  // return 'fluffy'
1426
1649
  // }
1427
1650
  })
1651
+
1652
+ return top_level_proxy
1428
1653
  }
1429
1654
  bus.state = make_proxy()
1430
1655
 
1431
- // function link (url) {
1432
- // var result = bus.get(url)
1433
- // result[symbols.is_link] = true
1434
- // return result
1435
- // }
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
+ }
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
1436
1677
 
1678
+ return proxy[symbols.raw]
1679
+ }
1437
1680
 
1438
1681
  // So chrome can print out proxy objects decently
1439
1682
  if (!nodejs)
@@ -1449,7 +1692,55 @@
1449
1692
  hasBody: function (x) {return false}
1450
1693
  }]
1451
1694
 
1452
- // ******************
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
+ // ************************************************
1453
1744
  // Network client
1454
1745
  function get_domain (key) { // Returns e.g. "state://foo.com"
1455
1746
  var m = key.match(/^i?statei?\:\/\/(([^:\/?#]*)(?:\:([0-9]+))?)/)
@@ -1465,9 +1756,10 @@
1465
1756
  function ws_mount (prefix, url, client_creds) {
1466
1757
  // Local: state://foo.com/* or /*
1467
1758
  var preprefix = prefix.slice(0,-1)
1759
+ var bus = this
1468
1760
  var is_absolute = /^i?statei?:\/\//
1761
+ var creds = client_creds || (bus.client_creds && bus.client_creds(url))
1469
1762
  var has_prefix = new RegExp('^' + preprefix)
1470
- var bus = this
1471
1763
  var sock
1472
1764
  var attempts = 0
1473
1765
  var outbox = []
@@ -1547,7 +1839,6 @@
1547
1839
  set(peers)
1548
1840
 
1549
1841
  // Login
1550
- var creds = client_creds || (bus.client_creds && bus.client_creds(url))
1551
1842
  if (creds) {
1552
1843
  var i = []
1553
1844
  function intro (o) {i.push(JSON.stringify({set: o}))}
@@ -1753,8 +2044,10 @@
1753
2044
  }
1754
2045
 
1755
2046
  // Recurse through each property on objects
1756
- else if (typeof json === 'object' && json !== null) {
2047
+ else if (typeof json === 'object' && json !== null && !(symbols.link in json)) {
1757
2048
  var new_obj = {}
2049
+
2050
+ // Regular objects get their fields translated
1758
2051
  for (var k in json)
1759
2052
  if (typeof k === 'string')
1760
2053
  new_obj[translate(k, json)] = translate_fields(json[k], translate)
@@ -1785,6 +2078,21 @@
1785
2078
  escape_to_nelson = (obj) => translate_fields(obj, escape_field_to_nelson)
1786
2079
  unescape_from_nelson = (obj) => translate_fields(obj, unescape_field_from_nelson)
1787
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
+
1788
2096
  var field_replace = (field, pattern, replacement) => (typeof field === 'string'
1789
2097
  ? field.replace(pattern, replacement)
1790
2098
  : field)
@@ -1893,6 +2201,7 @@
1893
2201
  this.has = function (k, v) { return hash[k] && hash[k][v] }
1894
2202
  this.has_any = function (k) { return counts[k] }
1895
2203
  this.del = this.delete // for compatibility; remove this soon
2204
+ this.empty = function () { return !Object.values(counts).some(count => count !== 0) }
1896
2205
  }
1897
2206
  function Set () {
1898
2207
  var hash = {}
@@ -2234,14 +2543,15 @@
2234
2543
 
2235
2544
  // Make these private methods accessible
2236
2545
  var api = ['cache backup_cache get set forget del fire dirty get_once',
2237
- 'subspace bindings run_handler bind unbind reactive uncallback',
2546
+ 'old_subspace new_subspace bindings run_handler bind unbind reactive uncallback',
2238
2547
  'versions new_version',
2239
- 'aget',
2548
+ 'aget client_bus_for',
2240
2549
  'funk_key funk_name funks key_id key_name id',
2241
2550
  'pending_gets gets_in gets_out loading_keys loading once',
2242
2551
  'global_funk busses rerunnable_funks',
2243
- 'translate_keys translate_links apply_patch',
2552
+ 'link raw translate_keys translate_links apply_patch',
2244
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',
2245
2555
  'ws_mount net_automount message_method',
2246
2556
  'parse Set One_To_Many clone extend deep_map deep_equals prune validate sorta_diff log deps symbols'
2247
2557
  ].join(' ').split(' ')