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