statebus 6.1.12 → 6.1.16

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 CHANGED
@@ -60,6 +60,7 @@
60
60
  function braid_http_mount (prefix, url) {
61
61
  var preprefix = prefix.slice(0,-1)
62
62
  var has_prefix = new RegExp('^' + preprefix)
63
+ var is_absolute = /^https?:\/\//
63
64
  var client_fetched_keys = new bus.Set()
64
65
  if (url[url.length-1]=='/') url = url.substr(0,url.length-1)
65
66
  function add_prefix (key) {
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.12",
3
+ "version": "6.1.16",
4
4
  "description": "Synchronize state.",
5
5
  "main": "statebus.js",
6
6
  "scripts": {
package/server.js CHANGED
@@ -2,6 +2,15 @@ var fs = require('fs'),
2
2
  util = require('util')
3
3
  var unique_sockjs_string = '_connect_to_statebus_'
4
4
 
5
+ // Import braidify if it's available
6
+ try {
7
+ var braidify = require('braidify').http_server
8
+ console.log('Found braidify library. Braid-HTTP enabled!')
9
+ } catch (e) {
10
+ var braidify = undefined
11
+ }
12
+
13
+
5
14
  function default_options (bus) { return {
6
15
  port: 'auto',
7
16
  backdoor: null,
@@ -68,6 +77,7 @@ function import_server (bus, options)
68
77
  bus.express = express()
69
78
  bus.http = express.Router()
70
79
  bus.install_express(bus.express)
80
+ bus.initialize_braid_http()
71
81
 
72
82
  // use gzip compression if available
73
83
  try { bus.http.use(require('compression')())
@@ -99,16 +109,94 @@ function import_server (bus, options)
99
109
  // User will put his routes in here
100
110
  bus.express.use('/', bus.http)
101
111
 
102
- // Use braidify if it's available
103
- try {
104
- var braidify = require('braidify').http_server
105
- console.log('Found braidify library. Braid-HTTP enabled!')
106
- } catch (e) {
107
- var braidify = undefined
112
+ // Free the CORS for Braid requests!
113
+ bus.express.use((req, res, next) => {
114
+ // If this requests knows about Braid then we presume the
115
+ // programmer knows that any client might make this cross-origin
116
+ // request.
117
+ if (req.version || req.parents || req.subscribe
118
+ || req.headers.subscribe
119
+ || req.method === 'OPTIONS')
120
+ free_the_cors(req, res, next)
121
+ else
122
+ next()
123
+ })
124
+
125
+ // Connect bus to the HTTP server
126
+ bus.express.use(bus.http_handlers)
127
+
128
+ // Serve Client Coffee
129
+ bus.serve_client_coffee()
130
+
131
+ // Custom router
132
+ bus.route = function server_route (key, method, arg, t) {
133
+ var handlers = bus.bindings(key, method)
134
+ var count = handlers.length
135
+ if (count)
136
+ bus.log('route:', bus+'("'+key+'").'+method+'['+handlers.length+'](key:"'+(arg.key||arg)+'")')
137
+
138
+ // Call all handlers!
139
+ for (var i=0; i<handlers.length; i++)
140
+ bus.run_handler(handlers[i].func, method, arg, {t: t, binding: handlers[i].key})
141
+
142
+ // Now handle backup handlers
143
+ if (count === 0) {
144
+
145
+ // A save to master without a to_save defined should be
146
+ // automatically reflected to all clients.
147
+ if (method === 'to_save') {
148
+ bus.save.fire(arg, t)
149
+ bus.route(arg.key, 'on_set_sync', arg, t)
150
+ count++
151
+ }
152
+
153
+ // A fetch to master without a to_fetch defined should go to
154
+ // the database, if we have one.
155
+ if (method === 'to_fetch') {
156
+ bus.db_fetch(key)
157
+ // This basically renders the count useless-- you probably
158
+ // don't want to wrap this route() with another route().
159
+ count++
160
+ }
161
+ }
162
+ return handlers.length
108
163
  }
109
164
 
110
- // Mirror state over HTTP
111
- bus.express.get('*', function (req, res) {
165
+ // Back door to the control room
166
+ if (bus.options.backdoor) {
167
+ bus.make_http_server({
168
+ port: bus.options.backdoor,
169
+ name: 'backdoor_http_server',
170
+ use_ssl: use_ssl
171
+ })
172
+ bus.sockjs_server(this.backdoor_http_server)
173
+ }
174
+ },
175
+
176
+ initialize_braid_http: function () {
177
+ // Set up default linked json converters
178
+ if (bus.options.braid_mode_test) {
179
+ bus.to_http_body = (o) => JSON.stringify(o.val)
180
+ bus.state = bus.braid_proxy()
181
+ } else
182
+ bus.to_http_body = (o) => {
183
+ var unwrap = (Object.keys(o).length === 2
184
+ && '_' in o
185
+ && typeof o._ === 'string')
186
+ // To do: translate pointers as keys
187
+ return unwrap ? o._ : JSON.stringify(o)
188
+ }
189
+
190
+ bus.from_http_body = (key, body) => ({
191
+ key,
192
+ val: JSON.parse(body)
193
+ })
194
+ },
195
+
196
+ // Connect HTTP GET and PUT to our Fetch and Save
197
+ http_handlers: function (req, res, next) {
198
+ if (req.method === 'GET') {
199
+
112
200
  // Make a temporary client bus
113
201
  var cbus = bus.bus_for_http_client(req, res)
114
202
  var cb
@@ -146,13 +234,13 @@ function import_server (bus, options)
146
234
  else
147
235
  res.send(body)
148
236
 
149
- // And shut down the connection if there's nothing left to do
237
+ // And shut down the connection if nothing's left to do
150
238
  if (!braidify || !req.subscribe || !body)
151
239
  end_it_all()
152
240
  })
153
- })
241
+ }
154
242
 
155
- bus.express.put('*', function (req, res) {
243
+ else if (req.method === 'PUT') {
156
244
  console.log('Got a put with body', req.body)
157
245
 
158
246
  // Make a temporary client bus
@@ -190,72 +278,9 @@ function import_server (bus, options)
190
278
  res.end()
191
279
  })
192
280
  }
193
- })
194
-
195
- // Set up default linked json converters
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
- })
212
-
213
- // Serve Client Coffee
214
- bus.serve_client_coffee()
215
-
216
- // Custom router
217
- bus.route = function server_route (key, method, arg, t) {
218
- var handlers = bus.bindings(key, method)
219
- var count = handlers.length
220
- if (count)
221
- bus.log('route:', bus+'("'+key+'").'+method+'['+handlers.length+'](key:"'+(arg.key||arg)+'")')
222
-
223
- // Call all handlers!
224
- for (var i=0; i<handlers.length; i++)
225
- bus.run_handler(handlers[i].func, method, arg, {t: t, binding: handlers[i].key})
226
-
227
- // Now handle backup handlers
228
- if (count === 0) {
229
-
230
- // A save to master without a to_save defined should be
231
- // automatically reflected to all clients.
232
- if (method === 'to_save') {
233
- bus.save.fire(arg, t)
234
- bus.route(arg.key, 'on_set_sync', arg, t)
235
- count++
236
- }
237
-
238
- // A fetch to master without a to_fetch defined should go to
239
- // the database, if we have one.
240
- if (method === 'to_fetch') {
241
- bus.db_fetch(key)
242
- // This basically renders the count useless-- you probably
243
- // don't want to wrap this route() with another route().
244
- count++
245
- }
246
- }
247
- return handlers.length
248
- }
249
-
250
- // Back door to the control room
251
- if (bus.options.backdoor) {
252
- bus.make_http_server({
253
- port: bus.options.backdoor,
254
- name: 'backdoor_http_server',
255
- use_ssl: use_ssl
256
- })
257
- bus.sockjs_server(this.backdoor_http_server)
258
281
  }
282
+ else
283
+ next()
259
284
  },
