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 |
|
23 | if (callback) {
|
24 | (callback.defined = callback.defined || []
|
25 | ).push({as:'fetch callback', key:key});
|
26 | callback.has_seen = callback.has_seen || function (bus, key, version) {
|
27 | callback.seen_keys = callback.seen_keys || {}
|
28 | var bus_key = JSON.stringify([bus.id, key])
|
29 | var seen_versions =
|
30 | callback.seen_keys[bus_key] = callback.seen_keys[bus_key] || []
|
31 | seen_versions.push(version)
|
32 | if (seen_versions.length > 50) seen_versions.shift()
|
33 | }
|
34 | }
|
35 |
|
36 |
|
37 |
|
38 | fetches_in.add(key, funk_key(funk))
|
39 | if (to_be_forgotten[key]) {
|
40 | clearTimeout(to_be_forgotten[key])
|
41 | delete to_be_forgotten[key]
|
42 | }
|
43 |
|
44 | bind(key, 'on_save', funk)
|
45 |
|
46 |
|
47 |
|
48 |
|
49 |
|
50 |
|
51 |
|
52 | var to_fetchers = 0
|
53 | if (!fetches_out[key])
|
54 | to_fetchers = bus.route(key, 'to_fetch', key)
|
55 |
|
56 |
|
57 |
|
58 |
|
59 |
|
60 |
|
61 |
|
62 |
|
63 |
|
64 | if (called_from_reactive_funk) {
|
65 | funk.has_seen(bus, key, versions[key])
|
66 | backup_cache[key] = backup_cache[key] || {key: key}
|
67 | return cache[key] = cache[key] || {key: key}
|
68 | }
|
69 |
|
70 |
|
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 | bus.route(obj.key, 'on_set_sync', obj, t)
|
136 | }
|
137 | }
|
138 | finally {
|
139 | statelog_indent--
|
140 | currently_saving = was_saving
|
141 | }
|
142 |
|
143 |
|
144 |
|
145 |
|
146 | }
|
147 | save.fire = fire
|
148 | function fire (obj, t) {
|
149 | t = t || {}
|
150 |
|
151 | t.version = t.version || new_version()
|
152 |
|
153 |
|
154 | if (obj.key && honking_at(obj.key)) {
|
155 |
|
156 |
|
157 |
|
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 |
|
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 |
|
191 |
|
192 |
|
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 |
|
201 |
|
202 |
|
203 | update_cache(obj, backup_cache)
|
204 |
|
205 |
|
206 |
|
207 | for (var i=0; i < modified_keys.length; i++) {
|
208 | var key = modified_keys[i]
|
209 | var parents = [versions[key]]
|
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 |
|
229 | function bus (arg) {
|
230 |
|
231 | if (typeof arg === 'function') {
|
232 | var f = reactive(arg)
|
233 | f()
|
234 | return f
|
235 | }
|
236 |
|
237 |
|
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 |
|
244 |
|
245 |
|
246 |
|
247 |
|
248 |
|
249 |
|
250 |
|
251 |
|
252 |
|
253 |
|
254 | delete busses[bus.id]
|
255 | }
|
256 |
|
257 |
|
258 | var cache = {}
|
259 | var backup_cache = {}
|
260 | var versions = {}
|
261 |
|
262 |
|
263 |
|
264 | function update_cache (object, cache) {
|
265 | var modified_keys = new Set()
|
266 | function update_object (obj) {
|
267 |
|
268 |
|
269 |
|
270 |
|
271 |
|
272 |
|
273 |
|
274 |
|
275 |
|
276 |
|
277 |
|
278 |
|
279 |
|
280 |
|
281 |
|
282 |
|
283 |
|
284 |
|
285 |
|
286 |
|
287 |
|
288 |
|
289 |
|
290 |
|
291 | if (Array.isArray(obj))
|
292 | obj = obj.slice()
|
293 |
|
294 |
|
295 | else if (typeof obj === 'object'
|
296 | && obj
|
297 | && !(obj.key
|
298 | && cache[obj.key] === obj)) {
|
299 | var tmp = {}; for (var k in obj) tmp[k] = obj[k]; obj = tmp
|
300 | }
|
301 |
|
302 |
|
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 |
|
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 |
|
320 | cache[obj.key] = obj
|
321 |
|
322 | else if (obj !== cache[obj.key]) {
|
323 |
|
324 |
|
325 |
|
326 | for (var k in obj)
|
327 | if (cache[obj.key][k] !== obj[k])
|
328 | cache[obj.key][k] = obj[k]
|
329 |
|
330 |
|
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 |
|
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 |
|
367 | save_handler = save_handler || executing_funk
|
368 | var fkey = funk_key(save_handler)
|
369 |
|
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 | console.trace()
|
375 | return
|
376 |
|
377 | }
|
378 |
|
379 | fetches_in.delete(key, fkey)
|
380 | unbind(key, 'on_save', save_handler)
|
381 |
|
382 |
|
383 |
|
384 |
|
385 | if (!fetches_in.has_any(key)) {
|
386 | clearTimeout(to_be_forgotten[key])
|
387 | to_be_forgotten[key] = setTimeout(function () {
|
388 |
|
389 | bus.route(key, 'to_forget', key)
|
390 |
|
391 |
|
392 |
|
393 | delete fetches_out[key]
|
394 | delete to_be_forgotten[key]
|
395 | }, 200)
|
396 | }
|
397 | }
|
398 | function del (key) {
|
399 | key = key.key || key
|
400 | bogus_check(key)
|
401 |
|
402 | if ((executing_funk !== global_funk) && executing_funk.loading()) {
|
403 | abort_changes([key])
|
404 | return
|
405 | }
|
406 |
|
407 | statelog(key, yellow, 'v', 'Deleting ' + key)
|
408 |
|
409 | var handlers_called = bus.route(key, 'to_delete', key)
|
410 | if (handlers_called === 0)
|
411 |
|
412 | delete cache[key]
|
413 |
|
414 |
|
415 |
|
416 |
|
417 |
|
418 |
|
419 |
|
420 |
|
421 |
|
422 |
|
423 |
|
424 |
|
425 |
|
426 |
|
427 |
|
428 |
|
429 |
|
430 |
|
431 |
|
432 |
|
433 |
|
434 | }
|
435 |
|
436 | var changed_keys = new Set()
|
437 | var dirty_fetchers = new Set()
|
438 | function dirty (key, t) {
|
439 | statelog(key, brown, '*', bus + ".dirty('"+key+"')")
|
440 | bogus_check(key)
|
441 |
|
442 |
|
443 | var found = false
|
444 | if (fetches_out.hasOwnProperty(key))
|
445 | for (var i=0; i<fetches_out[key].length; i++) {
|
446 | dirty_fetchers.add(funk_key(fetches_out[key][i]))
|
447 | found = true
|
448 | }
|
449 | clean_timer = clean_timer || setTimeout(clean)
|
450 |
|
451 |
|
452 | if (!found && cache.hasOwnProperty(key)) mark_changed(key, t)
|
453 | }
|
454 |
|
455 | function mark_changed (key, t) {
|
456 |
|
457 | log('Marking changed', bus.toString(), key)
|
458 | changed_keys.add(key)
|
459 | clean_timer = clean_timer || setTimeout(clean)
|
460 | }
|
461 |
|
462 | function clean () {
|
463 |
|
464 | var dirty_funks = new Set()
|
465 | for (var b in busses) {
|
466 | var fs = busses[b].rerunnable_funks()
|
467 | for (var i=0; i<fs.length; i++)
|
468 | dirty_funks.add(fs[i])
|
469 | }
|
470 | clean_timer = null
|
471 |
|
472 |
|
473 | dirty_funks = dirty_funks.values()
|
474 | log('Cleaning up', dirty_funks.length, 'funks')
|
475 | for (var i=0; i<dirty_funks.length; i++) {
|
476 |
|
477 | var p = funks[dirty_funks[i]].proxies_for
|
478 | if (p && p.priority) {
|
479 | log('Clean-early:', funk_name(funks[dirty_funks[i]]))
|
480 | funks[dirty_funks[i]].react()
|
481 | dirty_funks.splice(i,1)
|
482 | i--
|
483 | }
|
484 | }
|
485 |
|
486 |
|
487 | for (var i=0; i<dirty_funks.length; i++) {
|
488 | log('Clean:', funk_name(funks[dirty_funks[i]]))
|
489 | if (bus.render_when_loading || !funks[dirty_funks[i]].loading())
|
490 | funks[dirty_funks[i]].react()
|
491 | }
|
492 |
|
493 | }
|
494 |
|
495 | function rerunnable_funks () {
|
496 | var result = []
|
497 | var keys = changed_keys.values()
|
498 | var fetchers = dirty_fetchers.values()
|
499 |
|
500 |
|
501 | for (var i=0; i<keys.length; i++) {
|
502 |
|
503 |
|
504 |
|
505 |
|
506 | var fs = bindings(keys[i], 'on_save')
|
507 | for (var j=0; j<fs.length; j++) {
|
508 | var f = fs[j].func
|
509 | if (f.react) {
|
510 |
|
511 | var v = f.fetched_keys[JSON.stringify([this.id, keys[i]])]
|
512 |
|
513 | if (v && v.indexOf(versions[keys[i]]) !== -1) {
|
514 | log('skipping', funk_name(f), 'already at version', versions[keys[i]], 'proof:', v)
|
515 | continue
|
516 | }
|
517 | } else {
|
518 |
|
519 | f.seen_keys = f.seen_keys || {}
|
520 | var v = f.seen_keys[JSON.stringify([this.id, keys[i]])]
|
521 | if (v && v.indexOf(versions[keys[i]]) !== -1) {
|
522 |
|
523 |
|
524 |
|
525 |
|
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++)
|
536 | result.push(fetchers[i])
|
537 |
|
538 | changed_keys.clear()
|
539 | dirty_fetchers.clear()
|
540 |
|
541 |
|
542 |
|
543 | return result
|
544 | }
|
545 |
|
546 |
|
547 |
|
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 |
|
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 |
|
608 | var handlers = new One_To_Many()
|
609 | var wildcard_handlers = []
|
610 |
|
611 |
|
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 |
|
623 |
|
624 | }
|
625 | function unbind (key, method, funk, allow_wildcards) {
|
626 | bogus_check(key)
|
627 | if (allow_wildcards && key[key.length-1] === '*')
|
628 |
|
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)
|
636 | i--
|
637 | }
|
638 | }
|
639 | else
|
640 |
|
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 |
|
652 | var result = []
|
653 | var seen = {}
|
654 |
|
655 |
|
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 |
|
667 | for (var i=0; i < wildcard_handlers.length; i++) {
|
668 | handler = wildcard_handlers[i]
|
669 |
|
670 | var prefix = handler.prefix.slice(0, -1)
|
671 | if (prefix === key.substr(0,prefix.length)
|
672 | && method === handler.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 |
|
690 |
|
691 |
|
692 |
|
693 |
|
694 | var funk = funck.react && funck,
|
695 | func = !funk && funck
|
696 |
|
697 | console.assert(funk || func)
|
698 |
|
699 | if (false && !funck.global_funk) {
|
700 |
|
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 |
|
710 |
|
711 |
|
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 |
|
720 |
|
721 |
|
722 |
|
723 |
|
724 |
|
725 |
|
726 |
|
727 |
|
728 |
|
729 |
|
730 |
|
731 |
|
732 |
|
733 |
|
734 |
|
735 |
|
736 |
|
737 |
|
738 | }
|
739 |
|
740 |
|
741 |
|
742 |
|
743 |
|
744 |
|
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 |
|
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 |
|
773 |
|
774 |
|
775 |
|
776 |
|
777 |
|
778 |
|
779 |
|
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 {
|
788 | o.key = key
|
789 | bus.save.fire(o, t)
|
790 |
|
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 |
|
799 | var args = []
|
800 | args[0] = arg
|
801 | args[1] = t
|
802 |
|
803 |
|
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 |
|
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 |
|
824 | }
|
825 |
|
826 |
|
827 |
|
828 | var result = func.apply(null, args)
|
829 |
|
830 |
|
831 |
|
832 |
|
833 |
|
834 |
|
835 |
|
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 |
|
843 | if (method === 'to_fetch' && result instanceof Object
|
844 | && !f.loading()
|
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 |
|
854 |
|
855 |
|
856 | if (method !== 'to_fetch' && !f.loading())
|
857 | f.forget()
|
858 | })
|
859 | f.proxies_for = func
|
860 | f.arg = arg
|
861 |
|
862 |
|
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 |
|
872 |
|
873 |
|
874 |
|
875 |
|
876 | fetches_out[key] = fetches_out[key] || []
|
877 | fetches_out[key].push(f)
|
878 | pending_fetches[key] = f
|
879 | }
|
880 |
|
881 | if (just_make_it)
|
882 | return f
|
883 |
|
884 | return f()
|
885 | }
|
886 |
|
887 |
|
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 |
|
893 |
|
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 |
|
898 |
|
899 |
|
900 |
|
901 | return handlers.length
|
902 | }
|
903 |
|
904 |
|
905 |
|
906 |
|
907 |
|
908 |
|
909 |
|
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 |
|
920 |
|
921 |
|
922 |
|
923 |
|
924 |
|
925 |
|
926 |
|
927 |
|
928 |
|
929 |
|
930 |
|
931 |
|
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 |
|
940 | funk.forget()
|
941 |
|
942 |
|
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 |
|
953 | if (funk.loading()) return null
|
954 | else {
|
955 |
|
956 | if (nodejs) {
|
957 | console.error(e.stack)
|
958 | process.exit()
|
959 | } else {
|
960 |
|
961 |
|
962 | var result = func.apply(funk.this, funk.args)
|
963 |
|
964 |
|
965 |
|
966 |
|
967 |
|
968 |
|
969 |
|
970 |
|
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
|
982 | funk.called_directly = true
|
983 | funk.fetched_keys = {}
|
984 |
|
985 | funk.abortable_keys = []
|
986 | funk.has_seen = function (bus, key, version) {
|
987 |
|
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 (seen_versions.length > 50) seen_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 |
|
1006 |
|
1007 |
|
1008 |
|
1009 |
|
1010 |
|
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)
|
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
|
1027 | && bus.pending_fetches[key])
|
1028 | return true
|
1029 | }
|
1030 | return false
|
1031 | }
|
1032 |
|
1033 |
|
1034 | funk.is_loading = funk.loading
|
1035 |
|
1036 | return funk
|
1037 | }
|
1038 |
|
1039 | function loading_keys (keys) {
|
1040 |
|
1041 |
|
1042 | for (var i=0; i<keys.length; i++)
|
1043 | if (pending_fetches[keys[i]]) return true
|
1044 | return false
|
1045 | }
|
1046 |
|
1047 |
|
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 |
|
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 |
|
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 |
|
1115 | args[options.callback_at || args.length] = cb
|
1116 |
|
1117 |
|
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 |
|
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 |
|
1153 |
|
1154 |
|
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 |
|
1212 |
|
1213 |
|
1214 |
|
1215 |
|
1216 |
|
1217 |
|
1218 | deleteProperty: function del (o, k) {
|
1219 | bus.delete(encode_field(k))
|
1220 | }
|
1221 | })
|
1222 | })()
|
1223 |
|
1224 |
|
1225 |
|
1226 |
|
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 | var strict_mode = (function () {return !this})()
|
1262 | function pget (base, o, k) {
|
1263 |
|
1264 |
|
1265 | if (base) {
|
1266 | o = o[k]
|
1267 |
|
1268 |
|
1269 | if (typeof o == 'object' && 'key' in o) {
|
1270 | base = o
|
1271 | bus.fetch(o.key)
|
1272 | }
|
1273 | } else {
|
1274 |
|
1275 | o = bus.fetch(k)
|
1276 |
|
1277 | base = o
|
1278 | if (bus.validate(o, {key: '*', '?_': '*'})) {
|
1279 |
|
1280 | o = o._
|
1281 | }
|
1282 | if (typeof o === 'object' && o.key)
|
1283 | base = o
|
1284 | }
|
1285 |
|
1286 |
|
1287 | if (typeof o == 'object' && 'key' in o && '_' in o) {
|
1288 |
|
1289 |
|
1290 |
|
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 |
|
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 |
|
1309 | else if (typeof x === 'object') {
|
1310 |
|
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 |
|
1318 | else if (typeof x === 'function' && x[symbols.is_proxy]) {
|
1319 | return x()
|
1320 | }
|
1321 |
|
1322 |
|
1323 | return x
|
1324 | }
|
1325 | function proxy_decode_json (json) {
|
1326 |
|
1327 |
|
1328 |
|
1329 |
|
1330 |
|
1331 | if (bus.validate(json, {key: '*', '_': '*'}))
|
1332 | return proxy_decode_json(json._)
|
1333 |
|
1334 |
|
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 |
|
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 |
|
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 |
|
1366 |
|
1367 |
|
1368 | if (base && base._ && Object.keys(base).length === 2
|
1369 | && base._ === o)
|
1370 | return base
|
1371 |
|
1372 |
|
1373 | return o
|
1374 | }
|
1375 |
|
1376 |
|
1377 |
|
1378 | var dummy_obj = function () {}
|
1379 | return new Proxy(dummy_obj, {
|
1380 | get: function (dummy_obj, k) {
|
1381 |
|
1382 |
|
1383 |
|
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 |
|
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 |
|
1408 | return make_proxy(base2, o2)
|
1409 | },
|
1410 | set: function (dummy_obj, k, v) {
|
1411 |
|
1412 |
|
1413 | if (base) {
|
1414 | var encoded_v = o[encode_field(k)] = proxy_encode_val(v)
|
1415 |
|
1416 |
|
1417 |
|
1418 |
|
1419 |
|
1420 |
|
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 |
|
1429 | for (var k2 in base._)
|
1430 | base[k2] = base._[k2]
|
1431 | delete base._
|
1432 | }
|
1433 |
|
1434 | bus.save(base)
|
1435 | }
|
1436 |
|
1437 |
|
1438 | else {
|
1439 | var encoded_v = proxy_encode_val(v)
|
1440 |
|
1441 |
|
1442 |
|
1443 | if (v === undefined)
|
1444 | encoded_v = {key: k}
|
1445 |
|
1446 |
|
1447 | else if (
|
1448 | (typeof v === 'object' && v[symbols.is_proxy])
|
1449 |
|
1450 | || (typeof v === 'object' && Object.keys(v).length === 0)
|
1451 |
|
1452 | || typeof v !== 'object' || v === null
|
1453 |
|
1454 | || Array.isArray(v))
|
1455 | encoded_v = {_: encoded_v}
|
1456 | encoded_v.key = k
|
1457 |
|
1458 |
|
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 |
|
1467 |
|
1468 |
|
1469 |
|
1470 |
|
1471 |
|
1472 |
|
1473 |
|
1474 | return o.hasOwnProperty(encode_field(k))
|
1475 | },
|
1476 | deleteProperty: function del (O, k) {
|
1477 | if (base) {
|
1478 |
|
1479 | delete o[encode_field(k)]
|
1480 | if (Object.keys(o).length === 1 && o.key)
|
1481 | o._ = {}
|
1482 | bus.save(base)
|
1483 | }
|
1484 | else
|
1485 | bus.delete(encode_field(k))
|
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 |
|
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 |
|
1506 | function get_domain (key) {
|
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 |
|
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 |
|
1580 | var peers = bus.fetch('peers')
|
1581 | peers[url] = peers[url] || {}
|
1582 | peers[url].connected = true
|
1583 | save(peers)
|
1584 |
|
1585 |
|
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 |
|
1595 | if (creds.private_key && creds.public_key) {
|
1596 |
|
1597 |
|
1598 |
|
1599 |
|
1600 |
|
1601 |
|
1602 | }
|
1603 | outbox = i.concat(outbox); flush_outbox()
|
1604 | }
|
1605 |
|
1606 |
|
1607 | if (attempts > 0) {
|
1608 |
|
1609 |
|
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 |
|
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 | function clientjs_option (option_name) {
|
2191 | // This function is duplicated in client.js. Be sure to clone all
|
2192 | // edits there.
|
2193 | var script_elem = (
|
2194 | document.querySelector('script[src*="/client"][src$=".js"]') ||
|
2195 | document.querySelector('script[src^="client"][src$=".js"]'))
|
2196 | return script_elem && script_elem.getAttribute(option_name)
|
2197 | }
|
2198 | if (nodejs || clientjs_option('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 | }))
|