UNPKG

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