UNPKG

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