260
285
 
261
286
  bus_for_http_client: function (req, res) {
@@ -2450,6 +2475,24 @@ function import_server (bus, options)
2450
2475
  }
2451
2476
 
2452
2477
 
2478
+ // Disables CORS in HTTP servers
2479
+ function free_the_cors (req, res, next) {
2480
+ console.log('free the cors!', req.method, req.url)
2481
+
2482
+ var free_the_cors = {
2483
+ "Access-Control-Allow-Origin": "*",
2484
+ "Access-Control-Allow-Methods": "OPTIONS, HEAD, GET, PUT, UNSUBSCRIBE",
2485
+ "Access-Control-Allow-Headers": "subscribe, peer, version, parents, merge-type, content-type, patches, cache-control"
2486
+ }
2487
+ Object.entries(free_the_cors).forEach(x => res.setHeader(x[0], x[1]))
2488
+ if (req.method === 'OPTIONS') {
2489
+ res.writeHead(200)
2490
+ res.end()
2491
+ } else
2492
+ next()
2493
+ }
2494
+
2495
+
2453
2496
  // Handy functions for writing tests on nodejs
2454
2497
  var tests = []
2455
2498
  function test (f) {tests.push(f)}
package/statebus.js CHANGED
@@ -1217,19 +1217,19 @@
1217
1217
  get: function get(o, k) {
1218
1218
  if (k === 'inspect' || k === 'valueOf' || typeof k === 'symbol')
1219
1219
  return undefined
1220
- k = underscore_escape(k)
1220
+ k = escape_keys(k)
1221
1221
  return item_proxy(base, o[k])
1222
1222
  },
