UNPKG

85.7 kBJavaScriptView Raw
1// These 5 lines generate a module that can be included with CommonJS, AMD, and <script> tags.
2(function(name, definition) {
3 if (typeof module != 'undefined') module.exports = definition()
4 else if (typeof define == 'function' && typeof define.amd == 'object') define(definition)
5 else this[name] = definition()
6}('statebus', function() {statelog_indent = 0; var busses = {}, executing_funk, global_funk, funks = {}, clean_timer, symbols, nodejs = typeof window === 'undefined'; function make_bus (options) {
7
8
9 // ****************
10 // Fetch, Save, Forget, Delete
11
12 function fetch (key, callback) {
13 key = key.key || key // You can pass in an object instead of key
14 // We should probably disable this in future
15 if (typeof key !== 'string')
16 throw ('Error: fetch(key) called with a non-string key: '+key)
17 bogus_check(key)
18
19 var called_from_reactive_funk = !callback
20 var funk = callback || executing_funk
21
22 if (callback) {
23 (callback.defined = callback.defined || []
24 ).push({as:'fetch callback', key:key});
25 callback.has_seen = callback.has_seen || function (bus, key, version) {
26 callback.seen_keys = callback.seen_keys || {}
27 var bus_key = JSON.stringify([bus.id, key])
28 var seen_versions =
29 callback.seen_keys[bus_key] = callback.seen_keys[bus_key] || []
30 seen_versions.push(version)
31 if (versions.length > 50) versions.shift()
32 }
33 }
34
35 // ** Subscribe the calling funk **
36
37 if (called_from_reactive_funk)
38 funk.has_seen(bus, key, versions[key]) // Maybe this line should go below, in the existing "if (called_from_reactive_funk) {" ??
39 fetches_in.add(key, funk_key(funk))
40 if (to_be_forgotten[key]) {
41 clearTimeout(to_be_forgotten[key])
42 delete to_be_forgotten[key]
43 }
44
45 bind(key, 'on_save', funk)
46
47 // ** Call fetchers upstream **
48
49 // TODO: checking fetches_out[] doesn't count keys that we got which
50 // arrived nested within a bigger object, because we never explicity
51 // fetched those keys. But we don't need to fetch them now cause we
52 // already have them.
53 var to_fetchers = 0
54 if (!fetches_out[key])
55 to_fetchers = bus.route(key, 'to_fetch', key)
56
57 // Now there might be a new value pubbed onto this bus.
58 // Or there might be a pending fetch.
59 // ... or there weren't any fetchers upstream.
60
61
62 // ** Return a value **
63
64 // If called reactively, we always return a value.
65 if (called_from_reactive_funk) {
66 backup_cache[key] = backup_cache[key] || {key: key}
67 return cache[key] = cache[key] || {key: key}
68 }
69
70 // Otherwise, we want to make sure that a pub gets called on the
71 // handler. If there's a pending fetch, then it'll get called later.
72 // If there was a to_fetch, then it already got called. Otherwise,
73 // let's call it now.
74 else if (!pending_fetches[key] && to_fetchers === 0) {
75 // TODO: my intuition suggests that we might prefer to
76 // delay this .on_save getting called in a
77 // setTimeout(f,0), to be consistent with other calls to
78 // .on_save.
79 backup_cache[key] = backup_cache[key] || {key: key}
80 run_handler(funk, 'on_save', cache[key] = cache[key] || {key: key})
81 }
82 }
83 function fetch_once (key, cb) {
84 function cb2 (o) { cb(o); forget(key, cb2) }
85 // fetch(key) // This prevents key from being forgotten
86 fetch(key, cb2)
87 }
88 fetch.once = fetch_once
89 var pending_fetches = {}
90 var fetches_out = {} // Maps `key' to `func' iff we've fetched `key'
91 var fetches_in = new One_To_Many() // Maps `key' to `pub_funcs' subscribed to our key
92
93 var currently_saving
94 function save (obj, t) {
95 // First let's handle diffs
96 if (typeof obj === 'string' && t && t.patch) {
97 if (typeof t.patch == 'string') t.patch = [t.patch]
98 // Apply the patch locally
99 obj = apply_patch(bus.cache[obj] || {key: obj}, t.patch[0])
100 }
101
102 if (!('key' in obj) || typeof obj.key !== 'string')
103 console.error('Error: save(obj) called on object without a key: ', obj)
104 bogus_check(obj.key)
105
106 t = t || {}
107 // Make sure it has a version.
108 t.version = t.version || new_version()
109
110 if ((executing_funk !== global_funk) && executing_funk.loading()) {
111 abort_changes([obj.key])
112 return
113 }
114
115 if (honking_at(obj.key))
116 var message = save_msg(obj, t, 'save')
117
118 // Ignore if nothing happened
119 if (obj.key && !changed(obj)) {
120 statelog(obj.key, grey, 'x', message)
121 return
122 } else
123 statelog(obj.key, red, 'o', message)
124
125 try {
126 statelog_indent++
127 var was_saving = currently_saving
128 currently_saving = obj.key
129
130 // Call the to_save() handlers!
131 var num_handlers = bus.route(obj.key, 'to_save', obj, t)
132 if (num_handlers === 0)
133 // And fire if there weren't any!
134 save.fire(obj, t)
135 }
136 finally {
137 statelog_indent--
138 currently_saving = was_saving
139 }
140 // TODO: Here's an alternative. Instead of counting the handlers and
141 // seeing if there are zero, I could just make a to_save handler that
142 // is shadowed by other handlers if I can get later handlers to shadow
143 // earlier ones.
144 }
145 save.fire = fire
146 function fire (obj, t) {
147 t = t || {}
148 // Make sure it has a version.
149 t.version = t.version || new_version()
150
151 // Print a statelog entry
152 if (obj.key && honking_at(obj.key)) {
153 // Warning: Changes to *nested* objects will *not* be printed out!
154 // In the future, we'll remove the recursion from fire() so that
155 // nested objects aren't even changed.
156 var message = save_msg(obj, t, 'save.fire')
157 var color, icon
158 if (currently_saving === obj.key &&
159 !(obj.key && !changed(obj))) {
160 statelog_indent--
161 statelog(obj.key, red, '•', '↵' +
162 (t.version ? '\t\t\t[' + t.version + ']' : ''))
163 statelog_indent++
164 } else {
165 // Ignore if nothing happened
166 if (obj.key && !changed(obj)) {
167 color = grey
168 icon = 'x'
169 if (t.to_fetch)
170 message = (t.m) || 'Fetched ' + bus + "('"+obj.key+"')"
171 if (t.version) message += ' [' + t.version + ']'
172 statelog(obj.key, color, icon, message)
173 return
174 }
175
176 color = red, icon = '•'
177 if (t.to_fetch || pending_fetches[obj.key]) {
178 color = green
179 icon = '^'
180 message = add_diff_msg((t.m)||'Fetched '+bus+"('"+obj.key+"')",
181 obj)
182 if (t.version) message += ' [' + t.version + ']'
183 }
184
185 statelog(obj.key, color, icon, message)
186 }
187 }
188 // Then we're gonna fire!
189
190 // Recursively add all of obj, and its sub-objects, into the cache
191 var modified_keys = update_cache(obj, cache)
192
193 delete pending_fetches[obj.key]
194
195 if ((executing_funk !== global_funk) && executing_funk.loading()) {
196 abort_changes(modified_keys)
197 } else {
198 // Let's publish these changes!
199
200 // These objects must replace their backups
201 update_cache(obj, backup_cache)
202
203 // And we mark each changed key as changed so that
204 // reactions happen to them
205 for (var i=0; i < modified_keys.length; i++) {
206 var key = modified_keys[i]
207 var parents = [versions[key]] // Not stored yet
208 versions[key] = t.version
209 mark_changed(key, t)
210 }
211 }
212 }
213
214 save.abort = function (obj, t) {
215 if (!obj) console.error('No obj', obj)
216 abort_changes([obj.key])
217 statelog(obj.key, yellow, '<', 'Aborting ' + obj.key)
218 mark_changed(obj.key, t)
219 }
220
221 var version_count = 0
222 function new_version () {
223 return (bus.label||(id+' ')) + (version_count++).toString(36)
224 }
225
226 // Now create the statebus object
227 function bus (arg) {
228 // Called with a function to react to
229 if (typeof arg === 'function') {
230 var f = reactive(arg)
231 f()
232 return f
233 }
234
235 // Called with a key to produce a subspace
236 else return subspace(arg)
237 }
238 var id = 'bus ' + Math.random().toString(36).substring(7)
239 bus.toString = function () { return bus.label || id }
240 bus.delete_bus = function () {
241 // // Forget all wildcard handlers
242 // for (var i=0; i<wildcard_handlers.length; i++) {
243 // console.log('Forgetting', funk_name(wildcard_handlers[i].funk))
244 // wildcard_handlers[i].funk.forget()
245 // }
246
247 // // Forget all handlers
248 // for (var k1 in handlers.hash)
249 // for (var k2 in handlers.hash[k])
250 // handlers.hash[k][k2].forget()
251
252 delete busses[bus.id]
253 }
254
255 // The Data Almighty!!
256 var cache = {}
257 var backup_cache = {}
258 var versions = {}
259
260 // Folds object into the cache recursively and returns the keys
261 // for all mutated staet
262 function update_cache (object, cache) {
263 var modified_keys = new Set()
264 function update_object (obj) {
265
266 // Two ways to optimize this in future:
267 //
268 // 1. Only clone objects/arrays if they are new.
269 //
270 // Right now we re-clone all internal arrays and objects on
271 // each pub. But we really only need to clone them the first
272 // time they are pubbed into the cache. After that, we can
273 // trust that they aren't referenced elsewhere. (We make it
274 // the programmer's responsibility to clone data if necessary
275 // on fetch, but not when on pub.)
276 //
277 // We'll optimize this once we have history. We can look at
278 // the old version to see if an object/array existed already
279 // before cloning it.
280 //
281 // 2. Don't go infinitely deep.
282 //
283 // Eventually, each save/pub will be limited to the scope
284 // underneath nested keyed objects. Right now I'm just
285 // recursing infinitely on the whole data structure with each
286 // pub.
287
288 // Clone arrays
289 if (Array.isArray(obj))
290 obj = obj.slice()
291
292 // Clone objects
293 else if (typeof obj === 'object'
294 && obj // That aren't null
295 && !(obj.key // That aren't already in cache
296 && cache[obj.key] === obj)) {
297 var tmp = {}; for (var k in obj) tmp[k] = obj[k]; obj = tmp
298 }
299
300 // Inline pointers
301 if ((nodejs ? global : window).pointerify && obj && obj._key) {
302 if (Object.keys(obj).length > 1)
303 console.error('Got a {_key: ...} object with additional fields')
304 obj = bus.cache[obj._key] = bus.cache[obj._key] || {key: obj._key}
305 }
306
307 // Fold cacheable objects into cache
308 else if (obj && obj.key) {
309 bogus_check(obj.key)
310
311 if (cache !== backup_cache)
312 if (changed(obj))
313 modified_keys.add(obj.key)
314 else
315 log('Boring modified key', obj.key)
316 if (!cache[obj.key])
317 // This object is new. Let's store it.
318 cache[obj.key] = obj
319
320 else if (obj !== cache[obj.key]) {
321 // Else, mutate cache to match the object.
322
323 // First, add/update missing/changed fields to cache
324 for (var k in obj)
325 if (cache[obj.key][k] !== obj[k])
326 cache[obj.key][k] = obj[k]
327
328 // Then delete extra fields from cache
329 for (var k in cache[obj.key])
330 if (!obj.hasOwnProperty(k))
331 delete cache[obj.key][k]
332 }
333 obj = cache[obj.key]
334 }
335
336 return obj
337 }
338 deep_map(object, update_object)
339 return modified_keys.values()
340 }
341
342 function changed (object) {
343 return pending_fetches[object.key]
344 || ! cache.hasOwnProperty(object.key)
345 || !backup_cache.hasOwnProperty(object.key)
346 || !(deep_equals(object, backup_cache[object.key]))
347 }
348 function abort_changes (keys) {
349 for (var i=0; i < keys.length; i++)
350 update_cache(backup_cache[keys[i]], cache)
351 }
352
353
354 function forget (key, save_handler) {
355 if (arguments.length === 0) {
356 // Then we're forgetting the executing funk
357 console.assert(executing_funk !== global_funk,
358 'forget() with no arguments forgets the currently executing reactive function.\nHowever, there is no currently executing reactive function.')
359 executing_funk.forget()
360 return
361 }
362 bogus_check(key)
363
364 //log('forget:', key, funk_name(save_handler), funk_name(executing_funk))
365 save_handler = save_handler || executing_funk
366 var fkey = funk_key(save_handler)
367 //console.log('Fetches in is', fetches_in.hash)
368 if (!fetches_in.has(key, fkey)) {
369 console.error("***\n****\nTrying to forget lost key", key,
370 'from', funk_name(save_handler), fkey,
371 "that hasn't fetched that key.",
372 funks[fetches_in.get(key)[0]],
373 funks[fetches_in.get(key)[0]] && funks[fetches_in.get(key)[0]].statebus_id
374 )
375 console.trace()
376 return
377 // throw Error('asdfalsdkfajsdf')
378 }
379
380 fetches_in.delete(key, fkey)
381 unbind(key, 'on_save', save_handler)
382
383 // If this is the last handler listening to this key, then we can
384 // delete the cache entry, send a forget upstream, and de-activate the
385 // .on_fetch handler.
386 if (!fetches_in.has_any(key)) {
387 clearTimeout(to_be_forgotten[key])
388 to_be_forgotten[key] = setTimeout(function () {
389 // Send a forget upstream
390 bus.route(key, 'to_forget', key)
391
392 // Delete the cache entry...?
393 // delete cache[key]
394 delete fetches_out[key]
395 delete to_be_forgotten[key]
396
397 // Todo: deactivate any reactive .on_fetch handler, or
398 // .on_save handler.
399 }, 200)
400
401 // BUG: The delay on forgetting means that reactive functions that
402 // call forget() will still get re-run for a while. For now, they
403 // cannot depend on forget() making them not re-run
404 // immediately... we could fix this by adding a check when
405 // re-running for a key to see if the key is in to_be_forgotten,
406 // and not run anything that is supposed to be forgotten.
407 }
408 }
409 function del (key) {
410 key = key.key || key // Prolly disable this in future
411 bogus_check(key)
412
413 if ((executing_funk !== global_funk) && executing_funk.loading()) {
414 abort_changes([key])
415 return
416 }
417
418 statelog(key, yellow, 'v', 'Deleting ' + key)
419 // Call the to_delete handlers
420 var handlers_called = bus.route(key, 'to_delete', key)
421 if (handlers_called === 0)
422 // And go ahead and delete if there aren't any!
423 delete cache[key]
424
425 // console.warn("Deleting " + key + "-- Statebus doesn't yet re-run functions subscribed to it, or update versions")
426
427 // Todos:
428 //
429 // - Add transactions, so you can check permissions, abort a delete,
430 // etc.
431 // - NOTE: I did a crappy implementation of abort just now above!
432 // But it doesn't work if called after the to_delete handler returns.
433 // - Generalize the code across save and del with a "mutate"
434 // operation
435 //
436 // - Right now we fire the to_delete handlers right here.
437 //
438 // - Do we want to batch them up and fire them later?
439 // e.g. we could make a mark_deleted(key) like mark_changed(key)
440 //
441 // - We might also record a new version of the state to show that
442 // it's been deleted, which we can use to cancel echoes from the
443 // sending bus.
444
445 }
446
447 var changed_keys = new Set()
448 var dirty_fetchers = new Set()
449 function dirty (key, t) {
450 statelog(key, brown, '*', bus + ".dirty('"+key+"')")
451 bogus_check(key)
452
453 // Find any .to_fetch, and mark as dirty so that it re-runs
454 var found = false
455 if (fetches_out.hasOwnProperty(key))
456 for (var i=0; i<fetches_out[key].length; i++) {
457 dirty_fetchers.add(funk_key(fetches_out[key][i]))
458 found = true
459 }
460 clean_timer = clean_timer || setTimeout(clean)
461
462 // If none found, then just mark the key changed
463 if (!found && cache.hasOwnProperty(key)) mark_changed(key, t)
464 }
465
466 function mark_changed (key, t) {
467 // Marks a key as dirty, meaning that functions on it need to update
468 log('Marking changed', bus, key)
469 changed_keys.add(key)
470 clean_timer = clean_timer || setTimeout(clean)
471 }
472
473 function clean () {
474 // 1. Collect all functions for all keys and dirtied fetchers
475 var dirty_funks = new Set()
476 for (var b in busses) {
477 var fs = busses[b].rerunnable_funks()
478 for (var i=0; i<fs.length; i++)
479 dirty_funks.add(fs[i])
480 }
481 clean_timer = null
482
483 // 2. Run any priority function first (e.g. file_store's on_save)
484 dirty_funks = dirty_funks.values()
485 log('Cleaning up', dirty_funks.length, 'funks')
486 for (var i=0; i<dirty_funks.length; i++) {
487 // console.log(funks[dirty_funks[i]].proxies_for)
488 var p = funks[dirty_funks[i]].proxies_for
489 if (p && p.priority) {
490 log('Clean-early:', funk_name(funks[dirty_funks[i]]))
491 funks[dirty_funks[i]].react()
492 dirty_funks.splice(i,1)
493 i--
494 }
495 }
496
497 // 3. Re-run the functions
498 for (var i=0; i<dirty_funks.length; i++) {
499 log('Clean:', funk_name(funks[dirty_funks[i]]))
500 if (bus.render_when_loading || !funks[dirty_funks[i]].loading())
501 funks[dirty_funks[i]].react()
502 }
503 // log('We just cleaned up', dirty_funks.length, 'funks!')
504 }
505
506 function rerunnable_funks () {
507 var result = []
508 var keys = changed_keys.values()
509 var fetchers = dirty_fetchers.values()
510
511 //log(bus+' Cleaning up!', keys, 'keys, and', fetchers.length, 'fetchers')
512 for (var i=0; i<keys.length; i++) { // Collect all keys
513 // if (to_be_forgotten[keys[i]])
514 // // Ignore changes to keys that have been forgotten, but not
515 // // processed yet
516 // continue
517 var fs = bindings(keys[i], 'on_save')
518 for (var j=0; j<fs.length; j++) {
519 var f = fs[j].func
520 if (f.react) {
521 // Skip if it's already up to date
522 var v = f.fetched_keys[JSON.stringify([this.id, keys[i]])]
523 //log('re-run:', keys[i], f.statebus_id, f.fetched_keys)
524 if (v && v.indexOf(versions[keys[i]]) !== -1) {
525 log('skipping', funk_name(f), 'already at version', versions[keys[i]], 'proof:', v)
526 continue
527 }
528 } else {
529 // Fresh handlers are always run, but need a wrapper
530 f.seen_keys = f.seen_keys || {}
531 var v = f.seen_keys[JSON.stringify([this.id, keys[i]])]
532 if (v && v.indexOf(versions[keys[i]]) !== -1) {
533 //log('skipping', funk_name(f), 'already at version', v)
534 continue
535 }
536 autodetect_args(f)
537 f = run_handler(f, 'on_save', cache[keys[i]], {dont_run: true,
538 binding: keys[i]})
539 }
540 result.push(funk_key(f))
541 }
542 }
543 for (var i=0; i<fetchers.length; i++) // Collect all fetchers
544 result.push(fetchers[i])
545
546 changed_keys.clear()
547 dirty_fetchers.clear()
548
549 //log('found', result.length, 'funks to re run')
550
551 return result
552 }
553
554 // ****************
555 // Connections
556 function subspace (key) {
557 var result = {}
558 for (var method in {to_fetch:null, to_save:null, on_save:null,
559 to_delete:null, to_forget:null})
560 (function (method) {
561 Object.defineProperty(result, method, {
562 set: function (func) {
563 autodetect_args(func)
564 func.defined = func.defined || []
565 func.defined.push(
566 {as:'handler', bus:bus, method:method, key:key})
567 bind(key, method, func, 'allow_wildcards')
568 },
569 get: function () {
570 var result = bindings(key, method)
571 for (var i=0; i<result.length; i++) result[i] = result[i].func
572 result.delete = function (func) { unbind (key, method, func, 'allow_wildcards') }
573 return result
574 }
575 })
576 })(method)
577 return result
578 }
579
580 function autodetect_args (handler) {
581 if (handler.args) return
582
583 // Get an array of the handler's params
584 var comments = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg,
585 params = /([^\s,]+)/g,
586 s = handler.toString().replace(comments, '')
587 params = s.slice(s.indexOf('(')+1, s.indexOf(')')).match(params) || []
588
589 handler.args = {}
590 for (var i=0; i<params.length; i++)
591 switch (params[i]) {
592 case 'key':
593 case 'k':
594 handler.args['key'] = i; break
595 case 'json':
596 case 'vars':
597 handler.args['vars'] = i; break
598 case 'star':
599 case 'rest':
600 handler.args['rest'] = i; break
601 case 't':
602 case 'transaction':
603 handler.args['t'] = i; break
604 case 'o':
605 case 'obj':
606 case 'val':
607 case 'new':
608 case 'New':
609 handler.args['obj'] = i; break
610 case 'old':
611 handler.args['old'] = i; break
612 }
613 }
614
615 // The funks attached to each key, maps e.g. 'fetch /point/3' to '/30'
616 var handlers = new One_To_Many()
617 var wildcard_handlers = [] // An array of {prefix, method, funk}
618
619 // A set of timers, for keys to send forgets on
620 var to_be_forgotten = {}
621 function bind (key, method, func, allow_wildcards) {
622 bogus_check(key)
623 if (allow_wildcards && key[key.length-1] === '*')
624 wildcard_handlers.push({prefix: key,
625 method: method,
626 funk: func})
627 else
628 handlers.add(method + ' ' + key, funk_key(func))
629
630 // Now check if the method is a fetch and there's a fetched
631 // key in this space, and if so call the handler.
632 }
633 function unbind (key, method, funk, allow_wildcards) {
634 bogus_check(key)
635 if (allow_wildcards && key[key.length-1] === '*')
636 // Delete wildcard connection
637 for (var i=0; i<wildcard_handlers.length; i++) {
638 var handler = wildcard_handlers[i]
639 if (handler.prefix === key
640 && handler.method === method
641 && handler.funk === funk) {
642
643 wildcard_handlers.splice(i,1) // Splice this element out of the array
644 i-- // And decrement the counter while we're looping
645 }
646 }
647 else
648 // Delete direct connection
649 handlers.delete(method + ' ' + key, funk_key(funk))
650 }
651
652 function bindings(key, method) {
653 bogus_check(key)
654 if (typeof key !== 'string') {
655 console.error('Error:', key, 'is not a string', method)
656 console.trace()
657 }
658
659 //console.log('bindings:', key, method)
660 var result = []
661 var seen = {}
662
663 // First get the exact key matches
664 var exacts = handlers.get(method + ' ' + key)
665 for (var i=0; i < exacts.length; i++) {
666 var f = funks[exacts[i]]
667 if (!seen[funk_key(f)]) {
668 f.statebus_binding = {key:key, method:method}
669 result.push({method:method, key:key, func:f})
670 seen[funk_key(f)] = true
671 }
672 }
673
674 // Now iterate through prefixes
675 for (var i=0; i < wildcard_handlers.length; i++) {
676 handler = wildcard_handlers[i]
677
678 var prefix = handler.prefix.slice(0, -1) // Cut off the *
679 if (prefix === key.substr(0,prefix.length) // If the prefix matches
680 && method === handler.method // And it has the right method
681 && !seen[funk_key(handler.funk)]) {
682 handler.funk.statebus_binding = {key:handler.prefix, method:method}
683 result.push({method:method, key:handler.prefix, func:handler.funk})
684 seen[funk_key(handler.funk)] = true
685 }
686 }
687
688 return result
689 }
690
691 function run_handler(funck, method, arg, options) {
692 options = options || {}
693 var t = options.t,
694 just_make_it = options.dont_run,
695 binding = options.binding
696
697 // When we first run a handler (e.g. a fetch or save), we wrap it in a
698 // reactive() funk that calls it with its arg. Then if it fetches or
699 // saves, it'll register a .on_save handler with this funk.
700
701 // Is it reactive already? Let's distinguish it.
702 var funk = funck.react && funck, // Funky! So reactive!
703 func = !funk && funck // Just a function, waiting for a rapper to show it the funk.
704
705 console.assert(funk || func)
706
707 if (false && !funck.global_funk) {
708 // \u26A1
709 var event = {'to_save':'save','on_save':'save.fire','to_fetch':'fetch',
710 'to_delete':'delete','to_forget':'forget'}[method],
711 triggering = funk ? 're-running' : 'initiating'
712 console.log(' > a', bus+'.'+event + "('" + (arg.key||arg) + "') is " + triggering
713 +'\n ' + funk_name(funck))
714 }
715
716 if (funk) {
717 // Then this is an on_save event re-triggering an already-wrapped
718 // funk. It has its own arg internally that it's calling itself
719 // with. Let's tell it to re-trigger itself with that arg.
720
721 if (method !== 'on_save') {
722 console.error(method === 'on_save', 'Funk is being re-triggered, but isn\'t on_save. It is: "' + method + '", oh and funk: ' + funk_name(funk))
723 return
724 }
725 return funk.react()
726
727 // This might not work that great.
728 // Ex:
729 //
730 // bus('foo').on_save = function (o) {...}
731 // save({key: 'foo'})
732 // save({key: 'foo'})
733 // save({key: 'foo'})
734 //
735 // Does this spin up 3 reactive functions? I think so.
736 // No, I think it does, but they all get forgotten once
737 // they run once, and then are garbage collected.
738 //
739 // bus('foo*').on_save = function (o) {...}
740 // save({key: 'foo1'})
741 // save({key: 'foo2'})
742 // save({key: 'foo1'})
743 // save({key: 'foo3'})
744 //
745 // Does this work ok? Yeah, I think so.
746 }
747
748 // Alright then. Let's wrap this func with some funk.
749
750 // Fresh fetch/save/forget/delete handlers will just be regular
751 // functions. We'll store their arg and let them re-run until they
752 // are done re-running.
753 function key_arg () { return ((typeof arg.key) == 'string') ? arg.key : arg }
754 function rest_arg () { return (key_arg()).substr(binding.length-1) }
755 function vars_arg () {
756 var r = rest_arg()
757 try {
758 return JSON.parse(r)
759 } catch (e) {
760 return 'Bad JSON "' + r + '" for key ' + key_arg()
761 }
762 }
763 var f = reactive(function () {
764
765 // Initialize transaction
766 t = clone(t || {})
767 if (!(method in {to_fetch:1, to_forget:1}))
768 t.abort = function () {
769 var key = method === 'to_save' ? arg.key : arg
770 if (f.loading()) return
771 bus.cache[key] = bus.cache[key] || {key: key}
772 bus.backup_cache[key] = bus.backup_cache[key] || {key: key}
773 bus.save.abort(bus.cache[key])
774 }
775 if (method !== 'to_forget')
776 t.done = function (o) {
777 var key = method === 'to_save' ? arg.key : arg
778 bus.log('We are DONE()ing', method, key, o||arg)
779
780 // We use a simple (and crappy?) heuristic to know if to
781 // to_save handler has changed the state: whether the
782 // programmer passed (o) to the t.done(o) handler. If
783 // not, we assume it hasn't changed. If so, we assume it
784 // *has* changed, and thus we change the version of the
785 // state. I imagine it would be more accurate to diff o
786 // from before the to_save handler began with when
787 // t.done(o) ran.
788 if (o) t.version = new_version()
789
790 if (method === 'to_delete')
791 delete bus.cache[key]
792 else if (method === 'to_save')
793 bus.save.fire(o || arg, t)
794 else { // Then method === to_fetch
795 o.key = key
796 bus.save.fire(o, t)
797 // And now reset the version cause it could get called again
798 delete t.version
799 }
800 }
801 t.return = t.done
802 if (method === 'to_save')
803 t.refetch = function () { bus.dirty(arg.key) }
804
805 // Then in run_handler, we'll call it with:
806 var args = []
807 args[0] = arg
808 args[1] = t
809
810 //console.log('This funcs args are', func.args)
811 for (var k in (func.args||{})) {
812 switch (k) {
813 case 'key':
814 args[func.args[k]] = key_arg(); break
815 case 'rest':
816 args[func.args[k]] = rest_arg(); break
817 case 'vars':
818 args[func.args[k]] = vars_arg();
819 //console.log('We just made an arg', args[func.args[k]], 'in slot', func.args[k], 'for', k)
820 break
821 case 't':
822 args[func.args[k]] = t; break
823 case 'obj':
824 args[func.args[k]] = arg.key ? arg : bus.cache[arg]; break
825 case 'old':
826 var key = key_arg()
827 args[func.args[k]] = bus.cache[key] || (bus.cache[key] = {key:key})
828 break
829 }
830 //console.log('processed', k, 'at slot', func.args[k], 'to make', args[func.args[k]])
831 }
832 //console.log('args is', args)
833
834 // Call the raw function here!
835 var result = func.apply(null, args)
836
837 // We will wanna add in the fancy arg stuff here, with:
838 // arr = []
839 // for (var k of func.args || {})
840 // arr[func.args[k]] = <compute_blah(k)>
841
842 // Trigger done() or abort() by return value
843 console.assert(!(result === 'to_fetch' &&
844 (result === 'done' || result === 'abort')),
845 'Returning "done" or "abort" is not allowed from to_fetch handlers')
846 if (result === 'done') t.done()
847 if (result === 'abort') t.abort()
848
849 // For fetch
850 if (method === 'to_fetch' && result instanceof Object
851 && !f.loading() // Experimental.
852 ) {
853 result.key = arg
854 var new_t = clone(t || {})
855 new_t.to_fetch = true
856 save.fire(result, new_t)
857 return result
858 }
859
860 // Save, forget and delete handlers stop re-running once they've
861 // completed without anything loading.
862 // ... with f.forget()
863 if (method !== 'to_fetch' && !f.loading())
864 f.forget()
865 })
866 f.proxies_for = func
867 f.arg = arg
868
869 // to_fetch handlers stop re-running when the key is forgotten
870 if (method === 'to_fetch') {
871 var key = arg
872 function handler_done () {
873 f.forget()
874 unbind(key, 'to_forget', handler_done)
875 }
876 bind(key, 'to_forget', handler_done)
877
878 // // Check if it's doubled-up
879 // if (fetches_out[key])
880 // console.error('Two .to_fetch functions are running on the same key',
881 // key+'!', funk_name(funck), funk_name(fetches_out[key]))
882
883 fetches_out[key] = fetches_out[key] || []
884 fetches_out[key].push(f) // Record active to_fetch handler
885 pending_fetches[key] = f // Record that the fetch is pending
886 }
887
888 if (just_make_it)
889 return f
890
891 return f()
892 }
893
894 // route() can be overridden
895 bus.route = function (key, method, arg, t) {
896 var handlers = bus.bindings(key, method)
897 if (handlers.length)
898 log('route:', bus+'("'+key+'").'+method+'['+handlers.length+'](key:"'+(arg.key||arg)+'")')
899 // log('route: got bindings',
900 // funcs.map(function (f) {return funk_key(f)+':'+funk_keyr(f)}))
901 for (var i=0; i<handlers.length; i++)
902 bus.run_handler(handlers[i].func, method, arg, {t: t, binding: handlers[i].key})
903
904 // if (method === 'to_fetch')
905 // console.assert(handlers.length<2,
906 // 'Two to_fetch functions are registered for the same key '+key,
907 // handlers)
908 return handlers.length
909 }
910
911
912 // ****************
913 // Reactive functions
914 //
915 // We wrap any function with a reactive wrapper that re-calls it whenever
916 // state it's fetched changes.
917
918 if (!global_funk) {
919 global_funk = reactive(function global_funk () {})
920 global_funk.global_funk = true
921 executing_funk = global_funk
922 funks[global_funk.statebus_id = 'global funk'] = global_funk
923 }
924
925 function reactive(func) {
926 // You can call a funk directly:
927 //
928 // f = reactive(func)
929 // f(arg1, arg2)
930 //
931 // This will remember every fetch it depends on, and make it re-call
932 // itself whenever that state changes. It will remember arg1 and arg2
933 // and use those again. You can also trigger a re-action manually
934 // with:
935 //
936 // funk.react().
937 //
938 // ...which will make it re-run with the original arg1 and arg2 .
939 function funk () {
940 console.assert(executing_funk === global_funk
941 || executing_funk !== funk, 'Recursive funk', funk.func)
942
943 if (funk.called_directly)
944 funk.this = this, funk.args = arguments
945
946 // Forget the keys from last time
947 funk.forget()
948
949 // Now let's run it
950 var last_executing_funk = executing_funk
951 executing_funk = funk
952 try {
953 var result = func.apply(funk.this, funk.args)
954 } catch (e) {
955 if (e.message === 'Maximum call stack size exceeded') {
956 console.error(e)
957 process.exit()
958 }
959 //executing_funk = null // Or should this be last_executing_funk?
960 if (funk.loading()) return null
961 else {
962 // If we ware on node, then just print out the error
963 if (nodejs) {
964 console.error(e.stack)
965 process.exit()
966 } else {
967 // This is the best way to print errors in browsers,
968 // so that they get clickable line numbers
969 var result = func.apply(funk.this, funk.args)
970 // If code reaches here, there was an error triggering
971 // the error. We should warn the programmer, and then
972 // probably move on, because maybe the error went
973 // away... and it doesn't do us any good to just crash
974 // now, does it? Then the programmer has less
975 // information on what happened because he/she can't
976 // see it in the result, which might also be fucked
977 // up, and might be informative.
978 console.error('Non-deterministic Error!', e.stack || e)
979 console.warn("A non-deterministic error is when your reactive function triggers an error only some of the times it's called.\nThe error originated from calling:", funk_name(func, 400))
980 }
981 }
982 } finally {
983 executing_funk = last_executing_funk
984 }
985 return result
986 }
987
988 funk.func = func // just for debugging
989 funk.called_directly = true
990 funk.fetched_keys = {} // maps [bus,key] to version
991 // version will be undefined until loaded
992 funk.abortable_keys = []
993 funk.has_seen = function (bus, key, version) {
994 //console.log('depend:', bus, key, versions[key])
995 var bus_key = JSON.stringify([bus.id, key])
996 var seen_versions =
997 this.fetched_keys[bus_key] = this.fetched_keys[bus_key] || []
998 seen_versions.push(version)
999 if (versions.length > 10) versions.shift()
1000 }
1001 funk.react = function () {
1002 var result
1003 try {
1004 funk.called_directly = false
1005 result = funk()
1006 } finally {
1007 funk.called_directly = true
1008 }
1009 return result
1010 }
1011 funk.forget = function () {
1012 // Todo: This will bug out if an .on_save handler for a key also
1013 // fetches that key once, and then doesn't fetch it again, because
1014 // when it fetches the key, that key will end up being a
1015 // fetched_key, and will then be forgotten as soon as the funk is
1016 // re-run, and doesn't fetch it again, and the fact that it is
1017 // defined as an .on_save .on_save handler won't matter anymore.
1018
1019 if (funk.statebus_id === 'global funk') return
1020
1021 for (var hash in funk.fetched_keys) {
1022 var tmp = JSON.parse(hash),
1023 bus = busses[tmp[0]], key = tmp[1]
1024 if (bus) // Cause it might have been deleted
1025 bus.forget(key, funk)
1026 }
1027 funk.fetched_keys = {}
1028 }
1029 funk.loading = function () {
1030 for (var hash in funk.fetched_keys) {
1031 var tmp = JSON.parse(hash),
1032 bus = busses[tmp[0]], key = tmp[1]
1033 if (bus // Cause it might have been deleted
1034 && bus.pending_fetches[key])
1035 return true
1036 }
1037 return false
1038 }
1039
1040 // for backwards compatibility
1041 funk.is_loading = funk.loading
1042
1043 return funk
1044 }
1045
1046 function loading_keys (keys) {
1047 // Do any of these keys have outstanding gets?
1048 //console.log('Loading: pending_keys is', pending_fetches)
1049 for (var i=0; i<keys.length; i++)
1050 if (pending_fetches[keys[i]]) return true
1051 return false
1052 }
1053
1054 // Tells you whether the currently executing funk is loading
1055 function loading () { return executing_funk.loading() }
1056
1057 bus.default = function () {
1058 bus.deep_map(arguments, function (o) {
1059 if (o.key && !(bus.cache.hasOwnProperty(o.key)))
1060 bus.cache[o.key] = o
1061 return o
1062 })
1063 }
1064
1065 function once (f) {
1066 var r = reactive(function () {
1067 f()
1068 if (!r.loading()) r.forget()
1069 })
1070 r()
1071 }
1072
1073 // ******************
1074 // Pretty Printing
1075
1076 if (nodejs)
1077 var red = '\x1b[31m', normal = '\x1b[0m', grey = '\x1b[0;38;5;245m',
1078 green = '\x1b[0;38;5;46m', brown = '\x1b[0;38;5;130m',
1079 yellow = '\x1b[0;38;5;226m'
1080 else
1081 var red = '', normal = '', grey = '',
1082 green = '', brown = ''
1083 function add_diff_msg (message, obj) {
1084 var diff = sorta_diff(backup_cache[obj.key], obj)
1085 if (diff) {
1086 var end_col = message.length + 2 + statelog_indent * 3
1087 for (var i=0; i<40-end_col; i++) message += ' '
1088 message += diff.substring(0,80)
1089 }
1090 else message += ' <no diff>'
1091 return message
1092 }
1093 function save_msg (obj, t, meth) {
1094 if (!honking_at(obj.key)) return
1095 var message = (t && t.m) || bus + "."+meth+"('"+obj.key+"')"
1096 message = add_diff_msg(message, obj)
1097 if (t.version) message += ' [' + t.version + ']'
1098 return message
1099 }
1100
1101
1102 // ******************
1103 // Fancy Stuff
1104
1105 var uncallback_counter = 0
1106 function uncallback (f, options) {
1107 name = (options && options.name) || f.name || (uncallback_counter+'')
1108 if (!name) throw 'Uncallback function needs a name'
1109 var watching = {}
1110 var prefix = 'uncallback/' + name
1111 bus(prefix + '/*').to_fetch = function (key, json) {
1112 var args = json
1113 function cb (err, result) {
1114 if (err) {
1115 console.trace('have err:', err, 'and result is', JSON.stringify(result))
1116 throw err
1117 } else
1118 bus.save.fire({key: key, _: result})
1119 }
1120
1121 // Inject the callback into the right place
1122 args[options.callback_at || args.length] = cb
1123
1124 // And call the underlying function
1125 f.apply({key:key}, args)
1126 if (options.start_watching && !watching[key]) {
1127 watching[key] = true
1128 options.start_watching(
1129 args,
1130 function () { bus.dirty(key) },
1131 function () { bus.del(key) }
1132 )
1133 }
1134 }
1135 if (options.stop_watching)
1136 bus(prefix + '/*').to_forget = function (key, json) {
1137 console.assert(watching[key],
1138 'Forgetting a watcher for ' + JSON.stringify(key)
1139 + ' that is not enabled')
1140 delete watching[key]
1141 options.stop_watching(json)
1142 }
1143 return function () {
1144 var args = [].slice.call(arguments)
1145 return bus.fetch(prefix + '/' + JSON.stringify(args))._
1146 }
1147 }
1148
1149 function unpromise (f) {
1150 // Doesn't work yet! In progress.
1151 return uncallback(function () {
1152 var args = [].slice.call(arguments)
1153 var cb = args.pop()
1154 f.apply(null, args).then(cb)
1155 })
1156 }
1157
1158 var sb = (function sb () {
1159 // I have the cache behind the scenes
1160 // Each proxy has a target object -- the raw data on cache
1161 // If we're proxying a {_: ...} singleton then ...
1162
1163 function item_proxy (base, o) {
1164 if (typeof o === 'number'
1165 || typeof o === 'string'
1166 || typeof o === 'boolean'
1167 || o === undefined
1168 || o === null
1169 || typeof o === 'function') return o
1170
1171 return new Proxy(o, {
1172 get: function get(o, k) {
1173 if (k === 'inspect' || k === 'valueOf' || typeof k === 'symbol')
1174 return undefined
1175 k = encode_field(k)
1176 return item_proxy(base, o[k])
1177 },
1178 set: function set(o, k, v) {
1179 var result = o[encode_field(k)] = v
1180 bus.save(base)
1181 return result
1182 },
1183 has: function has(o, k) {
1184 return o.hasOwnProperty(encode_field(k))
1185 },
1186 deleteProperty: function del (o, k) {
1187 delete o[encode_field(k)]
1188 },
1189 apply: function apply (o, This, args) {
1190 return o
1191 }
1192 })}
1193
1194 return new Proxy(cache, {
1195 get: function get(o, k) {
1196 if (k in bogus_keys) return o[k]
1197 if (k === 'inspect' || k === 'valueOf' || typeof k === 'symbol')
1198 return undefined
1199 var raw = bus.fetch(k),
1200 obj = raw
1201 while (typeof obj == 'object' && '_' in obj) obj = obj._
1202 return item_proxy(raw, obj)
1203 },
1204 set: function set(o, k, v) {
1205 if (typeof v === 'number'
1206 || typeof v === 'string'
1207 || typeof v === 'boolean'
1208 || v === undefined
1209 || v === null
1210 || typeof v === 'function'
1211 || Array.isArray(v))
1212 v = {_:v}
1213 else
1214 v = bus.clone(v)
1215 v.key = k
1216 bus.save(v)
1217 },
1218 // In future, this might check if there's a .to_fetch function OR
1219 // something in the cache:
1220 //
1221 // has: function has(o, k) {
1222 // return k in o
1223 // },
1224 // ... but I haven't had a need yet.
1225 deleteProperty: function del (o, k) {
1226 bus.delete(encode_field(k))
1227 }
1228 })
1229 })()
1230
1231
1232 // ******** State (Proxy) API *********
1233 //
1234 // The top-level state[..] object translates pointers of the
1235 // special form:
1236 //
1237 // {key: <s>, _: *}
1238 //
1239 // Examples:
1240 //
1241 // state['uninitialized']
1242 // >> undefined
1243 //
1244 // state['uninitialized'] = {}
1245 // >> {key: 'uninitialized'}
1246 //
1247 // state['uninitialized'] = {a: 3}
1248 // >> {key: 'uninitialized', a: 3}
1249 //
1250 // state['uninitialized'] = []
1251 // >> {key: 'uninitialized', _: []}
1252 //
1253 // delete state['uninitialized']
1254 // state['uninitialized']
1255 // >> undefined
1256 //
1257 // ** Rules
1258 // Setting:
1259 // - Escape-translate each field recursively
1260 // - If setting an object, put each field directly on it
1261 // - If setting anything else, put it into ._
1262 //
1263 // Getting:
1264 // - If it has unescaped fields other than ._, return object with them
1265 // - Otherwise, return ._
1266 // - Unescape all fields
1267
1268 var strict_mode = (function () {return !this})()
1269 function pget (base, o, k) {
1270 // console.log('pget:', {base, o, k})
1271
1272 if (base) {
1273 o = o[k]
1274
1275 // If new base, update and subscribe
1276 if (typeof o == 'object' && 'key' in o) {
1277 base = o
1278 bus.fetch(o.key)
1279 }
1280 } else {
1281 // We are getting from the Root
1282 o = bus.fetch(k)
1283 // console.log('pget: fetched', k, 'and got', o)
1284 base = o
1285 if (bus.validate(o, {key: '*', '?_': '*'})) {
1286 // console.log('pget: jumping into the _')
1287 o = o._
1288 }
1289 if (typeof o === 'object' && o.key)
1290 base = o
1291 }
1292
1293 // Follow symlinks
1294 if (typeof o == 'object' && 'key' in o && '_' in o) {
1295 // Note: I don't actually need this recursion here, because
1296 // recursively linked state cannot be created by the proxy API.
1297 // So it can be undefined behavior.
1298 var tmp = pget(base, o, '_')
1299 base = tmp[0]
1300 o = tmp[1]
1301 }
1302
1303 return [base, o]
1304 }
1305
1306 function proxy_encode_val (x) {
1307 // Arrays
1308 if (Array.isArray(x)) {
1309 var result = []
1310 for (var i=0; i < x.length; i++)
1311 result[i] = proxy_encode_val(x[i])
1312 return result
1313 }
1314
1315 // Objects
1316 else if (typeof x === 'object') {
1317 // Actual objects need their keys translated
1318 var result = {}
1319 for (var k in x)
1320 result[encode_field(k)] = proxy_encode_val(x[k])
1321 return result
1322 }
1323
1324 // Proxieds: already have JSON, stored inside. Return it.
1325 else if (typeof x === 'function' && x[symbols.is_proxy]) {
1326 return x()
1327 }
1328
1329 // Everything else return
1330 return x
1331 }
1332 function proxy_decode_json (json) {
1333 // Returns data for proxies
1334 // - Remove keys
1335 // - Translate
1336
1337 // Root objects of special form
1338 if (bus.validate(json, {key: '*', '_': '*'}))
1339 return proxy_decode_json(json._)
1340
1341 // Arrays
1342 if (Array.isArray(json)) {
1343 var arr = json.slice()
1344 for (var i=0; i<arr.length; i++)
1345 arr[i] = proxy_decode_json(arr[i])
1346 return arr
1347 }
1348
1349 // Objects
1350 if (typeof json === 'object' && json !== null) {
1351 var obj = {}
1352 for (var k in json)
1353 if (k !== 'key')
1354 obj[decode_field(k)] = proxy_decode_json(json[k])
1355 return obj
1356 }
1357
1358 // Other primitives just return
1359 return json
1360 }
1361
1362 if (nodejs) var util = require('util')
1363 function make_proxy (base, o) {
1364 if (!symbols)
1365 symbols = {is_proxy: Symbol('is_proxy'),
1366 get_json: Symbol('get_json'),
1367 get_base: Symbol('get_base')}
1368
1369 if (typeof o !== 'object' || o === null) return o
1370
1371 function get_json() {
1372 // Pop up to parent if this is a singleton array.
1373 // We know it's a singleton array if base._ === x(), and base is
1374 // of the form {key: *, _: x}
1375 if (base && base._ && Object.keys(base).length === 2
1376 && base._ === o)
1377 return base
1378
1379 // Otherwise return x's JSON.
1380 return o
1381 }
1382
1383 // Javascript won't let us function call a proxy unless the "target"
1384 // is a function. So we make a dummy target, and don't use it.
1385 var dummy_obj = function () {}
1386 return new Proxy(dummy_obj, {
1387 get: function (dummy_obj, k) {
1388 // console.log('get:', k, '::'+typeof k, 'on', o)
1389
1390 // Print something nice for Node console inspector
1391 if (nodejs && k === util.inspect.custom) {
1392 if (o == bus.cache)
1393 return function () {return 'state'+bus.toString().substr(3)}
1394 return function () {return 'p: '+util.format(proxy_decode_json(o))}
1395 }
1396 if (k in bogus_keys) return o[k]
1397 // Proxies distinguish themselves via proxy.is_proxy == true
1398 if (k === symbols.is_proxy) return true
1399 if (k === symbols.get_json) return get_json()
1400 if (k === symbols.get_base) return base
1401 if (k === Symbol.isConcatSpreadable) return Array.isArray(o)
1402 if (k === Symbol.toPrimitive) return function () {
1403 return JSON.stringify(proxy_decode_json(o))
1404 }
1405 if (typeof k === 'symbol') {
1406 console.warn('Got request for weird symbol', k)
1407 return undefined
1408 }
1409
1410 var tmp2 = pget(base, o, encode_field(k))
1411 var base2 = tmp2[0]
1412 var o2 = tmp2[1]
1413
1414 // console.log('returning proxy on', base2, o2)
1415 return make_proxy(base2, o2)
1416 },
1417 set: function (dummy_obj, k, v) {
1418 // console.log('set:', {base, o, k, v})
1419
1420 if (base) {
1421 var encoded_v = o[encode_field(k)] = proxy_encode_val(v)
1422 // console.log(' set: saving', encoded_v, 'into', base)
1423
1424 // Collapse state of the form:
1425 // {key: '*', _: {foo: bar, ...}}
1426 // down to:
1427 // {key: '*', foo: bar}
1428 if (base._
1429 && Object.keys(base).length === 2
1430 && typeof base._ === 'object'
1431 && base._ !== null
1432 && !Array.isArray(base._)
1433 && !base._.key
1434 && Object.keys(base._).length !== 0) {
1435 // console.log('Collapsing', JSON.stringify(base))
1436 for (var k2 in base._)
1437 base[k2] = base._[k2]
1438 delete base._
1439 }
1440
1441 bus.save(base)
1442 }
1443
1444 // Saving into top-level state
1445 else {
1446 var encoded_v = proxy_encode_val(v)
1447 // console.log(' set top-level:', {v, encoded_v})
1448
1449 // Setting a top-level object to undefined wipes it out
1450 if (v === undefined)
1451 encoded_v = {key: k}
1452
1453 // Prefix with _: anything that is:
1454 else if (// A proxy to another state
1455 (typeof v === 'object' && v[symbols.is_proxy])
1456 // An empty {} object
1457 || (typeof v === 'object' && Object.keys(v).length === 0)
1458 // A number, bool, string, function, etc
1459 || typeof v !== 'object' || v === null
1460 // An array
1461 || Array.isArray(v))
1462 encoded_v = {_: encoded_v}
1463 encoded_v.key = k
1464
1465 // console.log(' set top-level: now encoded_v is', encoded_v)
1466 bus.save(encoded_v)
1467 }
1468
1469 var newbase = (encoded_v && encoded_v.key) ? encoded_v : base
1470 return true
1471 },
1472 has: function has(O, k) {
1473 // XXX QUESTIONS:
1474 //
1475 // - Do I want this to return true if there's a .to_fetch()
1476 // function for this o, k?
1477 //
1478 // - Does this need to do a fetch as well?
1479 //
1480 // - For a keyed object, should this do a loading() check?
1481 return o.hasOwnProperty(encode_field(k))
1482 },
1483 deleteProperty: function del (O, k) {
1484 if (base) {
1485 // console.log(' deleting:', encode_field(k), 'of', o)
1486 delete o[encode_field(k)] // Deleting innards
1487 if (Object.keys(o).length === 1 && o.key)
1488 o._ = {}
1489 bus.save(base)
1490 }
1491 else
1492 bus.delete(encode_field(k)) // Deleting top-level
1493 },
1494 apply: function apply (f, This, args) { return get_json() }
1495 })
1496 }
1497 if (nodejs || window.Proxy)
1498 var state = make_proxy(null, cache)
1499
1500 // So chrome can print out proxy objects decently
1501 if (!nodejs)
1502 window.devtoolsFormatters = [{
1503 header: function (x) {
1504 return x[symbols.is_proxy] &&
1505 ['span', {style: 'background-color: #feb; padding: 3px;'},
1506 JSON.stringify(proxy_decode_json(x()))]
1507 },
1508 hasBody: function (x) {return false}
1509 }]
1510
1511 // ******************
1512 // Network client
1513 function get_domain (key) { // Returns e.g. "state://foo.com"
1514 var m = key.match(/^i?statei?\:\/\/(([^:\/?#]*)(?:\:([0-9]+))?)/)
1515 return m && m[0]
1516 }
1517 function message_method (m) {
1518 return (m.fetch && 'fetch')
1519 || (m.save && 'save')
1520 || (m['delete'] && 'delete')
1521 || (m.forget && 'forget')
1522 }
1523
1524 function net_mount (prefix, url, client_creds) {
1525 // Local: state://foo.com/* or /*
1526 var preprefix = prefix.slice(0,-1)
1527 var is_absolute = /^i?statei?:\/\//
1528 var has_prefix = new RegExp('^' + preprefix)
1529 var bus = this
1530 var sock
1531 var attempts = 0
1532 var outbox = []
1533 var client_fetched_keys = new bus.Set()
1534 var heartbeat
1535 if (url[url.length-1]=='/') url = url.substr(0,url.length-1)
1536 function nlog (s) {
1537 if (nodejs) {console.log(s)} else console.log('%c' + s, 'color: blue')
1538 }
1539 function send (o, pushpop) {
1540 pushpop = pushpop || 'push'
1541 o = rem_prefixes(o)
1542 var m = message_method(o)
1543 if (m == 'fetch' || m == 'delete' || m == 'forget')
1544 o[m] = rem_prefix(o[m])
1545 bus.log('net_mount.send:', JSON.stringify(o))
1546 outbox[pushpop](JSON.stringify(o))
1547 flush_outbox()
1548 }
1549 function flush_outbox() {
1550 if (sock.readyState === 1)
1551 while (outbox.length > 0)
1552 sock.send(outbox.shift())
1553 else
1554 setTimeout(flush_outbox, 400)
1555 }
1556 function add_prefix (key) {
1557 return is_absolute.test(key) ? key : preprefix + key }
1558 function rem_prefix (key) {
1559 return has_prefix.test(key) ? key.substr(preprefix.length) : key }
1560 function add_prefixes (obj) {
1561 return bus.translate_keys(bus.clone(obj), add_prefix) }
1562 function rem_prefixes (obj) {
1563 return bus.translate_keys(bus.clone(obj), rem_prefix) }
1564
1565 bus(prefix).to_save = function (obj, t) {
1566 bus.save.fire(obj)
1567 var x = {save: obj}
1568 if (t.version) x.version = t.version
1569 if (t.parents) x.parents = t.parents
1570 if (t.patch) x.patch = t.patch
1571 if (t.patch) x.save = rem_prefix(x.save.key)
1572 send(x)
1573 }
1574 bus(prefix).to_fetch = function (key) { send({fetch: key}),
1575 client_fetched_keys.add(key) }
1576 bus(prefix).to_forget = function (key) { send({forget: key}),
1577 client_fetched_keys.delete(key) }
1578 bus(prefix).to_delete = function (key) { send({'delete': key}) }
1579
1580 function connect () {
1581 nlog('[ ] trying to open ' + url)
1582 sock = bus.make_websocket(url)
1583 sock.onopen = function() {
1584 nlog('[*] opened ' + url)
1585
1586 // Update state
1587 var peers = bus.fetch('peers')
1588 peers[url] = peers[url] || {}
1589 peers[url].connected = true
1590 save(peers)
1591
1592 // Login
1593 var creds = client_creds || (bus.client_creds && bus.client_creds(url))
1594 if (creds) {
1595 var i = []
1596 function intro (o) {i.push(JSON.stringify({save: o}))}
1597 if (creds.clientid)
1598 intro({key: 'current_user', client: creds.clientid})
1599 if (creds.name && creds.pass)
1600 intro({key: 'current_user', login_as: {name: creds.name, pass: creds.pass}})
1601 // Todo: make this kinda thing work:
1602 if (creds.private_key && creds.public_key) {
1603 // Send public_key... start waiting for a
1604 // challenge... look up server's public key, verify
1605 // signature from server's challenge, then respond to
1606 // challenge.
1607
1608 // This will be used for mailbus
1609 }
1610 outbox = i.concat(outbox); flush_outbox()
1611 }
1612
1613 // Reconnect
1614 if (attempts > 0) {
1615 // Then we need to refetch everything, cause it
1616 // might have changed
1617 var keys = client_fetched_keys.values()
1618 for (var i=0; i<keys.length; i++)
1619 send({fetch: keys[i]})
1620 }
1621
1622 attempts = 0
1623 //heartbeat = setInterval(function () {send({ping:true})}, 5000)
1624 }
1625 sock.onclose = function() {
1626 if (done) {
1627 nlog('[*] closed ' + url + '. Goodbye!')
1628 return
1629 }
1630 nlog('[*] closed ' + url)
1631 heartbeat && clearInterval(heartbeat); heartbeat = null
1632 setTimeout(connect, attempts++ < 3 ? 1500 : 5000)
1633
1634 // Update state
1635 var peers = bus.fetch('peers')
1636 peers[url] = peers[url] || {}
1637 peers[url].connected = false
1638 save(peers)
1639
1640 // Remove all fetches and forgets from queue
1641 var new_outbox = []
1642 var bad = {'fetch':1, 'forget':1}
1643 for (var i=0; i<outbox.length; i++)
1644 if (!bad[JSON.parse(outbox[i]).method])
1645 new_outbox.push(outbox[i])
1646 outbox = new_outbox
1647 }
1648
1649 sock.onmessage = function(event) {
1650 // Todo: Perhaps optimize processing of many messages
1651 // in batch by putting new messages into a queue, and
1652 // waiting a little bit for more messages to show up
1653 // before we try to re-render. That way we don't
1654 // re-render 100 times for a function that depends on
1655 // 100 items from server while they come in. This
1656 // probably won't make things render any sooner, but
1657 // will probably save energy.
1658
1659 //console.log('[.] message')
1660 try {
1661 var message = JSON.parse(event.data)
1662 var method = message_method(message)
1663
1664 // We only take saves from the server for now
1665 if (method !== 'save' && method !== 'pong') throw 'barf'
1666 bus.log('net client received', message)
1667 var t = {version: message.version,
1668 parents: message.parents,
1669 patch: message.patch}
1670 if (t.patch)
1671 msg.save = apply_patch(bus.cache[msg.save] || {key: msg.save},
1672 message.patch[0])
1673 if (!(t.version||t.parents||t.patch))
1674 t = undefined
1675 bus.save.fire(add_prefixes(message.save), t)
1676 } catch (err) {
1677 console.error('Received bad network message from '
1678 +url+': ', event.data, err)
1679 return
1680 }
1681 }
1682
1683 }
1684 connect()
1685
1686 var done = false
1687
1688 // Note: this return value is probably not necessary anymore.
1689 return {send: send, sock: sock, close: function () {done = true; sock.close()}}
1690 }
1691
1692 function net_automount () {
1693 var bus = this
1694 var old_route = bus.route
1695 var connections = {}
1696 bus.route = function (key, method, arg, opts) {
1697 var d = get_domain(key)
1698 if (d && !connections[d]) {
1699 bus.net_mount(d + '/*', d)
1700 connections[d] = true
1701 }
1702
1703 return old_route(key, method, arg, opts)
1704 }
1705 }
1706
1707
1708 // ******************
1709 // Key translation
1710 function translate_keys (obj, f) {
1711 // Recurse through each element in arrays
1712 if (Array.isArray(obj))
1713 for (var i=0; i < obj.length; i++)
1714 translate_keys(obj[i], f)
1715
1716 // Recurse through each property on objects
1717 else if (typeof obj === 'object')
1718 for (var k in obj) {
1719 if (k === 'key' || /.*_key$/.test(k))
1720 if (typeof obj[k] == 'string')
1721 obj[k] = f(obj[k])
1722 else if (Array.isArray(obj[k]))
1723 for (var i=0; i < obj[k].length; i++) {
1724 if (typeof obj[k][i] === 'string')
1725 obj[k][i] = f(obj[k][i])
1726 }
1727 translate_keys(obj[k], f)
1728 }
1729 return obj
1730 }
1731 function encode_field(k) {
1732 return k.replace(/(_(keys?|time)?$|^key$)/, '$1_')
1733 }
1734 function decode_field (k) {
1735 return k.replace(/(_$)/, '')
1736 }
1737
1738
1739 // ******************
1740 // JSON encoding
1741 // - Does both escape/unescape keys, and wraps/unwraps pointers
1742 // - Will use in both network communication and file_store
1743 function json_encode (highlevel_obj) {
1744 // Encodes fields, and converts {_key: 's'} to pointer to {key: 's'...}
1745 }
1746 function json_decode (lowlevel_obj) {
1747 // Decodes fields, and converts pointer to {key: 's'...} to {_key: 's'}
1748 }
1749
1750
1751 function key_id(string) { return string.match(/\/?[^\/]+\/(\d+)/)[1] }
1752 function key_name(string) { return string.match(/\/?([^\/]+).*/)[1] }
1753
1754 // ******************
1755 // Applying Patches, aka Diffs
1756 function apply_patch (obj, patch) {
1757 obj = bus.clone(obj)
1758 // Descend down a bunch of objects until we get to the final object
1759 // The final object can be a slice
1760 // Set the value in the final object
1761
1762 var x = patch.match(/(.*) = (.*)/),
1763 path = x[1],
1764 new_stuff = JSON.parse(x[2])
1765
1766 var path_segment = /^(\.([^\.\[]+))|(\[((-?\d+):)?(-?\d+)\])/
1767 var curr_obj = obj,
1768 last_obj = null
1769 function de_neg (x) {
1770 return x[0] === '-'
1771 ? curr_obj.length - parseInt(x.substr(1))
1772 : parseInt(x)
1773 }
1774
1775 while (true) {
1776 var match = path_segment.exec(path),
1777 subpath = match[0],
1778 field = match[2],
1779 slice_start = match[5],
1780 slice_end = match[6]
1781
1782 slice_start = slice_start && de_neg(slice_start)
1783 slice_end = slice_end && de_neg(slice_end)
1784
1785 // console.log('Descending', {curr_obj, path, subpath, field, slice_start, slice_end, last_obj})
1786
1787 // If it's the final item, set it
1788 if (path.length == subpath.length) {
1789 if (field) // Object
1790 curr_obj[field] = new_stuff
1791 else if (typeof curr_obj == 'string') { // String
1792 console.assert(typeof new_stuff == 'string')
1793 if (!slice_start) {slice_start = slice_end; slice_end = slice_end+1}
1794 if (last_obj) {
1795 var s = last_obj[last_field]
1796 last_obj[last_field] = (s.slice(0, slice_start)
1797 + new_stuff
1798 + s.slice(slice_end))
1799 } else
1800 return obj.slice(0, slice_start) + new_stuff + obj.slice(slice_end)
1801 } else // Array
1802 if (slice_start) // - Array splice
1803 [].splice.apply(curr_obj, [slice_start, slice_end-slice_start]
1804 .concat(new_stuff))
1805 else { // - Array set
1806 console.assert(slice_end >= 0, 'Index '+subpath+' is too small')
1807 console.assert(slice_end <= curr_obj.length - 1,
1808 'Index '+subpath+' is too big')
1809 curr_obj[slice_end] = new_stuff
1810 }
1811
1812 return obj
1813 }
1814
1815 // Otherwise, descend down the path
1816 console.assert(!slice_start, 'No splices allowed in middle of path')
1817 last_obj = curr_obj
1818 last_field = field
1819 curr_obj = curr_obj[field || slice_end]
1820 path = path.substr(subpath.length)
1821 }
1822 }
1823
1824 // ******************
1825 // Utility funcs
1826 function parse (s) {try {return JSON.parse(s)} catch (e) {return {}}}
1827 function One_To_Many() {
1828 var hash = this.hash = {}
1829 var counts = {}
1830 this.get = function (k) { return Object.keys(hash[k] || {}) }
1831 this.add = function (k, v) {
1832 if (hash[k] === undefined) hash[k] = {}
1833 if (counts[k] === undefined) counts[k] = 0
1834 if (!hash[k][v]) counts[k]++
1835 hash[k][v] = true
1836 }
1837 this.delete = function (k, v) { delete hash[k][v]; counts[k]-- }
1838 this.delete_all = function (k) { delete hash[k]; delete counts[k] }
1839 this.has = function (k, v) { return hash[k] && hash[k][v] }
1840 this.has_any = function (k) { return counts[k] }
1841 this.del = this.delete // for compatibility; remove this soon
1842 }
1843 function Set () {
1844 var hash = {}
1845 this.add = function (a) { hash[a] = true }
1846 this.has = function (a) { return a in hash }
1847 this.values = function () { return Object.keys(hash) }
1848 this.delete = function (a) { delete hash[a] }
1849 this.clear = function () { hash = {} }
1850 this.del = this.delete // for compatibility; remove this soon
1851 this.all = this.values // for compatibility; remove this soon
1852 }
1853 //Set = window.Set || Set
1854 // function clone(obj) {
1855 // if (obj == null) return obj
1856 // var copy = obj.constructor()
1857 // for (var attr in obj)
1858 // if (obj.hasOwnProperty(attr)) copy[attr] = obj[attr]
1859 // return copy
1860 // }
1861 function clone(item) {
1862 if (!item // null, undefined values check
1863 || item instanceof Number
1864 || item instanceof String
1865 || item instanceof Boolean)
1866 return item
1867
1868 if (Array.isArray(item)) {
1869 item = item.slice()
1870 for (var i=0; i<item.length; i++)
1871 item[i] = clone(item[i])
1872 return item
1873 }
1874
1875 if (typeof item == "object") {
1876 // Is it DOM
1877 if (item.nodeType && typeof item.cloneNode == "function")
1878 return item.cloneNode(true)
1879
1880 if (item instanceof Date)
1881 return new Date(item)
1882 else {
1883 var result = {}
1884 for (var i in item) result[i] = clone(item[i])
1885 return result
1886 }
1887 }
1888
1889 // Give up on everything else...
1890 return item
1891 }
1892
1893 function extend(obj, with_obj) {
1894 if (with_obj === undefined) return obj
1895 for (var attr in with_obj)
1896 if (obj.hasOwnProperty(attr)) obj[attr] = with_obj[attr]
1897 return obj
1898 }
1899
1900 function deep_map (object, func) {
1901 object = func(object)
1902
1903 // Recurse through each element in arrays
1904 if (Array.isArray(object))
1905 for (var i=0; i < object.length; i++)
1906 object[i] = deep_map(object[i], func)
1907
1908 // Recurse through each property on objects
1909 else if (typeof(object) === 'object')
1910 for (var k in object)
1911 object[k] = deep_map(object[k], func)
1912
1913 return object
1914 }
1915 function deep_equals (a, b) {
1916 // Equal Primitives?
1917 if (a === b
1918 // But because NaN === NaN returns false:
1919 || (isNaN(a) && isNaN(b)
1920 // And because isNaN(undefined) returns true:
1921 && typeof a === 'number' && typeof b === 'number'))
1922 return true
1923
1924 // Equal Arrays?
1925 var a_array = Array.isArray(a), b_array = Array.isArray(b)
1926 if (a_array !== b_array) return false
1927 if (a_array) {
1928 if (a.length !== b.length) return false
1929 for (var i=0; i < a.length; i++)
1930 if (!deep_equals (a[i], b[i]))
1931 return false
1932 return true
1933 }
1934
1935 // Equal Objects?
1936 var a_obj = a && typeof a === 'object', // Note: typeof null === 'object'
1937 b_obj = b && typeof b === 'object'
1938 if (a_obj !== b_obj) return false
1939 if (a_obj) {
1940 var a_length = 0, b_length = 0
1941 for (var k in a) {
1942 a_length++
1943 if (!deep_equals(a[k], b[k]))
1944 return false
1945 }
1946 for (var k in b) b_length++
1947 if (a_length !== b_length)
1948 return false
1949 return true
1950 }
1951
1952 // Then Not Equal.
1953 return false
1954 }
1955 function sorta_diff(a, b) {
1956 // Equal Primitives?
1957 if (a === b
1958 // But because NaN === NaN returns false:
1959 || (isNaN(a) && isNaN(b)
1960 // And because isNaN(undefined) returns true:
1961 && typeof a === 'number' && typeof b === 'number'))
1962 return null
1963
1964 // Equal Arrays?
1965 var a_array = Array.isArray(a), b_array = Array.isArray(b)
1966 if (a_array !== b_array) return ' = ' + JSON.stringify(b)
1967 if (a_array) {
1968 if (a.length === b.length-1
1969 && !deep_equals(a[a.length], b[b.length])) {
1970 return '.push(' +JSON.stringify(b[b.length]) + ')'
1971 }
1972 for (var i=0; i < a.length; i++) {
1973 var tmp = sorta_diff (a[i], b[i])
1974 if (tmp)
1975 return '['+i+'] = '+tmp
1976 }
1977 return null
1978 }
1979
1980 // Equal Objects?
1981 var a_obj = a && typeof a === 'object', // Note: typeof null === 'object'
1982 b_obj = b && typeof b === 'object'
1983 if (a_obj !== b_obj) return ' = ' + JSON.stringify(b)
1984 if (a_obj) {
1985 for (var k in a) {
1986 var tmp = sorta_diff(a[k], b[k])
1987 if (tmp)
1988 return '.' + k + tmp
1989 }
1990 for (var k in b) {
1991 if (!a.hasOwnProperty(k))
1992 return '.' + k +' = '+JSON.stringify(b[k])
1993 }
1994 return null
1995 }
1996
1997 // Then Not Equal.
1998 return ' = ' + JSON.stringify(b)
1999 }
2000
2001 // This prune() function is a temporary workaround for dealing with nested
2002 // objects in save() handlers, until we change statebus' behavior. Right
2003 // now, it calls .to_save only on the top-level state. But if that state
2004 // validates, it calls fire() on *every* level of state. This means that
2005 // state changes can sneak inside. Prune() will take out any changes from
2006 // the nested levels of state in a new object -- replacing them with the
2007 // existing state from this bus.
2008 function prune (obj) {
2009 var bus = this
2010 obj = bus.clone(obj)
2011 function recurse (o) {
2012 // Recurse through each element in arrays
2013 if (Array.isArray(o))
2014 for (var i=0; i < o.length; i++)
2015 o[i] = recurse(o[i])
2016
2017 // Recurse through each property on objects
2018 else if (typeof(o) === 'object')
2019 if (o.key)
2020 return bus.fetch(o.key)
2021 else
2022 for (var k in o)
2023 o[k] = recurse(o[k])
2024
2025 return o
2026 }
2027
2028 for (var k in obj)
2029 obj[k] = recurse(obj[k])
2030 return obj
2031 }
2032
2033 function validate (obj, schema) {
2034 // XXX Warning:
2035 //
2036 // If the programmer plugs a variable in as validation schema type,
2037 // thinking it's ok cause he'll be seeking an exact match:
2038 //
2039 // var thing // manipulable via user input
2040 // bus.validate(obj, {a: thing})
2041 //
2042 // An attacker could set `thing' to 'string', 'number', or '*', and
2043 // suddenly get it to validate anything he wants.
2044 //
2045 // I *only* imagine this a tempting way to program if you are seeking
2046 // an exact match on schema. So we should consider removing this
2047 // feature, 3 lines below.
2048
2049 var optional = false
2050 if (schema === '*') return true
2051 if (obj === schema) return true // DANGEROUS API!!!
2052
2053 if (typeof obj === 'string') return schema === 'string'
2054 if (typeof obj === 'number') return schema === 'number'
2055 if (typeof obj === 'boolean') return schema === 'boolean'
2056 if ( obj === null) return schema === 'null'
2057 if ( obj === undefined) return schema === 'undefined'
2058
2059 if (Array.isArray(obj)) return schema === 'array'
2060
2061 if (typeof obj === 'object') {
2062 if (schema === 'object') return true
2063
2064 if (typeof schema === 'object') {
2065 for (var k in obj) {
2066 var sk
2067 if (schema.hasOwnProperty(k))
2068 sk = k
2069 else if (schema.hasOwnProperty('?'+k))
2070 sk = '?'+k
2071 else if (schema.hasOwnProperty('*'))
2072 sk = '*'
2073 else return false
2074
2075 if (!validate(obj[k], schema[sk]))
2076 return false
2077 }
2078 for (var k in schema)
2079 if (k[0] !== '?' && k !== '*')
2080 if (!(obj.hasOwnProperty(k)))
2081 return false
2082
2083 return true
2084 }
2085
2086 return false
2087 }
2088
2089 if (typeof obj == 'function')
2090 throw 'bus.validate() cannot validate functions'
2091 console.trace()
2092 throw "You hit a Statebus bug! Tell the developers!"
2093 }
2094
2095 function funk_key (funk) {
2096 if (!funk.statebus_id) {
2097 funk.statebus_id = Math.random().toString(36).substring(7)
2098 funks[funk.statebus_id] = funk
2099 }
2100 return funk.statebus_id
2101 }
2102 function funk_keyr (funk) {
2103 while (funk.proxies_for) funk = funk.proxies_for
2104 return funk_key(funk)
2105 }
2106 function funk_name (f, char_limit) {
2107 char_limit = char_limit || 30
2108
2109 // if (f.react)
2110 // var arg = JSON.stringify((f.args && f.args[0] && (f.args[0].key || f.args[0])) || '').substring(0.30)
2111 // else
2112 // var arg = ''
2113 var arg = f.react ? (f.args && f.args[0]) : ''
2114 arg = f.react ? (JSON.stringify(f.arg)||'').substring(0,30) : ''
2115 f = f.proxies_for || f
2116 var f_string = 'function ' + (f.name||'') + '(' + (arg||'') + ') {..}'
2117 // Or: f.toString().substr(0,char_limit) + '...'
2118
2119 if (!f.defined) return f_string
2120 if (f.defined.length > 1) return '**' + f_string + '**'
2121
2122 var def = f.defined[0]
2123 switch (def.as) {
2124 case 'handler':
2125 return def.bus+"('"+def.key+"')."+def.method+' = '+f_string
2126 case 'fetch callback':
2127 return 'fetch('+def.key+', '+f_string+')'
2128 case 'reactive':
2129 return "reactive('"+f_string+"')"
2130 default:
2131 return 'UNKNOWN Funky Definition!!!... ???'
2132 }
2133 }
2134
2135 function deps (key) {
2136 // First print out everything waiting for it to pub
2137 var result = 'Deps: ('+key+') fires into:'
2138 var pubbers = bindings(key, 'on_save')
2139 if (pubbers.length === 0) result += ' nothing'
2140 for (var i=0; i<pubbers.length; i++)
2141 result += '\n ' + funk_name(pubbers[i].func)
2142 return result
2143 }
2144
2145 function log () {
2146 if (bus.honk === true) indented_log.apply(null, arguments)
2147 }
2148 function indented_log () {
2149 if (nodejs) {
2150 var indent = ''
2151 for (var i=0; i<statelog_indent; i++) indent += ' '
2152 console.log(indent+require('util').format.apply(null,arguments).replace(/\n/g,'\n'+indent))
2153 } else
2154 console.log.apply(console, arguments)
2155 }
2156 function statelog (key, color, icon, message) {
2157 if (honking_at(key))
2158 indented_log(color + icon + ' ' + message + normal)
2159 }
2160 function honking_at (key) {
2161 return (bus.honk instanceof RegExp
2162 ? bus.honk.test(key)
2163 : bus.honk)
2164 }
2165 var bogus_keys = {constructor:1, hasOwnProperty:1, isPrototypeOf:1,
2166 propertyIsEnumerable:1, toLocaleString:1, toString:1, valueOf:1,
2167 __defineGetter__:1, __defineSetter__:1,
2168 __lookupGetter__:1, __lookupSetter__:1, __proto__:1}
2169 function bogus_check (key) {
2170 if (!(key in bogus_keys))
2171 return
2172
2173 var msg = "Sorry, statebus.js currently prohibits use of the key \""+key+"\", and in fact all of these keys: " + Object.keys(bogus_keys).join(', ') + ". This is because Javascript is kinda lame, and even empty objects like \"{}\" have the \""+key+"\" field defined on them. Try typing this in your Javascript console: \"({}).constructor\" -- it returns a function instead of undefined! We could work around it by meticulously replacing every \"obj[key]\" with \"obj.hasOwnProperty(key) && obj[key]\" in the statebus code, but that will make the source more difficult to understand. So please contact us if this use-case is important for you, and we'll consider doing it. We're hoping that, for now, our users don't need to use these keys."
2174 console.error(msg)
2175 throw 'Invalid key'
2176 }
2177
2178 // Make these private methods accessible
2179 var api = ['cache backup_cache fetch save forget del fire dirty fetch_once',
2180 'subspace bindings run_handler bind unbind reactive uncallback',
2181 'versions new_version',
2182 'make_proxy state sb',
2183 'funk_key funk_name funks key_id key_name id',
2184 'pending_fetches fetches_in fetches_out loading_keys loading once',
2185 'global_funk busses rerunnable_funks',
2186 'encode_field decode_field translate_keys apply_patch',
2187 'net_mount net_automount message_method',
2188 'parse Set One_To_Many clone extend deep_map deep_equals prune validate sorta_diff log deps'
2189 ].join(' ').split(' ')
2190 for (var i=0; i<api.length; i++)
2191 bus[api[i]] = eval(api[i])
2192
2193 bus.delete = bus.del
2194 bus.executing_funk = function () {return executing_funk}
2195
2196 // Export globals
2197 if (nodejs || !(document.querySelector('script[src*="client"][src$=".js"]')
2198 .getAttribute('globals') == 'false')) {
2199 var globals = ['loading', 'clone', 'forget']
2200 var client_globals = ['fetch', 'save', 'del', 'state']
2201 if (!nodejs && Object.keys(busses).length == 0)
2202 globals = globals.concat(client_globals)
2203
2204 this.og_fetch = this.fetch // Cause fetch() is actually a browser API now
2205 for (var i=0; i<globals.length; i++)
2206 this[globals[i]] = eval(globals[i])
2207 }
2208
2209 busses[bus.id] = bus
2210
2211 if (nodejs)
2212 require('./server').import_server(bus, options)
2213
2214 bus.render_when_loading = true
2215 return bus
2216}
2217
2218if (nodejs) require('./server').import_module(make_bus)
2219
2220return make_bus
2221}))