├── .gitignore
├── LICENSE
├── README.md
├── bullet.js
├── examples
├── index.html
├── package-lock.json
└── remove.html
└── utilities
├── remove.js
├── rpc.js
└── ttl.js
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | .DS_Store
3 | backup/
4 | node_modules/
5 | playground/
6 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Levi Roberts
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # bullet
2 | Wrapper around [GunDB](https://github.com/amark/gun) to provide alternative syntax, promise support, utility modules, and easy Gun adapter events.
3 |
4 | # Rewriting Gun docs: #
5 |
6 | ## Original: ##
7 |
8 | var gun = Gun();
9 |
10 | gun.get('mark').put({
11 | name: "Mark",
12 | email: "mark@gunDB.io",
13 | });
14 |
15 | gun.get('mark').on(function(data, key){
16 | console.log("update:", data);
17 | });
18 |
19 | ## With Bullet: ##
20 |
21 | var bullet = new Bullet()
22 |
23 | bullet.mark = {
24 | name: "Mark",
25 | email: "mark@gunDB.io",
26 | }
27 |
28 | bullet.mark.on(data => {
29 | console.log("update:", data);
30 | })
31 |
32 | ## Second Example: ##
33 |
34 | var cat = {name: "Fluffy", species: "kitty"};
35 | var mark = {boss: cat};
36 | cat.slave = mark;
37 |
38 | // partial updates merge with existing data!
39 | gun.get('mark').put(mark);
40 |
41 | // access the data as if it is a document.
42 | gun.get('mark').get('boss').get('name').once(function(data, key){
43 | // `val` grabs the data once, no subscriptions.
44 | console.log("Mark's boss is", data);
45 | });
46 |
47 | // traverse a graph of circular references!
48 | gun.get('mark').get('boss').get('slave').once(function(data, key){
49 | console.log("Mark is the slave!", data);
50 | });
51 |
52 | // add both of them to a table!
53 | gun.get('list').set(gun.get('mark').get('boss'));
54 | gun.get('list').set(gun.get('mark'));
55 |
56 | // grab each item once from the table, continuously:
57 | gun.get('list').map().once(function(data, key){
58 | console.log("Item:", data);
59 | });
60 |
61 | // live update the table!
62 | gun.get('list').set({type: "cucumber", goal: "scare cat"});
63 |
64 | ## With Bullet: ##
65 |
66 | var cat = { name: 'Fluffy', species: 'kitty' }
67 | var mark = { boss: cat }
68 | cat.slave = mark
69 |
70 | // partial updates merge with existing data!
71 | bullet.mark = mark
72 |
73 | // access the data as if it is a document.
74 | let marksBoss = await bullet.mark.boss.name.value
75 | console.log("Mark's boss is", marksBoss)
76 |
77 | // add both of them to a table!
78 | bullet.list.set(bullet.mark.boss)
79 | bullet.list.set(bullet.mark)
80 |
81 | // grab each item once from the table, continuously:
82 | bullet.list.map().once(data => {
83 | console.log("Item:", data)
84 | })
85 |
86 | // live update the table!
87 | bullet.list.set({ type: "cucumber", goal: "scare cat" })
88 |
89 | # How is this possible? #
90 | It's simple really. Bullet wraps the Gun instance with a Proxy. Each property lookup (using dot notation or bracket) calls a `gun.get()` and returns the Proxy to be used for chaining. Any call to `bullet.value` will return a promise getter of `gun.once()`.
91 |
92 | # Utilities: #
93 | - `.value` - Promise getter for `.once()`; `let cats = await bullet.cats.value`
94 | - `.remove()` - `bullet.cats.remove()`
95 |
96 | # Writing your own Gun adapter, made easy: #
97 | One of the best features of Bullet is to write Gun adapters without having to know too much about the Gun constructor, proper placement to initialize your adapter, and bugs caused by not forwarding the events. Bullet takes care of all of this for you! Simply define a `Function` or `Class`, return an object that contains the events you want to hook into, and the rest is up to you.
98 |
99 | Example class:
100 |
101 | class gunAdapter {
102 | constructor(bullet, opts, context) {
103 | return {
104 | events : {
105 | // Storage
106 | get: function(...),
107 | put: function(...),
108 |
109 | // Wire
110 | in: function(...),
111 | out: function(...),
112 | }
113 | }
114 | }
115 | }
116 |
117 | Example function:
118 |
119 | function gunAdapter(bullet, opts, context) {
120 | return {
121 | events: {
122 | // Storage
123 | get: function(...),
124 | put: function(...),
125 |
126 | // Wire
127 | in: function(...),
128 | out: function(...),
129 | }
130 | }
131 | }
132 |
133 | To use your adapter, include it in your project then:
134 | `bullet.extend(gunAdapter)`
135 |
136 | # More coming soon! #
137 | Bullet expects to be a wrapper for other utility functions, offering an easy way to extend either bullet or gun via Proxies or directly. This will allow you to create custom bullet methods for wrapping verbose syntaxes.
138 |
139 | Some utility proposals have already emerged, such as handling arrays in a more suitable fashion.
140 |
141 | bullet.cats.sparky = { color: 'orange' }
142 | bullet.cats.howie = { color: 'white' }
143 |
144 | bullet.mark.cats = [bullet.cats.sparky, bullet.cats.howie]
145 |
146 | Another utility proposal was RPC
147 |
148 | // local
149 | bullet.rpc.host('peerName')
150 | bullet.rpc.register('procName', function(data) {})
151 |
152 | // Remote
153 | bullet.rpc.exec('procName', { some: 'data' })
154 | // or
155 | bullet.rpc.select('peerName').exec('procName')
156 |
157 |
158 |
159 | ## License: ##
160 | [MIT](https://github.com/bugs181/bullet/blob/master/LICENSE)
161 |
--------------------------------------------------------------------------------
/bullet.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | class Bullet {
4 | constructor(gun, opts) {
5 | this.gun = gun
6 | this.Gun = (typeof window !== 'undefined') ? window.Gun : require('gun/gun')
7 |
8 | // If we don't pass gun instance, then see if it's available from window or file - use Bullet's args to create a new gun instance.
9 | /*const hasGun = (gun && gun.chain) ? true : false
10 | if (!hasGun)
11 | this.gun = this.Gun(...arguments)
12 | */
13 |
14 | this._ctx = null
15 | this._ctxVal = null
16 | this._ready = true
17 | this._proxyEnable = true
18 |
19 | // Immutability is an opt-in feature. use: new Bullet(gun, { immutable: true })
20 | this.immutable = (opts && opts.immutable) ? true : false
21 |
22 | const that = this
23 | this.Gun.on('opt', function(context) {
24 | that._registerContext = context
25 | this.to.next(context)
26 | })
27 | this.gun = this.Gun(...arguments)
28 |
29 | this.mutate = this.mutate.bind(this)
30 | this.extend = this.extend.bind(this)
31 |
32 | return new Proxy(this.gun, bulletProxy(this))
33 | }
34 |
35 | get value() {
36 | return new Promise((resolve, reject) => {
37 | if (!this || !this._ctx || !this._ctx.once)
38 | return reject('No gun context')
39 |
40 | this._ctx.once(data => {
41 | let timer = setInterval(() => {
42 | // Wait until bullet is ready (pending .put results)
43 | if (this._ready) {
44 | resolve(data)
45 | clearInterval(timer)
46 | }
47 | }, 100)
48 | })
49 | })
50 | }
51 |
52 | get events() {
53 | // Example use: bullet.events.on('get')
54 | return this._registerContext
55 | }
56 |
57 | mutate(val) {
58 | if (!val && this._ctxVal) {
59 | this._ready = false
60 | this._ctxProp.put(this._ctxVal, () => this._ready = true)
61 | }
62 | }
63 |
64 | extend(clss, opts) {
65 | this._proxyEnable = false
66 | if (typeof cls === 'object')
67 | if (!Array.isArray(clss))
68 | throw new Error('bullet.extends() only supports a single utility or an array of utilities')
69 | else
70 | clss = [clss]
71 | else
72 | clss = [clss]
73 |
74 | for (let cls of clss)
75 | if (typeof cls === 'function') {
76 | const instance = new cls(this, opts, this._registerContext)
77 | this[instance.name] = instance
78 | this._registerInstanceHooks(instance)
79 | }
80 | this._proxyEnable = true
81 | }
82 |
83 | _registerInstanceHooks(instance) {
84 | // Register Gun.opts.on() events
85 | if (typeof instance.events === 'object')
86 | for (let event of Object.keys(instance.events)) {
87 | if (typeof instance.events[event] === 'function')
88 | this._registerContext.on(event, instance.events[event])
89 | }
90 | }
91 | }
92 |
93 |
94 | function bulletProxy(base) {
95 | return {
96 | get (target, prop, receiver) {
97 | // Return any class methods/props
98 | if (prop in target || prop === 'inspect' || prop === 'constructor' || typeof prop == 'symbol') {
99 | if (typeof target[prop] === 'function')
100 | target[prop] = target[prop].bind(target)
101 |
102 | return Reflect.get(target, prop, receiver)
103 | }
104 |
105 | // Proxy all other requests as chainables
106 | if (base[prop]) // Method exists in Bullet
107 | return base[prop]
108 |
109 | // Method does not exist, is a chainable
110 | base._ctx = new Proxy(target.get(prop), bulletProxy(base))
111 | return base._ctx
112 | },
113 |
114 | set (target, prop, receiver) {
115 | if (prop in base || !base._proxyEnable)
116 | return base[prop] = receiver
117 |
118 | if (!base.immutable) {
119 | this._ready = false
120 | target.get(prop).put(receiver, () => base._ready = true)
121 | } else {
122 | // eslint-disable-next-line no-console
123 | console.warn('You have immutable turned on; be sure to .mutate()')
124 | base._ctxProp = target.get(prop)
125 | base._ctxVal = receiver
126 | base._ready = true
127 | }
128 |
129 | return target
130 | },
131 | }
132 | }
133 |
134 | // If environment is not browser, export it (for node compatibility)
135 | if (typeof window === 'undefined')
136 | module.exports = Bullet
137 |
--------------------------------------------------------------------------------
/examples/index.html:
--------------------------------------------------------------------------------
1 |
2 |