1223
1223
  set: function set(o, k, v) {
1224
- var result = o[underscore_escape(k)] = v
1224
+ var result = o[escape_keys(k)] = v
1225
1225
  bus.save(base)
1226
1226
  return result
1227
1227
  },
1228
1228
  has: function has(o, k) {
1229
- return o.hasOwnProperty(underscore_escape(k))
1229
+ return o.hasOwnProperty(escape_keys(k))
1230
1230
  },
1231
1231
  deleteProperty: function del (o, k) {
1232
- delete o[underscore_escape(k)]
1232
+ delete o[escape_keys(k)]
1233
1233
  },
1234
1234
  apply: function apply (o, This, args) {
1235
1235
  return o
@@ -1268,7 +1268,7 @@
1268
1268
  // },
1269
1269
  // ... but I haven't had a need yet.
1270
1270
  deleteProperty: function del (o, k) {
1271
- bus.delete(underscore_escape(k))
1271
+ bus.delete(escape_keys(k))
1272
1272
  }
1273
1273
  })
1274
1274
  })()
@@ -1362,7 +1362,7 @@
1362
1362
  // Actual objects need their keys translated
1363
1363
  var result = {}
1364
1364
  for (var k in x)
1365
- result[underscore_escape(k)] = proxy_encode_val(x[k])
1365
+ result[escape_keys(k)] = proxy_encode_val(x[k])
1366
1366
  return result
1367
1367
  }
1368
1368
 
@@ -1396,7 +1396,7 @@
1396
1396
  var obj = {}
1397
1397
  for (var k in json)
1398
1398
  if (k !== 'key')
1399
- obj[underscore_unescape(k)] = proxy_decode_json(json[k])
1399
+ obj[unescape_keys(k)] = proxy_decode_json(json[k])
1400
1400
  return obj
1401
1401
  }
1402
1402
 
@@ -1452,7 +1452,7 @@
1452
1452
  return undefined
1453
1453
  }
1454
1454
 
1455
- var tmp2 = pget(base, o, underscore_escape(k))
1455
+ var tmp2 = pget(base, o, escape_keys(k))
1456
1456
  var base2 = tmp2[0]
1457
1457
  var o2 = tmp2[1]
1458
1458
 
@@ -1463,7 +1463,7 @@
1463
1463
  // console.log('set:', {base, o, k, v})
1464
1464
 
1465
1465
  if (base) {
1466
- var encoded_v = o[underscore_escape(k)] = proxy_encode_val(v)
1466
+ var encoded_v = o[escape_keys(k)] = proxy_encode_val(v)
1467
1467
  // console.log(' set: saving', encoded_v, 'into', base)
1468
1468
 
1469
1469
  // Collapse state of the form:
@@ -1523,18 +1523,18 @@
1523
1523
  // - Does this need to do a fetch as well?
1524
1524
  //
1525
1525
  // - For a keyed object, should this do a loading() check?
1526
- return o.hasOwnProperty(underscore_escape(k))
1526
+ return o.hasOwnProperty(escape_keys(k))
1527
1527
  },
1528
1528
  deleteProperty: function del (O, k) {
1529
1529
  if (base) {
1530
- // console.log(' deleting:', underscore_escape(k), 'of', o)
1531
- delete o[underscore_escape(k)] // Deleting innards
1530
+ // console.log(' deleting:', escape_keys(k), 'of', o)
1531
+ delete o[escape_keys(k)] // Deleting innards
1532
1532
  if (Object.keys(o).length === 1 && o.key)
1533
1533
  o._ = {}
1534
1534
  bus.save(base)
1535
1535
  }
1536
1536
  else
1537
- bus.delete(underscore_escape(k)) // Deleting top-level
1537
+ bus.delete(escape_keys(k)) // Deleting top-level
1538
1538
  },
1539
1539
  apply: function apply (f, This, args) { return get_json() }
1540
1540
  })
@@ -1546,9 +1546,12 @@
1546
1546
  if (!nodejs)
