UNPKG

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