statebus 7.0.33 → 7.1.1

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,140 +1452,243 @@
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')
1384
- }
1385
- function make_proxy () {
1386
- // var custom_inspect_symbol = nodejs && require('util').inspect.custom,
1387
- // custom_inspect = function (depth, options) {
1388
- // return '3333333333';// util.inspect(bus.unescape_from_nelson(this), options)
1389
- // }
1390
- function item_proxy (base, path, o) {
1391
-
1392
- // Primitives pass through unscathed
1393
- if (typeof o === 'number'
1394
- || typeof o === 'string'
1395
- || typeof o === 'boolean'
1396
- || o === undefined
1397
- || o === null
1398
- || typeof o === 'function')
1399
-
1400
- return o
1401
-
1402
- // We recursively descend through {key: ...} links
1403
- if (typeof o === 'object' && 'link' in o) {
1404
- var new_base = bus.get(o.link)
1405
- return item_proxy(new_base, '', new_base.val)
1406
- }
1455
+ raw: Symbol('raw'),
1456
+ link: Symbol('link')
1457
+ }
1407
1458
 
1459
+ // The top-level Proxy object holds HTTP resources
1460
+ var top_level_proxy = new Proxy(cache, {
1461
+ get: function get(o, k) {
1462
+ if (k === symbols.is_proxy)
1463
+ return true
1464
+ if (k === symbols.raw)
1465
+ return raw_proxy()
1466
+ if (k === 'inspect' || k === 'valueOf' || typeof k === 'symbol')
1467
+ return undefined
1468
+ bogus_check(k)
1469
+ var base = bus.get(k)
1470
+ return json_proxy(base, '', base.val)
1471
+ },
1472
+ set: function set(o, key, val) {
1473
+ bus.set({
1474
+ key: key,
1475
+ val: escape_json_to_bus(val)
1476
+ })
1477
+ return true
1478
+ },
1479
+ deleteProperty: function del (o, k) {
1480
+ bus.delete(escape_field_to_bus(k))
1481
+ return true // Report success to Proxy
1482
+ }
1483
+ })
1408
1484
 
1409
- // For function proxies:
1410
- //
1411
- // // Javascript won't let us function call a proxy unless the
1412
- // // "target" is a function. So we make a dummy target, and
1413
- // // don't use it.
1414
- // var dummy = function () {}
1415
-
1416
-
1417
- return new Proxy(o, {
1418
- get: function (o, k) {
1419
- if (k === 'inspect' || k === 'valueOf')
1420
- return undefined
1421
- // if (custom_inspect && k === custom_inspect)
1422
- // return custom_inspect
1423
- if (k === symbols.is_proxy)
1424
- return true
1425
- if (typeof k === 'symbol')
1426
- return undefined
1427
-
1428
- // Compute the new path
1429
- var new_path = path + '[' + JSON.stringify(k) + ']'
1430
- return item_proxy(base, new_path, o[escape_field_to_bus(escape_field_to_nelson(k))])
1431
- },
1432
- 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
1435
- var new_path = path + '[' + JSON.stringify(k) + ']'
1436
- bus.set(
1437
- base,
1438
- // Forward the patches too
1439
- {patches: [{unit: 'json',
1440
- range: new_path,
1441
- content: JSON.stringify(v)}]}
1442
- )
1443
- return true
1444
- },
1445
- has: function (o, k) {
1446
- // if (custom_inspect && k === custom_inspect)
1447
- // return true
1448
- return o.hasOwnProperty(escape_field_to_bus(escape_field_to_nelson(k)))
1449
- },
1450
- ownKeys: function () {
1451
- return Object.keys(o).map(unescape_field_from_nelson).map(unescape_field_from_bus)
1452
- },
1453
- getOwnPropertyDescriptor: function (target, key) {
1454
- return { enumerable: true, configurable: true, value: this.get(o, key) }
1455
- },
1456
- deleteProperty: function del (o, k) {
1457
- var new_path = path + '[' + JSON.stringify(k) + ']'
1458
- delete o[escape_field_to_bus(escape_field_to_nelson(k))]
1459
- bus.set(
1460
- base,
1461
- // Forward the patches too
1462
- {patches: [{unit: 'json',
1463
- range: new_path,
1464
- content: undefined}]}
1465
- )
1466
- return true // Report success to Proxy
1467
- }
1468
- // For function proxies:
1469
- //
1470
- // apply: function apply (o, This, args) {
1471
- // return translate_fields(o, unescape_field_from_bus)
1472
- // }
1473
- })}
1485
+ // The proxy within; a recursive tree holds each piece of JSON in a resource
1486
+ function json_proxy (base, path, o) {
1487
+
1488
+ // Primitives pass through unscathed
1489
+ if (typeof o === 'number'
1490
+ || typeof o === 'string'
1491
+ || typeof o === 'boolean'
1492
+ || o === undefined
1493
+ || o === null
1494
+ || typeof o === 'function')
1495
+
1496
+ return o
1497
+
1498
+ // We recursively descend through {key: ...} links
1499
+ if (typeof o === 'object' && 'link' in o) {
1500
+ var new_base = bus.get(o.link)
1501
+ return json_proxy(new_base, '', new_base.val)
1502
+ // return link(o.link)
1503
+ }
1474
1504
 
1475
1505
 
1476
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.
1477
1511
  // var dummy = function () {}
1478
1512
 
1479
- // The top-level Proxy object holds HTTP resources
1480
- return new Proxy(cache, {
1481
- get: function get(o, k) {
1513
+
1514
+ return new Proxy(o, {
1515
+ get: function (o, k) {
1516
+ if (k === 'inspect' || k === 'valueOf')
1517
+ return undefined
1518
+ // if (custom_inspect && k === custom_inspect)
1519
+ // return custom_inspect
1482
1520
  if (k === symbols.is_proxy)
1483
1521
  return true
1484
- if (k === 'inspect' || k === 'valueOf' || typeof k === 'symbol')
1522
+ if (k === symbols.raw)
1523
+ return o
1524
+ if (typeof k === 'symbol')
1485
1525
  return undefined
1486
- bogus_check(k)
1487
- var base = bus.get(k)
1488
- return item_proxy(base, '', base.val)
1526
+
1527
+ // Compute the new path
1528
+ var new_path = path + '[' + JSON.stringify(k) + ']'
1529
+ return json_proxy(base, new_path, o[escape_field_json_to_bus(k)])
1489
1530
  },
1490
- set: function set(o, key, val) {
1491
- bus.set({
1492
- key: key,
1493
- val: translate_fields(val, field => escape_field_to_bus(escape_field_to_nelson(field)))
1494
- })
1531
+ set: function (o, k, v) {
1532
+ var value = escape_json_to_bus(v)
1533
+ o[escape_field_json_to_bus(k)] = value
1534
+ var new_path = path + '[' + JSON.stringify(k) + ']'
1535
+ bus.set.sync(
1536
+ base,
1537
+ // Forward the patches too
1538
+ {patches: [{unit: 'json',
1539
+ range: new_path,
1540
+ content: JSON.stringify(v)}]}
1541
+ )
1495
1542
  return true
1496
1543
  },
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
+ },
1497
1555
  deleteProperty: function del (o, k) {
1498
- bus.delete(escape_field_to_bus(k))
1556
+ var new_path = path + '[' + JSON.stringify(k) + ']'
1557
+ delete o[escape_field_json_to_bus(k)]
1558
+ bus.set(
1559
+ base,
1560
+ // Forward the patches too
1561
+ {patches: [{unit: 'json',
1562
+ range: new_path,
1563
+ content: undefined}]}
1564
+ )
1499
1565
  return true // Report success to Proxy
1500
- },
1566
+ }
1501
1567
  // For function proxies:
1568
+ //
1502
1569
  // apply: function apply (o, This, args) {
1503
- // return 'fluffy'
1570
+ // return translate_fields(o, unescape_field_from_bus)
1504
1571
  // }
1505
1572
  })
1506
1573
  }
1507
- bus.state = make_proxy()
1508
1574
 
1509
- // function link (url) {
1510
- // var result = bus.get(url)
1511
- // result[symbols.is_link] = true
1512
- // return result
1575
+ bus.state = top_level_proxy
1576
+
1577
+ // This is temporary code to wrap the cache as a flat key/valuel store
1578
+ // that returns raw objects, dereferencing .val. We can remove it once we
1579
+ // remove the internal .val stuff from statebus.
1580
+ function raw_proxy () {
1581
+ return new Proxy(cache, {
1582
+ get: function get(o, k) { return bus.cache[k].val },
1583
+ set: function set(o, key, val) {
1584
+ return false
1585
+ },
1586
+ deleteProperty: function del (o, k) {
1587
+ return false
1588
+ }
1589
+ })
1590
+ }
1591
+
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 {[symbols.link]: url}
1601
+ }
1602
+
1603
+ // // The proxy object for links. Disabled for now.
1604
+ // // This type of link has to be function called, as link(), to dereference.
1605
+ // function proxy_link (url) {
1606
+ // function follow_link () {}
1607
+ // return new Proxy(follow_link, {
1608
+ // get: function (o, k) {
1609
+ // console.log('get', k)
1610
+ // if (k === 'inspect' || k === 'valueOf')
1611
+ // return undefined
1612
+ // // if (custom_inspect && k === custom_inspect)
1613
+ // // return custom_inspect
1614
+ // if (k === symbols.is_proxy)
1615
+ // return true
1616
+ // if (k === symbols.link)
1617
+ // return true
1618
+ // if (k === symbols.raw)
1619
+ // return {link: url}
1620
+ // if (typeof k === 'symbol')
1621
+ // return undefined
1622
+
1623
+ // if (k === 'link')
1624
+ // return url
1625
+
1626
+ // return follow_link[k]
1627
+
1628
+ // // return undefined
1629
+ // },
1630
+ // set: function (o, k, v) {
1631
+ // if (k === 'link') {
1632
+ // url = v
1633
+ // }
1634
+
1635
+ // console.assert(false, "Have not fully implemented setting links yet")
1636
+
1637
+ // // Todo: update the containing proxy object with bus.set.sync
1638
+ // // else return
1639
+ // // var value = escape_json_to_bus(v)
1640
+ // // o[escape_field_json_to_bus(k)] = value
1641
+ // // var new_path = path + '[' + JSON.stringify(k) + ']'
1642
+ // // bus.set.sync(
1643
+ // // base,
1644
+ // // // Forward the patches too
1645
+ // // {patches: [{unit: 'json',
1646
+ // // range: new_path,
1647
+ // // content: JSON.stringify(v)}]}
1648
+ // // )
1649
+ // return true
1650
+ // },
1651
+ // has: function (o, k) {
1652
+ // return k === 'link'
1653
+ // },
1654
+ // ownKeys: function () {
1655
+ // console.log('ownkeys')
1656
+ // return ['link', 'arguments', 'caller', 'prototype']
1657
+ // },
1658
+ // getOwnPropertyDescriptor: function (target, key) {
1659
+ // console.log('getownpropertydescriptor', key)
1660
+ // return {
1661
+ // enumerable: true,
1662
+ // configurable: true,
1663
+ // value: key === 'link' ? url : undefined
1664
+ // }
1665
+ // },
1666
+ // // deleteProperty: function del (o, k) {
1667
+ // // var new_path = path + '[' + JSON.stringify(k) + ']'
1668
+ // // delete o[escape_field_json_to_bus(k)]
1669
+ // // bus.set(
1670
+ // // base,
1671
+ // // // Forward the patches too
1672
+ // // {patches: [{unit: 'json',
1673
+ // // range: new_path,
1674
+ // // content: undefined}]}
1675
+ // // )
1676
+ // // return true // Report success to Proxy
1677
+ // // }
1678
+
1679
+ // // For function proxy:
1680
+ // apply: function apply (o, This, args) {
1681
+ // // console.log('Descending into the link', url, '!')
1682
+ // return top_level_proxy[url]
1683
+ // }
1684
+ // })
1513
1685
  // }
1686
+ function raw (proxy) {
1687
+ if (!(typeof proxy === 'object' && proxy[symbols.is_proxy]))
1688
+ return proxy
1514
1689
 
1690
+ return proxy[symbols.raw]
1691
+ }
1515
1692
 
1516
1693
  // So chrome can print out proxy objects decently
1517
1694
  if (!nodejs)
@@ -1527,7 +1704,55 @@
1527
1704
  hasBody: function (x) {return false}
1528
1705
  }]
