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