1547
1547
  window.devtoolsFormatters = [{
1548
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()))]
1549
+ if (x[symbols.is_proxy])
1550
+ return ['span', {style: 'background-color: #feb; padding: 3px;'},
1551
+ JSON.stringify(proxy_decode_json(x()))]
1552
+ else if (x[symbols.is_braid_proxy])
1553
+ return ['span', {style: 'background-color: #fffbe5; padding: 3px;'},
1554
+ JSON.stringify(x)]
1552
1555
  },
1553
1556
  hasBody: function (x) {return false}
1554
1557
  }]
@@ -1556,41 +1559,61 @@
1556
1559
 
1557
1560
  // ******************
1558
1561
  // Braid Test Mode Proxy
1559
- function braid_proxy () {
1560
- // I have the cache behind the scenes
1561
- // Each proxy has a target object -- the raw data on cache
1562
- // If we're proxying a {_: ...} singleton then ...
1563
1562
 
1563
+ var symbols = {is_braid_proxy: Symbol('is_braid_proxy'),
1564
+ is_proxy: Symbol('is_proxy'),
1565
+ is_link: Symbol('is_link'),
1566
+ get_json: Symbol('get_json'),
1567
+ get_base: Symbol('get_base')
1568
+ }
1569
+ function braid_proxy () {
1564
1570
  function item_proxy (base, o) {
1571
+
1572
+ // We only create a proxy for objects.
1565
1573
  if (typeof o === 'number'
1566
1574
  || typeof o === 'string'
1567
1575
  || typeof o === 'boolean'
1568
1576
  || o === undefined
1569
1577
  || o === null
1570
- || typeof o === 'function') return o
1578
+ || typeof o === 'function')
1579
+ // Everything else passes through unscathed
1580
+ return o
1581
+
1582
+ // We recursively descend through {key: ...} links
1583
+ if (typeof o === 'object' && 'key' in o) {
1584
+ var new_base = bus.fetch(o.key)
1585
+ return item_proxy(new_base, new_base.val)
1586
+ }
1571
1587
 
1572
1588
  return new Proxy(o, {
1573
1589
  get: function get(o, k) {
1574
- if (k === 'inspect' || k === 'valueOf' || typeof k === 'symbol')
1590
+ if (k === 'inspect' || k === 'valueOf')
1575
1591
  return undefined
1576
- return item_proxy(base, o[underscore_escape(k)])
1592
+ if (k === symbols.is_braid_proxy)
1593
+ return true
1594
+ if (typeof k === 'symbol')
1595
+ return undefined
1596
+ return item_proxy(base, o[proxied_2_keyed(k)])
1577
1597
  },
1578
1598
  set: function set(o, k, v) {
1579
- var result = o[underscore_escape(k)] = v
1599
+ var value = translate_fields(v, proxied_2_keyed)
1600
+ o[proxied_2_keyed(k)] = value
1580
1601
  bus.save(base)
1581
- return result
1602
+ return true
1582
1603
  },
1583
1604
  has: function has(o, k) {
1584
- return o.hasOwnProperty(underscore_escape(k))
1605
+ return o.hasOwnProperty(proxied_2_keyed(k))
1585
1606
  },
1586
1607
  deleteProperty: function del (o, k) {
1587
- delete o[underscore_escape(k)]
1608
+ delete o[proxied_2_keyed(k)]
1588
1609
  },
1589
1610
  apply: function apply (o, This, args) {
1590
1611
  return o
1591
1612
  }
1592
1613
  })}
1593
1614
 
1615
+
1616
+ // The top-level Proxy object holds HTTP resources
1594
1617
  return new Proxy(cache, {
1595
1618
  get: function get(o, k) {
1596
1619
  if (k === 'inspect' || k === 'valueOf' || typeof k === 'symbol')
@@ -1601,21 +1624,27 @@
1601
1624
  return item_proxy(base, base.val)
1602
1625
  },
1603
1626
  set: function set(o, key, val) {
1604
- bus.save({key, val})
1627
+ bus.save({key: key,
1628
+ val: translate_fields(val, proxied_2_keyed)})
1629
+ return true
1605
1630
  },
1606
- // In future, this might check if there's a .to_fetch function OR
1607
- // something in the cache:
1608
- //
1609
- // has: function has(o, k) {
1610
- // return k in o
1611
- // },
1612
- // ... but I haven't had a need yet.
1613
1631
  deleteProperty: function del (o, k) {
1614
- bus.delete(underscore_escape(k))
1632
+ bus.delete(proxied_2_keyed(k))
1615
1633
  }
1616
1634
  })
1617
1635
  }