1529
1706
 
1530
- // ******************
1707
+
1708
+ // ************************************************
1709
+ //
1710
+ // Every dialogue we have with a peer gets an ID.
1711
+ // This `bus.dialogues` variable maps:
1712
+ //
1713
+ // dialogue ID -> send_function
1714
+ //
1715
+ // for each peer we are in dialogue with.
1716
+ //
1717
+ // One can use this to make sure we don't send an update
1718
+ // back to the same peer that sent it to us.
1719
+
1720
+ bus.dialogues = {}
1721
+
1722
+
1723
+ // ************************************************
1724
+ // Custom clients
1725
+ var client_counter = 0
1726
+ var client_busses = {}
1727
+ function client_bus_for (client_id) {
1728
+ if (!bus.custom_clients) return bus
1729
+
1730
+ // Do we have a bus for this client yet?
1731
+ if (client_busses[client_id])
1732
+
1733
+ // Return existing bus
1734
+ return client_busses[client_id]
1735
+
1736
+ else {
1737
+
1738
+ // Make a new bus
1739
+ var client_bus = make_bus()
1740
+ client_bus.label = 'client-' + client_counter++
1741
+ client_bus.master = bus
1742
+
1743
+ // Initialize the client-specific handlers
1744
+ bus.custom_clients(client_bus, client_id)
1745
+
1746
+ // Remember it, and auto-delete it when it's no longer being used
1747
+ client_busses[client_id] = client_bus
1748
+ client_bus.auto_delete = () => delete client_busses[client_id]
1749
+
1750
+ return client_bus
1751
+ }
1752
+ }
1753
+
1754
+
1755
+ // ************************************************
1531
1756
  // Network client
