statebus 6.1.9 → 6.1.13
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.js +76 -11
- package/extras/tests.js +133 -0
- package/package.json +2 -1
- package/server.js +76 -14
- package/statebus.js +447 -315
package/client.js
CHANGED
|
@@ -57,6 +57,62 @@
|
|
|
57
57
|
return {clientid: me.client}
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
+
function braid_http_mount (prefix, url) {
|
|
61
|
+
var preprefix = prefix.slice(0,-1)
|
|
62
|
+
var has_prefix = new RegExp('^' + preprefix)
|
|
63
|
+
var is_absolute = /^https?:\/\//
|
|
64
|
+
var client_fetched_keys = new bus.Set()
|
|
65
|
+
if (url[url.length-1]=='/') url = url.substr(0,url.length-1)
|
|
66
|
+
function add_prefix (key) {
|
|
67
|
+
return is_absolute.test(key) ? key : preprefix + key }
|
|
68
|
+
function rem_prefix (key) {
|
|
69
|
+
return has_prefix.test(key) ? key.substr(preprefix.length) : key }
|
|
70
|
+
function add_prefixes (obj) {
|
|
71
|
+
return bus.translate_keys(bus.clone(obj), add_prefix) }
|
|
72
|
+
function rem_prefixes (obj) {
|
|
73
|
+
return bus.translate_keys(bus.clone(obj), rem_prefix) }
|
|
74
|
+
|
|
75
|
+
bus(prefix).to_save = function (obj, t) {
|
|
76
|
+
console.log('saving', prefix, obj)
|
|
77
|
+
bus.save.fire(obj)
|
|
78
|
+
|
|
79
|
+
// var x = {save: obj}
|
|
80
|
+
// if (t.version) x.version = t.version
|
|
81
|
+
// if (t.parents) x.parents = t.parents
|
|
82
|
+
// if (t.patch) x.patch = t.patch
|
|
83
|
+
// if (t.patch) x.save = rem_prefix(x.save.key)
|
|
84
|
+
|
|
85
|
+
braid_fetch(url + obj.key,
|
|
86
|
+
{
|
|
87
|
+
method: 'put',
|
|
88
|
+
body: JSON.stringify(obj.val)
|
|
89
|
+
})
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
bus(prefix).to_fetch = function (key, t) {
|
|
93
|
+
try {
|
|
94
|
+
braid_fetch(url + rem_prefix(key),
|
|
95
|
+
{
|
|
96
|
+
method: 'get',
|
|
97
|
+
subscribe: true
|
|
98
|
+
}
|
|
99
|
+
).andThen( x => {
|
|
100
|
+
t.return({
|
|
101
|
+
key,
|
|
102
|
+
val: add_prefixes(JSON.parse(x.body))
|
|
103
|
+
})
|
|
104
|
+
})
|
|
105
|
+
} catch (e) {
|
|
106
|
+
console.error('Braid-HTTP network error:', e)
|
|
107
|
+
}
|
|
108
|
+
client_fetched_keys.add(key)
|
|
109
|
+
}
|
|
110
|
+
bus(prefix).to_forget = function (key) {
|
|
111
|
+
// send({forget: key}),
|
|
112
|
+
client_fetched_keys.delete(key)
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
60
116
|
|
|
61
117
|
// ****************
|
|
62
118
|
// Manipulate Localstorage
|
|
@@ -382,16 +438,22 @@
|
|
|
382
438
|
react_render = React.version >= '0.14.' ? ReactDOM.render : React.render
|
|
383
439
|
make_client_statebus_maker()
|
|
384
440
|
window.bus = window.statebus()
|
|
385
|
-
|
|
386
|
-
|
|
441
|
+
bus.label = 'bus'
|
|
442
|
+
if (clientjs_option('braid_mode_test'))
|
|
443
|
+
bus.state = bus.braid_proxy()
|
|
444
|
+
else
|
|
445
|
+
window.sb = bus.sb
|
|
387
446
|
statebus.widget = React_View
|
|
388
447
|
statebus.create_react_class = React_View
|
|
389
448
|
statebus.createReactClass = React_View
|
|
390
449
|
|
|
391
450
|
improve_react()
|
|
392
451
|
window.ignore_flashbacks = false
|
|
393
|
-
if (statebus_server !== 'none')
|
|
452
|
+
if (statebus_server !== 'none') {
|
|
394
453
|
bus.net_mount ('/*', statebus_server)
|
|
454
|
+
if (clientjs_option('braid_mode_test'))
|
|
455
|
+
braid_http_mount ('http/*', statebus_server)
|
|
456
|
+
}
|
|
395
457
|
|
|
396
458
|
if (window.statebus_backdoor) {
|
|
397
459
|
window.master = statebus()
|
|
@@ -399,16 +461,19 @@
|
|
|
399
461
|
}
|
|
400
462
|
bus.net_automount()
|
|
401
463
|
|
|
402
|
-
//
|
|
403
|
-
|
|
404
|
-
|
|
464
|
+
// This /new/* code is deprecated
|
|
465
|
+
if (!clientjs_option('braid_mode_test')) {
|
|
466
|
+
bus('/new/*').to_save = function (o) {
|
|
467
|
+
if (o.key.split('/').length > 3) return
|
|
405
468
|
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
469
|
+
var old_key = o.key
|
|
470
|
+
o.key = old_key + '/' + Math.random().toString(36).substring(2,12)
|
|
471
|
+
statebus.cache[o.key] = o
|
|
472
|
+
delete statebus.cache[old_key]
|
|
473
|
+
bus.save(o)
|
|
474
|
+
}
|
|
411
475
|
}
|
|
476
|
+
|
|
412
477
|
load_coffee()
|
|
413
478
|
|
|
414
479
|
statebus.compile_coffee = compile_coffee
|
package/extras/tests.js
CHANGED
|
@@ -294,6 +294,43 @@ test(function url_translation (done) {
|
|
|
294
294
|
done()
|
|
295
295
|
})
|
|
296
296
|
|
|
297
|
+
test(function translate_fields (done) {
|
|
298
|
+
// Translate Statebus -> Proxy format
|
|
299
|
+
var tests1 = [
|
|
300
|
+
[{key: 'foo', link: 'boo'}, {key: 'foo', link: 'boo'}],
|
|
301
|
+
[{_key: 'foo', link: 'boo'}, {key: 'foo', link: 'boo'}],
|
|
302
|
+
[{__key: 'foo', link: 'boo'}, {_key: 'foo', link: 'boo'}],
|
|
303
|
+
[[{_key: 'foo', link: 'boo'}], [{key: 'foo', link: 'boo'}]],
|
|
304
|
+
[{a: [{_key: 'foo'}]}, {a: [{key: 'foo'}]}]
|
|
305
|
+
]
|
|
306
|
+
|
|
307
|
+
// Translate Proxy -> Statebus format
|
|
308
|
+
var tests2 = [
|
|
309
|
+
[{key: 'foo', link: 'boo'}, {_key: 'foo', link: 'boo'}],
|
|
310
|
+
[{_key: 'foo', link: 'boo'}, {__key: 'foo', link: 'boo'}],
|
|
311
|
+
[[{_key: 'foo', link: ['boo']}], [{__key: 'foo', link: ['boo']}]]
|
|
312
|
+
]
|
|
313
|
+
|
|
314
|
+
// Test 1
|
|
315
|
+
var tests = tests1
|
|
316
|
+
for (var i=0; i<tests.length; i++) {
|
|
317
|
+
var trans = bus.translate_fields(tests[i][0], bus.keyed_2_proxied)
|
|
318
|
+
assert(bus.deep_equals(trans, tests[i][1]),
|
|
319
|
+
'Bad translation1: ' + i + ' ' + JSON.stringify(trans))
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// Test 2
|
|
323
|
+
tests = tests2
|
|
324
|
+
for (var i=0; i<tests2.length; i++) {
|
|
325
|
+
var trans = bus.translate_fields(tests[i][0], bus.proxied_2_keyed)
|
|
326
|
+
assert(bus.deep_equals(trans, tests[i][1]),
|
|
327
|
+
'Bad translation2: ' + i + ' ' + JSON.stringify(trans))
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
log('Passed translate_fields')
|
|
331
|
+
done()
|
|
332
|
+
})
|
|
333
|
+
|
|
297
334
|
test(function basics (done) {
|
|
298
335
|
bus('basic wait').to_fetch = function () {
|
|
299
336
|
setTimeout(function () {bus.save.fire({key:'basic wait', a:1})},
|
|
@@ -834,6 +871,102 @@ test(function proxies2 (done) {
|
|
|
834
871
|
// Getting a normal property should do a fetch
|
|
835
872
|
})
|
|
836
873
|
|
|
874
|
+
test(function braid_proxies (done) {
|
|
875
|
+
var bus = require('../statebus').serve({braid_mode_test: true,
|
|
876
|
+
file_store: false})
|
|
877
|
+
var state = bus.state
|
|
878
|
+
|
|
879
|
+
assert(state.array === undefined)
|
|
880
|
+
assert(state.bar === undefined)
|
|
881
|
+
|
|
882
|
+
state.array = []
|
|
883
|
+
log('array:', state.array)
|
|
884
|
+
assert(state.array.length === 0)
|
|
885
|
+
state.array[0] = 1
|
|
886
|
+
log('array:', state.array)
|
|
887
|
+
assert(state.array.length === 1)
|
|
888
|
+
state.bar = {}
|
|
889
|
+
log('bar:', state.bar)
|
|
890
|
+
state.bar = {a: 1}
|
|
891
|
+
log('bar:', state.bar)
|
|
892
|
+
assert(state.bar.a === 1)
|
|
893
|
+
state.bar.a = state.array
|
|
894
|
+
log('bar:', state.bar)
|
|
895
|
+
state.array[1] = 2
|
|
896
|
+
log('array:', state.array)
|
|
897
|
+
log('bar:', state.bar)
|
|
898
|
+
assert(state.bar.a[1] !== 2,
|
|
899
|
+
"Array ref linked.\n\t"
|
|
900
|
+
+ JSON.stringify(bus.cache.bar) + '\n\t'
|
|
901
|
+
+ JSON.stringify(bus.cache.array))
|
|
902
|
+
|
|
903
|
+
state.undefining = undefined
|
|
904
|
+
assert(state.undefining === undefined)
|
|
905
|
+
state.undefining = {a: undefined}
|
|
906
|
+
log('state.undefining =', state.undefining)
|
|
907
|
+
// assert(state.undefining.a === undefined)
|
|
908
|
+
// TODO: fix https://github.com/invisible-college/statebus/issues/34
|
|
909
|
+
state.undefining = {}
|
|
910
|
+
assert(!('a' in state.undefining))
|
|
911
|
+
state.undefining.a = undefined
|
|
912
|
+
assert('a' in state.undefining)
|
|
913
|
+
assert(state.undefining.a === undefined)
|
|
914
|
+
|
|
915
|
+
state.b = {a: undefined}
|
|
916
|
+
assert('a' in state.b)
|
|
917
|
+
assert(state.b.a === undefined)
|
|
918
|
+
delete state.b.a
|
|
919
|
+
// TODO: https://github.com/invisible-college/statebus/issues/34
|
|
920
|
+
assert(!('a' in state.b))
|
|
921
|
+
assert(state.b.a === undefined)
|
|
922
|
+
|
|
923
|
+
return done()
|
|
924
|
+
|
|
925
|
+
/*
|
|
926
|
+
Things I want to test:
|
|
927
|
+
|
|
928
|
+
- Setting nested items
|
|
929
|
+
- Escaping their fields
|
|
930
|
+
- Calling fetch on them
|
|
931
|
+
- Converting state[..] to keyed objects internally
|
|
932
|
+
- has() potentially doing a fetch, or loading()
|
|
933
|
+
- set() returning a proxy object
|
|
934
|
+
- console output
|
|
935
|
+
- node AND chrome
|
|
936
|
+
- having nice colors and distinctions and shit
|
|
937
|
+
*/
|
|
938
|
+
|
|
939
|
+
state.foo = 3
|
|
940
|
+
// This should set into _
|
|
941
|
+
console.assert(bus.validate(bus.cache.foo,
|
|
942
|
+
{key: 'foo', _: 3}))
|
|
943
|
+
|
|
944
|
+
|
|
945
|
+
state.foo = {a: 5}
|
|
946
|
+
// This should set directly on it
|
|
947
|
+
console.assert(bus.validate(bus.cache.foo,
|
|
948
|
+
{key: 'foo', a: 5}))
|
|
949
|
+
state.foo.b = 6
|
|
950
|
+
console.assert(bus.validate(bus.cache.foo,
|
|
951
|
+
{key: 'foo', b: 6}))
|
|
952
|
+
|
|
953
|
+
state.bar = [3]
|
|
954
|
+
state.foo = {a: 3, bar: state.bar}
|
|
955
|
+
|
|
956
|
+
bus(() => {
|
|
957
|
+
state.foo // foo triggers re-render
|
|
958
|
+
state.foo.bar // bar triggers re-render
|
|
959
|
+
state.foo.a // triggers re-render too
|
|
960
|
+
})
|
|
961
|
+
|
|
962
|
+
// Getting a linked item should do a fetch
|
|
963
|
+
bus(() => {
|
|
964
|
+
state.bar
|
|
965
|
+
})
|
|
966
|
+
|
|
967
|
+
// Getting a normal property should do a fetch
|
|
968
|
+
})
|
|
969
|
+
|
|
837
970
|
test(function only_one (done) {
|
|
838
971
|
bus('only_one/*').to_fetch = function (k) {
|
|
839
972
|
var id = k[k.length-1]
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "statebus",
|
|
3
|
-
"version": "6.1.
|
|
3
|
+
"version": "6.1.13",
|
|
4
4
|
"description": "Synchronize state.",
|
|
5
5
|
"main": "statebus.js",
|
|
6
6
|
"scripts": {
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
"repository": "invisible-college/statebus",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"bcrypt-nodejs": "0.0.3",
|
|
13
|
+
"braidify": "^0.1.18",
|
|
13
14
|
"chokidar": "^3.4.0",
|
|
14
15
|
"coffeescript": "^2.3.1",
|
|
15
16
|
"cookie": "^0.3.1",
|
package/server.js
CHANGED
|
@@ -96,7 +96,7 @@ function import_server (bus, options)
|
|
|
96
96
|
bus.sync_files(x.state_path, x.fs_path)
|
|
97
97
|
})
|
|
98
98
|
|
|
99
|
-
// User will put
|
|
99
|
+
// User will put his routes in here
|
|
100
100
|
bus.express.use('/', bus.http)
|
|
101
101
|
|
|
102
102
|
// Use braidify if it's available
|
|
@@ -107,7 +107,7 @@ function import_server (bus, options)
|
|
|
107
107
|
var braidify = undefined
|
|
108
108
|
}
|
|
109
109
|
|
|
110
|
-
//
|
|
110
|
+
// Mirror state over HTTP
|
|
111
111
|
bus.express.get('*', function (req, res) {
|
|
112
112
|
// Make a temporary client bus
|
|
113
113
|
var cbus = bus.bus_for_http_client(req, res)
|
|
@@ -115,6 +115,7 @@ function import_server (bus, options)
|
|
|
115
115
|
function end_it_all () {
|
|
116
116
|
cbus.forget(key, cb)
|
|
117
117
|
cbus.delete_bus()
|
|
118
|
+
res.end()
|
|
118
119
|
}
|
|
119
120
|
|
|
120
121
|
braidify && braidify(req, res)
|
|
@@ -128,25 +129,86 @@ function import_server (bus, options)
|
|
|
128
129
|
cbus.honk = 'statelog'
|
|
129
130
|
var key = req.path.substr(1)
|
|
130
131
|
cbus.fetch(key, cb = (o) => {
|
|
131
|
-
|
|
132
|
-
|
|
132
|
+
var body = bus.to_http_body(o)
|
|
133
|
+
|
|
134
|
+
// Return 404 if the value is undefined
|
|
135
|
+
if (!body) {
|
|
136
|
+
console.log('no body! time to 404')
|
|
137
|
+
res.statusCode = 404
|
|
138
|
+
res.end()
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Or if we're braid, send via subscription
|
|
142
|
+
else if (braidify)
|
|
143
|
+
res.sendVersion({body})
|
|
144
|
+
|
|
145
|
+
// Or just return the current version
|
|
133
146
|
else
|
|
134
|
-
res.send(
|
|
147
|
+
res.send(body)
|
|
135
148
|
|
|
136
|
-
if
|
|
149
|
+
// And shut down the connection if there's nothing left to do
|
|
150
|
+
if (!braidify || !req.subscribe || !body)
|
|
137
151
|
end_it_all()
|
|
138
152
|
})
|
|
139
153
|
})
|
|
140
154
|
|
|
155
|
+
bus.express.put('*', function (req, res) {
|
|
156
|
+
console.log('Got a put with body', req.body)
|
|
157
|
+
|
|
158
|
+
// Make a temporary client bus
|
|
159
|
+
var cbus = bus.bus_for_http_client(req, res)
|
|
160
|
+
|
|
161
|
+
// Maybe the new state is expressed in patches
|
|
162
|
+
if (req.headers.patches) {
|
|
163
|
+
console.error("We don't actually handle PUT patches yet")
|
|
164
|
+
|
|
165
|
+
// var patches = await req.patches()
|
|
166
|
+
// console.log("...but we see these patches:", patches)
|
|
167
|
+
|
|
168
|
+
res.statusCode = 500
|
|
169
|
+
res.end()
|
|
170
|
+
} else {
|
|
171
|
+
// Otherwise, we assume the body is content-type: json
|
|
172
|
+
if (typeof req.headers['content-type'] !== 'string'
|
|
173
|
+
|| req.headers['content-type'].toLowerCase() !== 'application/json')
|
|
174
|
+
console.error('Error: PUT content-type is not application/json')
|
|
175
|
+
var body = ''
|
|
176
|
+
req.on('data', chunk => {body += chunk.toString()})
|
|
177
|
+
req.on('end', () => {
|
|
178
|
+
try {
|
|
179
|
+
console.log('gonna parse', body)
|
|
180
|
+
var path = req.url.substr(1)
|
|
181
|
+
var obj = bus.from_http_body(path, body)
|
|
182
|
+
} catch (e) {
|
|
183
|
+
console.error('Error: PUT body was not valid json', e)
|
|
184
|
+
res.statusCode = 500
|
|
185
|
+
res.end()
|
|
186
|
+
return
|
|
187
|
+
}
|
|
188
|
+
cbus.save(obj)
|
|
189
|
+
res.statusCode = 200
|
|
190
|
+
res.end()
|
|
191
|
+
})
|
|
192
|
+
}
|
|
193
|
+
})
|
|
194
|
+
|
|
141
195
|
// Set up default linked json converters
|
|
142
|
-
bus.
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
196
|
+
if (bus.options.braid_mode_test) {
|
|
197
|
+
bus.to_http_body = (o) => JSON.stringify(o.val)
|
|
198
|
+
bus.state = bus.braid_proxy()
|
|
199
|
+
} else
|
|
200
|
+
bus.to_http_body = (o) => {
|
|
201
|
+
var unwrap = (Object.keys(o).length === 2
|
|
202
|
+
&& '_' in o
|
|
203
|
+
&& typeof o._ === 'string')
|
|
204
|
+
// To do: translate pointers as keys
|
|
205
|
+
return unwrap ? o._ : JSON.stringify(o)
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
bus.from_http_body = (key, body) => ({
|
|
209
|
+
key,
|
|
210
|
+
val: JSON.parse(body)
|
|
211
|
+
})
|
|
150
212
|
|
|
151
213
|
// Serve Client Coffee
|
|
152
214
|
bus.serve_client_coffee()
|
package/statebus.js
CHANGED
|
@@ -1197,358 +1197,443 @@
|
|
|
1197
1197
|
})
|
|
1198
1198
|
}
|
|
1199
1199
|
|
|
1200
|
-
var sb = (function sb () {
|
|
1201
|
-
// I have the cache behind the scenes
|
|
1202
|
-
// Each proxy has a target object -- the raw data on cache
|
|
1203
|
-
// If we're proxying a {_: ...} singleton then ...
|
|
1204
1200
|
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1201
|
+
// Old Proxy Prototype
|
|
1202
|
+
if (true) {
|
|
1203
|
+
var sb = (function sb () {
|
|
1204
|
+
// I have the cache behind the scenes
|
|
1205
|
+
// Each proxy has a target object -- the raw data on cache
|
|
1206
|
+
// If we're proxying a {_: ...} singleton then ...
|
|
1207
|
+
|
|
1208
|
+
function item_proxy (base, o) {
|
|
1209
|
+
if (typeof o === 'number'
|
|
1210
|
+
|| typeof o === 'string'
|
|
1211
|
+
|| typeof o === 'boolean'
|
|
1212
|
+
|| o === undefined
|
|
1213
|
+
|| o === null
|
|
1214
|
+
|| typeof o === 'function') return o
|
|
1215
|
+
|
|
1216
|
+
return new Proxy(o, {
|
|
1217
|
+
get: function get(o, k) {
|
|
1218
|
+
if (k === 'inspect' || k === 'valueOf' || typeof k === 'symbol')
|
|
1219
|
+
return undefined
|
|
1220
|
+
k = escape_keys(k)
|
|
1221
|
+
return item_proxy(base, o[k])
|
|
1222
|
+
},
|
|
1223
|
+
set: function set(o, k, v) {
|
|
1224
|
+
var result = o[escape_keys(k)] = v
|
|
1225
|
+
bus.save(base)
|
|
1226
|
+
return result
|
|
1227
|
+
},
|
|
1228
|
+
has: function has(o, k) {
|
|
1229
|
+
return o.hasOwnProperty(escape_keys(k))
|
|
1230
|
+
},
|
|
1231
|
+
deleteProperty: function del (o, k) {
|
|
1232
|
+
delete o[escape_keys(k)]
|
|
1233
|
+
},
|
|
1234
|
+
apply: function apply (o, This, args) {
|
|
1235
|
+
return o
|
|
1236
|
+
}
|
|
1237
|
+
})}
|
|
1212
1238
|
|
|
1213
|
-
return new Proxy(
|
|
1239
|
+
return new Proxy(cache, {
|
|
1214
1240
|
get: function get(o, k) {
|
|
1241
|
+
if (k in bogus_keys) return o[k]
|
|
1215
1242
|
if (k === 'inspect' || k === 'valueOf' || typeof k === 'symbol')
|
|
1216
1243
|
return undefined
|
|
1217
|
-
|
|
1218
|
-
|
|
1244
|
+
var raw = bus.fetch(k),
|
|
1245
|
+
obj = raw
|
|
1246
|
+
while (typeof obj == 'object' && '_' in obj) obj = obj._
|
|
1247
|
+
return item_proxy(raw, obj)
|
|
1219
1248
|
},
|
|
1220
1249
|
set: function set(o, k, v) {
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1250
|
+
if (typeof v === 'number'
|
|
1251
|
+
|| typeof v === 'string'
|
|
1252
|
+
|| typeof v === 'boolean'
|
|
1253
|
+
|| v === undefined
|
|
1254
|
+
|| v === null
|
|
1255
|
+
|| typeof v === 'function'
|
|
1256
|
+
|| Array.isArray(v))
|
|
1257
|
+
v = {_:v}
|
|
1258
|
+
else
|
|
1259
|
+
v = bus.clone(v)
|
|
1260
|
+
v.key = k
|
|
1261
|
+
bus.save(v)
|
|
1227
1262
|
},
|
|
1263
|
+
// In future, this might check if there's a .to_fetch function OR
|
|
1264
|
+
// something in the cache:
|
|
1265
|
+
//
|
|
1266
|
+
// has: function has(o, k) {
|
|
1267
|
+
// return k in o
|
|
1268
|
+
// },
|
|
1269
|
+
// ... but I haven't had a need yet.
|
|
1228
1270
|
deleteProperty: function del (o, k) {
|
|
1229
|
-
delete
|
|
1230
|
-
},
|
|
1231
|
-
apply: function apply (o, This, args) {
|
|
1232
|
-
return o
|
|
1271
|
+
bus.delete(escape_keys(k))
|
|
1233
1272
|
}
|
|
1234
|
-
})
|
|
1235
|
-
|
|
1236
|
-
return new Proxy(cache, {
|
|
1237
|
-
get: function get(o, k) {
|
|
1238
|
-
if (k in bogus_keys) return o[k]
|
|
1239
|
-
if (k === 'inspect' || k === 'valueOf' || typeof k === 'symbol')
|
|
1240
|
-
return undefined
|
|
1241
|
-
var raw = bus.fetch(k),
|
|
1242
|
-
obj = raw
|
|
1243
|
-
while (typeof obj == 'object' && '_' in obj) obj = obj._
|
|
1244
|
-
return item_proxy(raw, obj)
|
|
1245
|
-
},
|
|
1246
|
-
set: function set(o, k, v) {
|
|
1247
|
-
if (typeof v === 'number'
|
|
1248
|
-
|| typeof v === 'string'
|
|
1249
|
-
|| typeof v === 'boolean'
|
|
1250
|
-
|| v === undefined
|
|
1251
|
-
|| v === null
|
|
1252
|
-
|| typeof v === 'function'
|
|
1253
|
-
|| Array.isArray(v))
|
|
1254
|
-
v = {_:v}
|
|
1255
|
-
else
|
|
1256
|
-
v = bus.clone(v)
|
|
1257
|
-
v.key = k
|
|
1258
|
-
bus.save(v)
|
|
1259
|
-
},
|
|
1260
|
-
// In future, this might check if there's a .to_fetch function OR
|
|
1261
|
-
// something in the cache:
|
|
1262
|
-
//
|
|
1263
|
-
// has: function has(o, k) {
|
|
1264
|
-
// return k in o
|
|
1265
|
-
// },
|
|
1266
|
-
// ... but I haven't had a need yet.
|
|
1267
|
-
deleteProperty: function del (o, k) {
|
|
1268
|
-
bus.delete(encode_field(k))
|
|
1269
|
-
}
|
|
1270
|
-
})
|
|
1271
|
-
})()
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
// ******** State (Proxy) API *********
|
|
1275
|
-
//
|
|
1276
|
-
// The top-level state[..] object translates pointers of the
|
|
1277
|
-
// special form:
|
|
1278
|
-
//
|
|
1279
|
-
// {key: <s>, _: *}
|
|
1280
|
-
//
|
|
1281
|
-
// Examples:
|
|
1282
|
-
//
|
|
1283
|
-
// state['uninitialized']
|
|
1284
|
-
// >> undefined
|
|
1285
|
-
//
|
|
1286
|
-
// state['uninitialized'] = {}
|
|
1287
|
-
// >> {key: 'uninitialized'}
|
|
1288
|
-
//
|
|
1289
|
-
// state['uninitialized'] = {a: 3}
|
|
1290
|
-
// >> {key: 'uninitialized', a: 3}
|
|
1291
|
-
//
|
|
1292
|
-
// state['uninitialized'] = []
|
|
1293
|
-
// >> {key: 'uninitialized', _: []}
|
|
1294
|
-
//
|
|
1295
|
-
// delete state['uninitialized']
|
|
1296
|
-
// state['uninitialized']
|
|
1297
|
-
// >> undefined
|
|
1298
|
-
//
|
|
1299
|
-
// ** Rules
|
|
1300
|
-
// Setting:
|
|
1301
|
-
// - Escape-translate each field recursively
|
|
1302
|
-
// - If setting an object, put each field directly on it
|
|
1303
|
-
// - If setting anything else, put it into ._
|
|
1304
|
-
//
|
|
1305
|
-
// Getting:
|
|
1306
|
-
// - If it has unescaped fields other than ._, return object with them
|
|
1307
|
-
// - Otherwise, return ._
|
|
1308
|
-
// - Unescape all fields
|
|
1273
|
+
})
|
|
1274
|
+
})()
|
|
1309
1275
|
|
|
1310
|
-
var strict_mode = (function () {return !this})()
|
|
1311
|
-
function pget (base, o, k) {
|
|
1312
|
-
// console.log('pget:', {base, o, k})
|
|
1313
1276
|
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1277
|
+
// ******** State (Proxy) API *********
|
|
1278
|
+
//
|
|
1279
|
+
// The top-level state[..] object translates pointers of the
|
|
1280
|
+
// special form:
|
|
1281
|
+
//
|
|
1282
|
+
// {key: <s>, _: *}
|
|
1283
|
+
//
|
|
1284
|
+
// Examples:
|
|
1285
|
+
//
|
|
1286
|
+
// state['uninitialized']
|
|
1287
|
+
// >> undefined
|
|
1288
|
+
//
|
|
1289
|
+
// state['uninitialized'] = {}
|
|
1290
|
+
// >> {key: 'uninitialized'}
|
|
1291
|
+
//
|
|
1292
|
+
// state['uninitialized'] = {a: 3}
|
|
1293
|
+
// >> {key: 'uninitialized', a: 3}
|
|
1294
|
+
//
|
|
1295
|
+
// state['uninitialized'] = []
|
|
1296
|
+
// >> {key: 'uninitialized', _: []}
|
|
1297
|
+
//
|
|
1298
|
+
// delete state['uninitialized']
|
|
1299
|
+
// state['uninitialized']
|
|
1300
|
+
// >> undefined
|
|
1301
|
+
//
|
|
1302
|
+
// ** Rules
|
|
1303
|
+
// Setting:
|
|
1304
|
+
// - Escape-translate each field recursively
|
|
1305
|
+
// - If setting an object, put each field directly on it
|
|
1306
|
+
// - If setting anything else, put it into ._
|
|
1307
|
+
//
|
|
1308
|
+
// Getting:
|
|
1309
|
+
// - If it has unescaped fields other than ._, return object with them
|
|
1310
|
+
// - Otherwise, return ._
|
|
1311
|
+
// - Unescape all fields
|
|
1312
|
+
|
|
1313
|
+
var strict_mode = (function () {return !this})()
|
|
1314
|
+
function pget (base, o, k) {
|
|
1315
|
+
// console.log('pget:', {base, o, k})
|
|
1316
|
+
|
|
1317
|
+
if (base) {
|
|
1318
|
+
o = o[k]
|
|
1319
|
+
|
|
1320
|
+
// If new base, update and subscribe
|
|
1321
|
+
if (typeof o == 'object' && 'key' in o) {
|
|
1322
|
+
base = o
|
|
1323
|
+
bus.fetch(o.key)
|
|
1324
|
+
}
|
|
1325
|
+
} else {
|
|
1326
|
+
// We are getting from the Root
|
|
1327
|
+
o = bus.fetch(k)
|
|
1328
|
+
// console.log('pget: fetched', k, 'and got', o)
|
|
1319
1329
|
base = o
|
|
1320
|
-
bus.
|
|
1330
|
+
if (bus.validate(o, {key: '*', '?_': '*'})) {
|
|
1331
|
+
// console.log('pget: jumping into the _')
|
|
1332
|
+
o = o._
|
|
1333
|
+
}
|
|
1334
|
+
if (typeof o === 'object' && o.key)
|
|
1335
|
+
base = o
|
|
1321
1336
|
}
|
|
1322
|
-
|
|
1323
|
-
//
|
|
1324
|
-
o
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1337
|
+
|
|
1338
|
+
// Follow symlinks
|
|
1339
|
+
if (typeof o == 'object' && 'key' in o && '_' in o) {
|
|
1340
|
+
// Note: I don't actually need this recursion here, because
|
|
1341
|
+
// recursively linked state cannot be created by the proxy API.
|
|
1342
|
+
// So it can be undefined behavior.
|
|
1343
|
+
var tmp = pget(base, o, '_')
|
|
1344
|
+
base = tmp[0]
|
|
1345
|
+
o = tmp[1]
|
|
1330
1346
|
}
|
|
1331
|
-
if (typeof o === 'object' && o.key)
|
|
1332
|
-
base = o
|
|
1333
|
-
}
|
|
1334
1347
|
|
|
1335
|
-
|
|
1336
|
-
if (typeof o == 'object' && 'key' in o && '_' in o) {
|
|
1337
|
-
// Note: I don't actually need this recursion here, because
|
|
1338
|
-
// recursively linked state cannot be created by the proxy API.
|
|
1339
|
-
// So it can be undefined behavior.
|
|
1340
|
-
var tmp = pget(base, o, '_')
|
|
1341
|
-
base = tmp[0]
|
|
1342
|
-
o = tmp[1]
|
|
1348
|
+
return [base, o]
|
|
1343
1349
|
}
|
|
1344
1350
|
|
|
1345
|
-
|
|
1346
|
-
|
|
1351
|
+
function proxy_encode_val (x) {
|
|
1352
|
+
// Arrays
|
|
1353
|
+
if (Array.isArray(x)) {
|
|
1354
|
+
var result = []
|
|
1355
|
+
for (var i=0; i < x.length; i++)
|
|
1356
|
+
result[i] = proxy_encode_val(x[i])
|
|
1357
|
+
return result
|
|
1358
|
+
}
|
|
1347
1359
|
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1360
|
+
// Objects
|
|
1361
|
+
else if (typeof x === 'object') {
|
|
1362
|
+
// Actual objects need their keys translated
|
|
1363
|
+
var result = {}
|
|
1364
|
+
for (var k in x)
|
|
1365
|
+
result[escape_keys(k)] = proxy_encode_val(x[k])
|
|
1366
|
+
return result
|
|
1367
|
+
}
|
|
1356
1368
|
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
for (var k in x)
|
|
1362
|
-
result[encode_field(k)] = proxy_encode_val(x[k])
|
|
1363
|
-
return result
|
|
1364
|
-
}
|
|
1369
|
+
// Proxieds: already have JSON, stored inside. Return it.
|
|
1370
|
+
else if (typeof x === 'function' && x[symbols.is_proxy]) {
|
|
1371
|
+
return x()
|
|
1372
|
+
}
|
|
1365
1373
|
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
return x()
|
|
1374
|
+
// Everything else return
|
|
1375
|
+
return x
|
|
1369
1376
|
}
|
|
1377
|
+
function proxy_decode_json (json) {
|
|
1378
|
+
// Returns data for proxies
|
|
1379
|
+
// - Remove keys
|
|
1380
|
+
// - Translate
|
|
1381
|
+
|
|
1382
|
+
// Root objects of special form
|
|
1383
|
+
if (bus.validate(json, {key: '*', '_': '*'}))
|
|
1384
|
+
return proxy_decode_json(json._)
|
|
1385
|
+
|
|
1386
|
+
// Arrays
|
|
1387
|
+
if (Array.isArray(json)) {
|
|
1388
|
+
var arr = json.slice()
|
|
1389
|
+
for (var i=0; i<arr.length; i++)
|
|
1390
|
+
arr[i] = proxy_decode_json(arr[i])
|
|
1391
|
+
return arr
|
|
1392
|
+
}
|
|
1370
1393
|
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
// Root objects of special form
|
|
1380
|
-
if (bus.validate(json, {key: '*', '_': '*'}))
|
|
1381
|
-
return proxy_decode_json(json._)
|
|
1394
|
+
// Objects
|
|
1395
|
+
if (typeof json === 'object' && json !== null) {
|
|
1396
|
+
var obj = {}
|
|
1397
|
+
for (var k in json)
|
|
1398
|
+
if (k !== 'key')
|
|
1399
|
+
obj[unescape_keys(k)] = proxy_decode_json(json[k])
|
|
1400
|
+
return obj
|
|
1401
|
+
}
|
|
1382
1402
|
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
var arr = json.slice()
|
|
1386
|
-
for (var i=0; i<arr.length; i++)
|
|
1387
|
-
arr[i] = proxy_decode_json(arr[i])
|
|
1388
|
-
return arr
|
|
1403
|
+
// Other primitives just return
|
|
1404
|
+
return json
|
|
1389
1405
|
}
|
|
1390
1406
|
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1407
|
+
if (nodejs) var util = require('util')
|
|
1408
|
+
function make_proxy (base, o) {
|
|
1409
|
+
if (!symbols)
|
|
1410
|
+
symbols = {is_proxy: Symbol('is_proxy'),
|
|
1411
|
+
get_json: Symbol('get_json'),
|
|
1412
|
+
get_base: Symbol('get_base')}
|
|
1413
|
+
|
|
1414
|
+
if (typeof o !== 'object' || o === null) return o
|
|
1415
|
+
|
|
1416
|
+
function get_json() {
|
|
1417
|
+
// Pop up to parent if this is a singleton array.
|
|
1418
|
+
// We know it's a singleton array if base._ === x(), and base is
|
|
1419
|
+
// of the form {key: *, _: x}
|
|
1420
|
+
if (base && base._ && Object.keys(base).length === 2
|
|
1421
|
+
&& base._ === o)
|
|
1422
|
+
return base
|
|
1423
|
+
|
|
1424
|
+
// Otherwise return x's JSON.
|
|
1425
|
+
return o
|
|
1426
|
+
}
|
|
1399
1427
|
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1428
|
+
// Javascript won't let us function call a proxy unless the "target"
|
|
1429
|
+
// is a function. So we make a dummy target, and don't use it.
|
|
1430
|
+
var dummy_obj = function () {}
|
|
1431
|
+
return new Proxy(dummy_obj, {
|
|
1432
|
+
get: function (dummy_obj, k) {
|
|
1433
|
+
// console.log('get:', k, '::'+typeof k, 'on', o)
|
|
1434
|
+
|
|
1435
|
+
// Print something nice for Node console inspector
|
|
1436
|
+
if (nodejs && k === util.inspect.custom) {
|
|
1437
|
+
if (o == bus.cache)
|
|
1438
|
+
return function () {return 'state'+bus.toString().substr(3)}
|
|
1439
|
+
return function () {return 'p: '+util.format(proxy_decode_json(o))}
|
|
1440
|
+
}
|
|
1441
|
+
if (k in bogus_keys) return o[k]
|
|
1442
|
+
// Proxies distinguish themselves via proxy.is_proxy == true
|
|
1443
|
+
if (k === symbols.is_proxy) return true
|
|
1444
|
+
if (k === symbols.get_json) return get_json()
|
|
1445
|
+
if (k === symbols.get_base) return base
|
|
1446
|
+
if (k === Symbol.isConcatSpreadable) return Array.isArray(o)
|
|
1447
|
+
if (k === Symbol.toPrimitive) return function () {
|
|
1448
|
+
return JSON.stringify(proxy_decode_json(o))
|
|
1449
|
+
}
|
|
1450
|
+
if (typeof k === 'symbol') {
|
|
1451
|
+
console.warn('Got request for weird symbol', k)
|
|
1452
|
+
return undefined
|
|
1453
|
+
}
|
|
1403
1454
|
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
symbols = {is_proxy: Symbol('is_proxy'),
|
|
1408
|
-
get_json: Symbol('get_json'),
|
|
1409
|
-
get_base: Symbol('get_base')}
|
|
1455
|
+
var tmp2 = pget(base, o, escape_keys(k))
|
|
1456
|
+
var base2 = tmp2[0]
|
|
1457
|
+
var o2 = tmp2[1]
|
|
1410
1458
|
|
|
1411
|
-
|
|
1459
|
+
// console.log('returning proxy on', base2, o2)
|
|
1460
|
+
return make_proxy(base2, o2)
|
|
1461
|
+
},
|
|
1462
|
+
set: function (dummy_obj, k, v) {
|
|
1463
|
+
// console.log('set:', {base, o, k, v})
|
|
1464
|
+
|
|
1465
|
+
if (base) {
|
|
1466
|
+
var encoded_v = o[escape_keys(k)] = proxy_encode_val(v)
|
|
1467
|
+
// console.log(' set: saving', encoded_v, 'into', base)
|
|
1468
|
+
|
|
1469
|
+
// Collapse state of the form:
|
|
1470
|
+
// {key: '*', _: {foo: bar, ...}}
|
|
1471
|
+
// down to:
|
|
1472
|
+
// {key: '*', foo: bar}
|
|
1473
|
+
if (base._
|
|
1474
|
+
&& Object.keys(base).length === 2
|
|
1475
|
+
&& typeof base._ === 'object'
|
|
1476
|
+
&& base._ !== null
|
|
1477
|
+
&& !Array.isArray(base._)
|
|
1478
|
+
&& !base._.key
|
|
1479
|
+
&& Object.keys(base._).length !== 0) {
|
|
1480
|
+
// console.log('Collapsing', JSON.stringify(base))
|
|
1481
|
+
for (var k2 in base._)
|
|
1482
|
+
base[k2] = base._[k2]
|
|
1483
|
+
delete base._
|
|
1484
|
+
}
|
|
1412
1485
|
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
// We know it's a singleton array if base._ === x(), and base is
|
|
1416
|
-
// of the form {key: *, _: x}
|
|
1417
|
-
if (base && base._ && Object.keys(base).length === 2
|
|
1418
|
-
&& base._ === o)
|
|
1419
|
-
return base
|
|
1486
|
+
bus.save(base)
|
|
1487
|
+
}
|
|
1420
1488
|
|
|
1421
|
-
|
|
1422
|
-
|
|
1489
|
+
// Saving into top-level state
|
|
1490
|
+
else {
|
|
1491
|
+
var encoded_v = proxy_encode_val(v)
|
|
1492
|
+
// console.log(' set top-level:', {v, encoded_v})
|
|
1493
|
+
|
|
1494
|
+
// Setting a top-level object to undefined wipes it out
|
|
1495
|
+
if (v === undefined)
|
|
1496
|
+
encoded_v = {key: k}
|
|
1497
|
+
|
|
1498
|
+
// Prefix with _: anything that is:
|
|
1499
|
+
else if (// A proxy to another state
|
|
1500
|
+
(typeof v === 'object' && v[symbols.is_proxy])
|
|
1501
|
+
// An empty {} object
|
|
1502
|
+
|| (typeof v === 'object' && Object.keys(v).length === 0)
|
|
1503
|
+
// A number, bool, string, function, etc
|
|
1504
|
+
|| typeof v !== 'object' || v === null
|
|
1505
|
+
// An array
|
|
1506
|
+
|| Array.isArray(v))
|
|
1507
|
+
encoded_v = {_: encoded_v}
|
|
1508
|
+
encoded_v.key = k
|
|
1509
|
+
|
|
1510
|
+
// console.log(' set top-level: now encoded_v is', encoded_v)
|
|
1511
|
+
bus.save(encoded_v)
|
|
1512
|
+
}
|
|
1513
|
+
|
|
1514
|
+
var newbase = (encoded_v && encoded_v.key) ? encoded_v : base
|
|
1515
|
+
return true
|
|
1516
|
+
},
|
|
1517
|
+
has: function has(O, k) {
|
|
1518
|
+
// XXX QUESTIONS:
|
|
1519
|
+
//
|
|
1520
|
+
// - Do I want this to return true if there's a .to_fetch()
|
|
1521
|
+
// function for this o, k?
|
|
1522
|
+
//
|
|
1523
|
+
// - Does this need to do a fetch as well?
|
|
1524
|
+
//
|
|
1525
|
+
// - For a keyed object, should this do a loading() check?
|
|
1526
|
+
return o.hasOwnProperty(escape_keys(k))
|
|
1527
|
+
},
|
|
1528
|
+
deleteProperty: function del (O, k) {
|
|
1529
|
+
if (base) {
|
|
1530
|
+
// console.log(' deleting:', escape_keys(k), 'of', o)
|
|
1531
|
+
delete o[escape_keys(k)] // Deleting innards
|
|
1532
|
+
if (Object.keys(o).length === 1 && o.key)
|
|
1533
|
+
o._ = {}
|
|
1534
|
+
bus.save(base)
|
|
1535
|
+
}
|
|
1536
|
+
else
|
|
1537
|
+
bus.delete(escape_keys(k)) // Deleting top-level
|
|
1538
|
+
},
|
|
1539
|
+
apply: function apply (f, This, args) { return get_json() }
|
|
1540
|
+
})
|
|
1423
1541
|
}
|
|
1542
|
+
if (nodejs || window.Proxy)
|
|
1543
|
+
var state = make_proxy(null, cache)
|
|
1544
|
+
|
|
1545
|
+
// So chrome can print out proxy objects decently
|
|
1546
|
+
if (!nodejs)
|
|
1547
|
+
window.devtoolsFormatters = [{
|
|
1548
|
+
header: function (x) {
|
|
1549
|
+
return x[symbols.is_proxy] &&
|
|
1550
|
+
['span', {style: 'background-color: #feb; padding: 3px;'},
|
|
1551
|
+
JSON.stringify(proxy_decode_json(x()))]
|
|
1552
|
+
},
|
|
1553
|
+
hasBody: function (x) {return false}
|
|
1554
|
+
}]
|
|
1555
|
+
}
|
|
1424
1556
|
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
var dummy_obj = function () {}
|
|
1428
|
-
return new Proxy(dummy_obj, {
|
|
1429
|
-
get: function (dummy_obj, k) {
|
|
1430
|
-
// console.log('get:', k, '::'+typeof k, 'on', o)
|
|
1431
|
-
|
|
1432
|
-
// Print something nice for Node console inspector
|
|
1433
|
-
if (nodejs && k === util.inspect.custom) {
|
|
1434
|
-
if (o == bus.cache)
|
|
1435
|
-
return function () {return 'state'+bus.toString().substr(3)}
|
|
1436
|
-
return function () {return 'p: '+util.format(proxy_decode_json(o))}
|
|
1437
|
-
}
|
|
1438
|
-
if (k in bogus_keys) return o[k]
|
|
1439
|
-
// Proxies distinguish themselves via proxy.is_proxy == true
|
|
1440
|
-
if (k === symbols.is_proxy) return true
|
|
1441
|
-
if (k === symbols.get_json) return get_json()
|
|
1442
|
-
if (k === symbols.get_base) return base
|
|
1443
|
-
if (k === Symbol.isConcatSpreadable) return Array.isArray(o)
|
|
1444
|
-
if (k === Symbol.toPrimitive) return function () {
|
|
1445
|
-
return JSON.stringify(proxy_decode_json(o))
|
|
1446
|
-
}
|
|
1447
|
-
if (typeof k === 'symbol') {
|
|
1448
|
-
console.warn('Got request for weird symbol', k)
|
|
1449
|
-
return undefined
|
|
1450
|
-
}
|
|
1557
|
+
// ******************
|
|
1558
|
+
// Braid Test Mode Proxy
|
|
1451
1559
|
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1560
|
+
var symbols = {is_proxy: Symbol('is_proxy'),
|
|
1561
|
+
is_link: Symbol('is_link'),
|
|
1562
|
+
get_json: Symbol('get_json'),
|
|
1563
|
+
get_base: Symbol('get_base')}
|
|
1564
|
+
function braid_proxy () {
|
|
1565
|
+
function item_proxy (base, o) {
|
|
1455
1566
|
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1567
|
+
// We only create a proxy for objects.
|
|
1568
|
+
if (typeof o === 'number'
|
|
1569
|
+
|| typeof o === 'string'
|
|
1570
|
+
|| typeof o === 'boolean'
|
|
1571
|
+
|| o === undefined
|
|
1572
|
+
|| o === null
|
|
1573
|
+
|| typeof o === 'function')
|
|
1574
|
+
// Everything else passes through unscathed
|
|
1575
|
+
return o
|
|
1576
|
+
|
|
1577
|
+
// We recursively descend through {key: ...} links
|
|
1578
|
+
if (typeof o === 'object' && 'key' in o) {
|
|
1579
|
+
var new_base = bus.fetch(o.key)
|
|
1580
|
+
return item_proxy(new_base, new_base.val)
|
|
1581
|
+
}
|
|
1582
|
+
|
|
1583
|
+
return new Proxy(o, {
|
|
1584
|
+
get: function get(o, k) {
|
|
1585
|
+
if (k === 'inspect'
|
|
1586
|
+
|| k === 'valueOf' || typeof k === 'symbol')
|
|
1587
|
+
return undefined
|
|
1588
|
+
return item_proxy(base, o[proxied_2_keyed(k)])
|
|
1589
|
+
},
|
|
1590
|
+
set: function set(o, k, v) {
|
|
1591
|
+
var value = translate_fields(v, proxied_2_keyed)
|
|
1592
|
+
o[proxied_2_keyed(k)] = value
|
|
1483
1593
|
bus.save(base)
|
|
1594
|
+
return value
|
|
1595
|
+
},
|
|
1596
|
+
has: function has(o, k) {
|
|
1597
|
+
return o.hasOwnProperty(proxied_2_keyed(k))
|
|
1598
|
+
},
|
|
1599
|
+
deleteProperty: function del (o, k) {
|
|
1600
|
+
delete o[proxied_2_keyed(k)]
|
|
1601
|
+
},
|
|
1602
|
+
apply: function apply (o, This, args) {
|
|
1603
|
+
return o
|
|
1484
1604
|
}
|
|
1605
|
+
})}
|
|
1485
1606
|
|
|
1486
|
-
// Saving into top-level state
|
|
1487
|
-
else {
|
|
1488
|
-
var encoded_v = proxy_encode_val(v)
|
|
1489
|
-
// console.log(' set top-level:', {v, encoded_v})
|
|
1490
|
-
|
|
1491
|
-
// Setting a top-level object to undefined wipes it out
|
|
1492
|
-
if (v === undefined)
|
|
1493
|
-
encoded_v = {key: k}
|
|
1494
|
-
|
|
1495
|
-
// Prefix with _: anything that is:
|
|
1496
|
-
else if (// A proxy to another state
|
|
1497
|
-
(typeof v === 'object' && v[symbols.is_proxy])
|
|
1498
|
-
// An empty {} object
|
|
1499
|
-
|| (typeof v === 'object' && Object.keys(v).length === 0)
|
|
1500
|
-
// A number, bool, string, function, etc
|
|
1501
|
-
|| typeof v !== 'object' || v === null
|
|
1502
|
-
// An array
|
|
1503
|
-
|| Array.isArray(v))
|
|
1504
|
-
encoded_v = {_: encoded_v}
|
|
1505
|
-
encoded_v.key = k
|
|
1506
|
-
|
|
1507
|
-
// console.log(' set top-level: now encoded_v is', encoded_v)
|
|
1508
|
-
bus.save(encoded_v)
|
|
1509
|
-
}
|
|
1510
1607
|
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
// - Does this need to do a fetch as well?
|
|
1521
|
-
//
|
|
1522
|
-
// - For a keyed object, should this do a loading() check?
|
|
1523
|
-
return o.hasOwnProperty(encode_field(k))
|
|
1608
|
+
// The top-level Proxy object holds HTTP resources
|
|
1609
|
+
return new Proxy(cache, {
|
|
1610
|
+
get: function get(o, k) {
|
|
1611
|
+
if (k === 'inspect' || k === 'valueOf' || typeof k === 'symbol')
|
|
1612
|
+
return undefined
|
|
1613
|
+
if (k in bogus_keys)
|
|
1614
|
+
return o[k]
|
|
1615
|
+
var base = bus.fetch(k)
|
|
1616
|
+
return item_proxy(base, base.val)
|
|
1524
1617
|
},
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
// console.log(' deleting:', encode_field(k), 'of', o)
|
|
1528
|
-
delete o[encode_field(k)] // Deleting innards
|
|
1529
|
-
if (Object.keys(o).length === 1 && o.key)
|
|
1530
|
-
o._ = {}
|
|
1531
|
-
bus.save(base)
|
|
1532
|
-
}
|
|
1533
|
-
else
|
|
1534
|
-
bus.delete(encode_field(k)) // Deleting top-level
|
|
1618
|
+
set: function set(o, key, val) {
|
|
1619
|
+
bus.save({key: key, val: val})
|
|
1535
1620
|
},
|
|
1536
|
-
|
|
1621
|
+
deleteProperty: function del (o, k) {
|
|
1622
|
+
bus.delete(proxied_2_keyed(k))
|
|
1623
|
+
}
|
|
1537
1624
|
})
|
|
1538
1625
|
}
|
|
1539
|
-
if (nodejs || window.Proxy)
|
|
1540
|
-
var state = make_proxy(null, cache)
|
|
1541
1626
|
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1627
|
+
function link (url) {
|
|
1628
|
+
var result = fetch(url)
|
|
1629
|
+
result[symbols.is_link] = true
|
|
1630
|
+
return result
|
|
1631
|
+
}
|
|
1632
|
+
|
|
1633
|
+
if ((nodejs && bus.options && bus.options.braid_mode_test)
|
|
1634
|
+
|| (!nodejs && window.Proxy && clientjs_option('braid_mode_test')))
|
|
1635
|
+
state = braid_proxy()
|
|
1636
|
+
|
|
1552
1637
|
|
|
1553
1638
|
// ******************
|
|
1554
1639
|
// Network client
|
|
@@ -1756,8 +1841,8 @@
|
|
|
1756
1841
|
}
|
|
1757
1842
|
|
|
1758
1843
|
|
|
1759
|
-
//
|
|
1760
|
-
//
|
|
1844
|
+
// ************************************************
|
|
1845
|
+
// Translating the URLs under keys of state
|
|
1761
1846
|
function translate_keys (obj, f) {
|
|
1762
1847
|
// Recurse through each element in arrays
|
|
1763
1848
|
if (Array.isArray(obj))
|
|
@@ -1779,14 +1864,60 @@
|
|
|
1779
1864
|
}
|
|
1780
1865
|
return obj
|
|
1781
1866
|
}
|
|
1782
|
-
function
|
|
1867
|
+
function escape_keys(k) {
|
|
1783
1868
|
return k.replace(/(_(keys?|time)?$|^key$)/, '$1_')
|
|
1784
1869
|
}
|
|
1785
|
-
function
|
|
1870
|
+
function unescape_keys (k) {
|
|
1786
1871
|
return k.replace(/(_$)/, '')
|
|
1787
1872
|
}
|
|
1788
1873
|
|
|
1789
1874
|
|
|
1875
|
+
// ************************************************
|
|
1876
|
+
// Translating fields of objects
|
|
1877
|
+
|
|
1878
|
+
// Recurse through JSON and swap all object fields with other field names,
|
|
1879
|
+
// according to the function f(field, object). Returns a copy.
|
|
1880
|
+
//
|
|
1881
|
+
// f(field, object) takes a string `field` and returns the new field name
|
|
1882
|
+
// for `object`.
|
|
1883
|
+
function translate_fields (input, f) {
|
|
1884
|
+
var result
|
|
1885
|
+
|
|
1886
|
+
// Recurse through each element in arrays
|
|
1887
|
+
if (Array.isArray(input)) {
|
|
1888
|
+
var new_array = input.slice()
|
|
1889
|
+
for (var i=0; i < input.length; i++)
|
|
1890
|
+
new_array[i] = translate_fields(input[i], f)
|
|
1891
|
+
result = new_array
|
|
1892
|
+
}
|
|
1893
|
+
|
|
1894
|
+
// Recurse through each property on objects
|
|
1895
|
+
else if (typeof input === 'object') {
|
|
1896
|
+
var new_obj = {}
|
|
1897
|
+
for (var k in input)
|
|
1898
|
+
new_obj[f(k, input)] = translate_fields(input[k], f)
|
|
1899
|
+
result = new_obj
|
|
1900
|
+
}
|
|
1901
|
+
|
|
1902
|
+
// Everything else passes through unscathed
|
|
1903
|
+
else
|
|
1904
|
+
result = input
|
|
1905
|
+
|
|
1906
|
+
return result
|
|
1907
|
+
}
|
|
1908
|
+
// Remove underscore from _key
|
|
1909
|
+
function keyed_2_proxied (k, object) {
|
|
1910
|
+
if (object[symbols.is_link])
|
|
1911
|
+
return k
|
|
1912
|
+
else
|
|
1913
|
+
return k.replace(/^(_*)_key$/, '$1key')
|
|
1914
|
+
}
|
|
1915
|
+
// Add underscore to key
|
|
1916
|
+
function proxied_2_keyed (k) {
|
|
1917
|
+
return k.replace(/^(_*)key$/, '$1_key')
|
|
1918
|
+
}
|
|
1919
|
+
|
|
1920
|
+
|
|
1790
1921
|
// ******************
|
|
1791
1922
|
// JSON encoding
|
|
1792
1923
|
// - Does both escape/unescape keys, and wraps/unwraps pointers
|
|
@@ -2209,7 +2340,7 @@
|
|
|
2209
2340
|
if (bus.honking_colors === false)
|
|
2210
2341
|
indented_log(icon + ' ' + message)
|
|
2211
2342
|
else
|
|
2212
|
-
indented_log(color + icon + ' ' + message +
|
|
2343
|
+
indented_log(color + icon + ' ' + message + normal)
|
|
2213
2344
|
}
|
|
2214
2345
|
function honking_at (key) {
|
|
2215
2346
|
return (bus.honk instanceof RegExp
|
|
@@ -2233,11 +2364,12 @@
|
|
|
2233
2364
|
var api = ['cache backup_cache fetch save forget del fire dirty fetch_once',
|
|
2234
2365
|
'subspace bindings run_handler bind unbind reactive uncallback',
|
|
2235
2366
|
'versions new_version',
|
|
2236
|
-
'make_proxy state sb',
|
|
2367
|
+
'make_proxy braid_proxy state sb link',
|
|
2237
2368
|
'funk_key funk_name funks key_id key_name id',
|
|
2238
2369
|
'pending_fetches fetches_in fetches_out loading_keys loading once',
|
|
2239
2370
|
'global_funk busses rerunnable_funks',
|
|
2240
|
-
'
|
|
2371
|
+
'escape_keys unescape_keys translate_keys apply_patch',
|
|
2372
|
+
'keyed_2_proxied proxied_2_keyed translate_fields',
|
|
2241
2373
|
'net_mount net_automount message_method',
|
|
2242
2374
|
'parse Set One_To_Many clone extend deep_map deep_equals prune validate sorta_diff log deps'
|
|
2243
2375
|
].join(' ').split(' ')
|