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/client-library.js +7 -3
- package/extras/tests.js +206 -40
- package/package.json +2 -2
- package/server-library.js +222 -232
- package/statebus.js +457 -209
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
|
-
|
|
110
|
-
|
|
111
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
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 '
|
|
688
|
-
handler.args['
|
|
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 '
|
|
708
|
-
handler.args['
|
|
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
|
-
|
|
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:
|
|
851
|
+
key: handler.pattern,
|
|
806
852
|
method: method,
|
|
807
|
-
|
|
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)
|
|
879
|
-
function rest_arg () {
|
|
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
|
-
|
|
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
|
|
897
|
-
// We're currently calling that (param1,param2:val2) part "
|
|
898
|
-
// that it exists in the
|
|
899
|
-
function
|
|
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
|
-
//
|
|
907
|
-
//
|
|
908
|
-
function
|
|
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
|
|
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
|
|
929
|
-
else if (v.startsWith("(")) ret[k] =
|
|
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
|
-
|
|
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.
|
|
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 '
|
|
1011
|
-
args[func.args[k]] = func.statebus_binding
|
|
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 '
|
|
1027
|
-
args[func.args[k]] =
|
|
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.
|
|
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
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
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
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
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
|
-
|
|
1480
|
-
return new Proxy(
|
|
1481
|
-
get: function
|
|
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 ===
|
|
1522
|
+
if (k === symbols.raw)
|
|
1523
|
+
return o
|
|
1524
|
+
if (typeof k === 'symbol')
|
|
1485
1525
|
return undefined
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
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
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
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
|
-
|
|
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
|
|
1570
|
+
// return translate_fields(o, unescape_field_from_bus)
|
|
1504
1571
|
// }
|
|
1505
1572
|
})
|
|
1506
1573
|
}
|
|
1507
|
-
bus.state = make_proxy()
|
|
1508
1574
|
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
//
|
|
1512
|
-
//
|
|
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
|
-
// -
|
|
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
|
-
|| (
|
|
2052
|
-
|
|
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
|
-
|| (
|
|
2092
|
-
|
|
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
|
-
'
|
|
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(' ')
|