1618
1636
 
1637
+ function link (url) {
1638
+ var result = fetch(url)
1639
+ result[symbols.is_link] = true
1640
+ return result
1641
+ }
1642
+
1643
+ if ((nodejs && bus.options && bus.options.braid_mode_test)
1644
+ || (!nodejs && window.Proxy && clientjs_option('braid_mode_test')))
1645
+ state = braid_proxy()
1646
+
1647
+
1619
1648
  // ******************
1620
1649
  // Network client
1621
1650
  function get_domain (key) { // Returns e.g. "state://foo.com"
@@ -1822,8 +1851,8 @@
1822
1851
  }
1823
1852
 
1824
1853
 
1825
- // ******************
1826
- // Key translation
1854
+ // ************************************************
1855
+ // Translating the URLs under keys of state
1827
1856
  function translate_keys (obj, f) {
1828
1857
  // Recurse through each element in arrays
1829
1858
  if (Array.isArray(obj))
@@ -1845,14 +1874,60 @@
1845
1874
  }
1846
1875
  return obj
1847
1876
  }
1848
- function underscore_escape(k) {
1877
+ function escape_keys(k) {
1849
1878
  return k.replace(/(_(keys?|time)?$|^key$)/, '$1_')
1850
1879
  }
1851
- function underscore_unescape (k) {
1880
+ function unescape_keys (k) {
1852
1881
  return k.replace(/(_$)/, '')
1853
1882
  }
1854
1883
 
1855
1884
 
1885
+ // ************************************************
1886
+ // Translating fields of objects
1887
+
1888
+ // Recurse through JSON and swap all object fields with other field names,
1889
+ // according to the function f(field, object). Returns a copy.
1890
+ //
1891
+ // f(field, object) takes a string `field` and returns the new field name
1892
+ // for `object`.
1893
+ function translate_fields (input, f) {
1894
+ var result
1895
+
1896
+ // Recurse through each element in arrays
1897
+ if (Array.isArray(input)) {
1898
+ var new_array = input.slice()
1899
+ for (var i=0; i < input.length; i++)
1900
+ new_array[i] = translate_fields(input[i], f)
1901
+ result = new_array
1902
+ }
1903
+
1904
+ // Recurse through each property on objects
1905
+ else if (typeof input === 'object') {
1906
+ var new_obj = {}
1907
+ for (var k in input)
1908
+ new_obj[f(k, input)] = translate_fields(input[k], f)
1909
+ result = new_obj
1910
+ }
1911
+
1912
+ // Everything else passes through unscathed
1913
+ else
1914
+ result = input
1915
+
1916
+ return result
1917
+ }
1918
+ // Remove underscore from _key
1919
+ function keyed_2_proxied (k, object) {
1920
+ if (object[symbols.is_link])
1921
+ return k
1922
+ else
1923
+ return k.replace(/^(_*)_key$/, '$1key')
1924
+ }
1925
+ // Add underscore to key
1926
+ function proxied_2_keyed (k) {
1927
+ return k.replace(/^(_*)key$/, '$1_key')
1928
+ }
1929
+
1930
+
1856
1931
  // ******************
1857
1932
  // JSON encoding
1858
1933
  // - Does both escape/unescape keys, and wraps/unwraps pointers
@@ -2299,13 +2374,14 @@
2299
2374
  var api = ['cache backup_cache fetch save forget del fire dirty fetch_once',
2300
2375
  'subspace bindings run_handler bind unbind reactive uncallback',
2301
2376
  'versions new_version',
2302
- 'make_proxy braid_proxy state sb',
2377
+ 'make_proxy braid_proxy state sb link',
2303
2378
  'funk_key funk_name funks key_id key_name id',
2304
2379
  'pending_fetches fetches_in fetches_out loading_keys loading once',
2305
2380
  'global_funk busses rerunnable_funks',
2306
- 'underscore_escape underscore_unescape translate_keys apply_patch',
2381
+ 'escape_keys unescape_keys translate_keys apply_patch',
2382
+ 'keyed_2_proxied proxied_2_keyed translate_fields',
2307
2383
  'net_mount net_automount message_method',
2308
- 'parse Set One_To_Many clone extend deep_map deep_equals prune validate sorta_diff log deps'
2384
+ 'parse Set One_To_Many clone extend deep_map deep_equals prune validate sorta_diff log deps symbols'
2309
2385
  ].join(' ').split(' ')
2310
2386
  for (var i=0; i<api.length; i++)
2311
2387
  bus[api[i]] = eval(api[i])