1532
1757
  function get_domain (key) { // Returns e.g. "state://foo.com"
1533
1758
  var m = key.match(/^i?statei?\:\/\/(([^:\/?#]*)(?:\:([0-9]+))?)/)
@@ -1543,9 +1768,10 @@
1543
1768
  function ws_mount (prefix, url, client_creds) {
1544
1769
  // Local: state://foo.com/* or /*
1545
1770
  var preprefix = prefix.slice(0,-1)
1771
+ var bus = this
1546
1772
  var is_absolute = /^i?statei?:\/\//
1773
+ var creds = client_creds || (bus.client_creds && bus.client_creds(url))
1547
1774
  var has_prefix = new RegExp('^' + preprefix)
1548
- var bus = this
1549
1775
  var sock
1550
1776
  var attempts = 0
1551
1777
  var outbox = []
@@ -1625,7 +1851,6 @@
1625
1851
  set(peers)
1626
1852
 
1627
1853
  // Login
1628
- var creds = client_creds || (bus.client_creds && bus.client_creds(url))
1629
1854
  if (creds) {
1630
1855
  var i = []
1631
1856
  function intro (o) {i.push(JSON.stringify({set: o}))}
@@ -1831,8 +2056,10 @@
1831
2056
  }
1832
2057
 
1833
2058
  // Recurse through each property on objects
1834
- else if (typeof json === 'object' && json !== null) {
2059
+ else if (typeof json === 'object' && json !== null && !(symbols.link in json)) {
1835
2060
  var new_obj = {}
2061
+
2062
+ // Regular objects get their fields translated
1836
2063
  for (var k in json)
1837
2064
  if (typeof k === 'string')
1838
2065
  new_obj[translate(k, json)] = translate_fields(json[k], translate)
@@ -1849,7 +2076,7 @@
1849
2076
 
1850
2077
  // Three levels of escaping through state:
1851
2078
  //
1852
- // - statebus-internal escapes key -> _key
2079
+ // - bus internal escapes key -> _key
1853
2080
  // - nelSON escapes link -> _link
1854
2081
  // - JSON
1855
2082
  //
@@ -1863,6 +2090,21 @@
1863
2090
  escape_to_nelson = (obj) => translate_fields(obj, escape_field_to_nelson)
1864
2091
  unescape_from_nelson = (obj) => translate_fields(obj, unescape_field_from_nelson)
1865
2092
 
2093
+ var escape_field_json_to_bus = (field) =>
2094
+ escape_field_to_bus(escape_field_to_nelson(field))
2095
+ var unescape_field_bus_to_json = (field) =>
2096
+ unescape_field_from_nelson(unescape_field_from_bus(field))
2097
+
2098
+ var escape_json_to_bus = (obj) => {
2099
+ obj = translate_fields(obj, escape_field_json_to_bus)
2100
+ return deep_map(obj, o => (typeof o === 'object' && symbols.link in o
2101
+ ? {link: o[symbols.link]}
2102
+ : o))
2103
+ }
2104
+ var unescape_bus_to_json = (obj) =>
2105
+ translate_fields(obj, field => unescape_field_bus_to_json(field))
2106
+
2107
+
1866
2108
  var field_replace = (field, pattern, replacement) => (typeof field === 'string'
1867
2109
  ? field.replace(pattern, replacement)
1868
2110
  : field)
@@ -1971,6 +2213,7 @@
1971
2213
  this.has = function (k, v) { return hash[k] && hash[k][v] }
1972
2214
  this.has_any = function (k) { return counts[k] }
1973
2215
  this.del = this.delete // for compatibility; remove this soon
2216
+ this.empty = function () { return !Object.values(counts).some(count => count !== 0) }
1974
2217
  }
1975
2218
  function Set () {
1976
2219
  var hash = {}
@@ -2045,12 +2288,17 @@
2045
2288
  return object
2046
2289
  }
2047
2290
  function deep_equals (a, b) {
2291
+ // This code was only needed for link_proxy:
2292
+ // // Treat link proxy objects as regular JSON links. Otherwise, the
2293
+ // // function fields on them confuse everything.
2294
+ // if (a && typeof a === 'object' && a[symbols.link]) a = {link: a.link}
2295
+ // if (b && typeof b === 'object' && b[symbols.link]) b = {link: b.link}
2296
+
2048
2297
  // Equal Primitives?
2049
2298
  if (a === b
2050
2299
  // But because NaN === NaN returns false:
2051
- || (isNaN(a) && isNaN(b)
2052
- // And because isNaN(undefined) returns true:
2053
- && typeof a === 'number' && typeof b === 'number'))
2300
+ || (typeof a === 'number' && typeof b === 'number'
2301
+ && isNaN(a) && isNaN(b)))
2054
2302
  return true
2055
2303
 
2056
2304
  // Equal Arrays?
@@ -2088,9 +2336,8 @@
2088
2336
  // Equal Primitives?
2089
2337
  if (a === b
2090
2338
  // But because NaN === NaN returns false:
2091
- || (isNaN(a) && isNaN(b)
2092
- // And because isNaN(undefined) returns true:
2093
- && typeof a === 'number' && typeof b === 'number'))
2339
+ || (typeof a === 'number' && typeof b === 'number'
2340
+ && isNaN(a) && isNaN(b)))
2094
2341
  return null
2095
2342
 
2096
2343
  // Equal Arrays?
@@ -2312,14 +2559,15 @@
2312
2559
 
2313
2560
  // Make these private methods accessible
2314
2561
  var api = ['cache backup_cache get set forget del fire dirty get_once',
2315
- 'subspace bindings run_handler bind unbind reactive uncallback',
2562
+ 'old_subspace new_subspace bindings run_handler bind unbind reactive uncallback',
2316
2563
  'versions new_version',
2317
- 'aget',
2564
+ 'aget client_bus_for',
2318
2565
  'funk_key funk_name funks key_id key_name id',
2319
2566
  'pending_gets gets_in gets_out loading_keys loading once',
2320
2567
  'global_funk busses rerunnable_funks',
2321
- 'translate_keys translate_links apply_patch',
2568
+ 'link raw translate_keys translate_links apply_patch',
2322
2569
  'translate_fields escape_to_bus unescape_from_bus escape_to_nelson unescape_from_nelson',
2570
+ 'escape_field_json_to_bus unescape_field_bus_to_json escape_json_to_bus unescape_bus_to_json',
2323
2571
  'ws_mount net_automount message_method',
2324
2572
  'parse Set One_To_Many clone extend deep_map deep_equals prune validate sorta_diff log deps symbols'
2325
2573
  ].join(' ').split(' ')