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