GunJS Starterkit and Quickstart - amark/gun GitHub Wiki
GUNJS-Starterkit
A collection of tools you need to run a local-first, decentralized graph database.
- Developer support
- What is GunJS?
- GunJS tools
- Blueprints
- Quickstart
Developer support
https://github.com/amark/gun/wiki
For GunJS Wiki checkhttps://gitter.im/amark/gun
If you have any more questions hit the GunJS communityNeed a hand? Artificial Intelligence Pair Programming
I work with ChatGPT for hours every day.
He writes me prototypes and POCs, and i just modify them myself.
In addition, ChatGPT is an excellent tool for crafting well-formulated questions to present to a community of developers. (To get the most out of ChatGPT's language assistance, simply start by saying "improve my text" and provide your initial question or statement. ChatGPT will then generate a response, which you can copy and paste back into the prompt. This process will allow you to refine your original question or statement and make any necessary corrections to ensure that your meaning is clear. By repeating this step, you can achieve a perfectly formulated question or statement that accurately conveys your intended message.)
See for yourself
It makes everything soo much easier. It's perfect for checking ideas right away.Browser: https://openai.com/blog/chatgpt/
Desktop App (Win, Mac, Linux) https://github.com/lencx/ChatGPT (installables on releases page)
NEW! Supertools https://supertools.therundown.ai All available AI Tools on one page (with filter options)
What is GunJS?
GunJS GunDB Gun
GunJS is still in an early phase. Respect the following for a stable production outcome!
Using public (stranger) relays can pose a risk because they may be wiped at any time. However, this may not necessarily be a significant problem. Instead, developers of gun applications should prioritize the local graph on the client-side, as the relay primarily serves the purpose of syncing, not long-term storage. If the intention is to rely on relays for long-term storage, this technology is not yet mature enough for that purpose. But again, the local graph client-side is your long-term storage. Striking a balance between deduplication, availability, and consistency is crucial.
By the way, have you considered thinking outside the box? For example, for long-term storage, why not encrypt the user profile and store it on Github? A repository could contain all the necessary data, history, pictures, and more. Combined with Github's versioning, the possibilities are endless!
Peer-to-peer pubsub
Pubsub is an architecture where you subscribe to a “topic”, like “cats”, then whenever someone publishes a message of topic “cat”, you receive it. A peer-to-peer pubsub network means that anyone can publish, and anyone can subscribe.
GunJS Tools
Gun, Desktop-Relay, Form, Relay Donation, Digital Globe (location-based services)
https://github.com/amark/gun
An open source cybersecurity protocol for syncing decentralized graph data.
https://github.com/worldpeaceenginelabs/ELECTRON-GUN
A Gun Relay Server executable for Desktop (Windows,-Linux,-MAC)
https://github.com/worldpeaceenginelabs/FORM-GUN
A decentralized form handler with Gun for "static" JAMStack websites on a CDN. Plus "Svelte Compiler Tutorial with Gun"
https://github.com/worldpeaceenginelabs/DONATE-DECENTRALIZE-UI
A little taskbar which empowers your fanbase to deploy a Gun relay on multiple ways to support your decentralized website/app.
https://github.com/worldpeaceenginelabs/CESIUM-GUN
Storage logic
https://github.com/amark/gun/wiki/Storage
GunJS - Included and third-party storage solutionsTo permanently store all GUN's data, your GUN server app must define which storage engine to use. By default a GUN server will store the data in a file, but this is just meant to be used during development. It is not a sound solution for production.
Webcrypto API
https://gist.github.com/pedrouid/b4056fd1f754918ddae86b32cf7d803e (Web Cryptography API Examples - A collection of well commented, well ordered snippets for 20 algorithms)
https://diafygi.github.io/webcrypto-examples/ (This table is live! Every ✓ or ✗ on this page is a test to see if your browser supports that method in WebCryptoAPI.)
🙏 😂
God bless the internet, open-source and collaboration. Amen Authentication, encryption/hash, security
https://github.com/worldpeaceenginelabs/GunJS-AUTH-SIGN-UP-LOGIN-LOGOUT
Gun-Auth - Ready to go sign-up, login, logout for the decentralized database GunJS.
https://github.com/wuyanxin/totp.js
Demo
TOTP(Time-based one-time password) generator, support for Google Authenticatorhttps://github.com/antelle/argon2-browser
Argon2 library compiled for browser runtime
https://github.com/ai/nanoid
A tiny (130 bytes), secure, URL-friendly, unique string ID generator for JavaScript
https://github.com/mozilla/node-srp
BLUEPRINTS
A collection of blueprints to give you an impression what a Gun app looks underneath
-
METAVERSE-DAO Dapp Blueprints
- your Gun App blueprints here...
QUICKSTART
for components, apps, webapps, dapps... (the decentralized back-end)
Table of contents
- GUN Relays
- Basic Principles
- Spaces
- Frozen Space = Content Addressing
- Comparing the graph structure to a folder system
- Fetching and storing data
- SEA (security, encryption, authentication)
- Understanding your GUN Relay
GUN Relays
Gun works local first even without ever using a relay, but for syncing between clients you need a relay
Gun Relay (How to run a node - Deploy a GUN relay server everywhere on GUN WIKI)
Gun Relay Desktop (Electron Gun)
Gun Relay Donation Tool (Donate Decentralize UI)
Basic Principles
more methods...(API) but these three are the basic ones.
.get() | .put | .on - There areThis is pretty much the core of everything. Notice how easy it is to connect your front-end code with the graph database GUN.
Notice that no matter how complex your function is: You just drop the result into one or multible variables and connect them to the GUN write function (green boxes, green lines).
db.on(data => {//your function here});
and get the data that you wrote to GUN before (red boxes, red lines)
Last, you can easily receive the data in any function, again, no matter how complex, by
.on(data)
subscribes to the GUN graph. Everything's new to the graph will automatically be rendered on the globe. Both local storage graph and/or relay graph changes!
The Spaces
GUN supports more than just key/value pairs, it is a graph database that can store SQL-like tables, JSON-like documents, files and livestreaming video, plus relational and hypergraph data!
Beware! Anyone can edit the data by default - to fix this, we have to use the User system. GUN is a universal graph which has 3 logical "spaces" protected by SEA:
Public Space
.get(name).put(data)
https://gun.eco/docs/API
Anyone can add, change, or remove data from here. Think of it as a giant wiki.
Note: Some data here may be encrypted such that the content stays secret, but it can always be overwritten. Imagine in real life someone hides a prize in a vault at the beach: Once it is found it may be damaged or moved, but only a person who knows the key can unlock it.
User Space
user.get(name).put(data)
https://gun.eco/docs/User
(or Key Space) Only data signed with the user's key can be put. Uses SEA. This data can only be be changed, added or removed, by the owner. The data can be either private or publicly readable.
Note: Data is cryptographically owned by the user, there is no "app admin" or "website owner", this may change how you build apps but it guarantees better safety. Owners can authorize or give other users permission to edit the owner's data. Again, the owner does this, not the app developer or database admin.
Frozen Space = Content Addressing
gun.get('#name').get(name).put(data);
https://gun.eco/docs/Frozen
(Hash Space, Content Id Space) The # operator is used. Gun interprets something like "Only allow data to be put here if its hash matches the appended hash object." This data cannot be changed or removed, only added to. Nobody owns this data.
Content Addressing (general explainer)
When data is added to a hash space, its contents are hashed using a cryptographic algorithm, producing a unique hash value. This hash value is then appended to the location in the content ID space where the data is being added.
Subsequently, if anyone wants to add more data to that same location, they must include the same hash value in their request. This ensures that only data with the correct hash value can be added to that location.
Once data is added to a hash space, it cannot be changed or removed. This means that the data is immutable and its contents remain fixed over time.
Nobody owns the data in a hash space, as it is stored in a decentralized and distributed network. This makes it difficult for any individual or entity to exert control over the data.
Once you know the CID of a file on the network, you have all you need for the network to locate and return the file back to you.
Data can be in graphs that link across different spaces.
Immutable links to mutable user content
Storing data
// Logged in user writes a message in his signed graph. Notice, it should be an object in order to have a soul
gun.user().get('messages').set({text:'hello'}).on(async data => { //store message in User Space then...
let soul = data._["#"] // gets us the Soul of the just stored node
let hash = await SEA.work(soul, null, null,{name:'SHA-256'}) // gets us the hash of the above data
gun.get('#messages').get(hash).put(soul) // User puts a hashed soul of the message in a public content-addressed node
})
Fetching data
// Others can read the message later with the soul
gun.get('#messages').map().on(data=> {
gun.get(data).once((d=>console.log(d))) // {text:'hello'}
})
Immutable links to mutable user content with data from variable
Content-addressed post, editable by user
Creating (storing) the post
function onSubmit{
// Logged in user stores a post in his signed graph. Notice, it should be an object in order to have a soul
let data = {name: 'Alex', city: 'New York', description: 'blabla', zoomLink: 'https://zoom.us/5sdf4w', long: '50.00', lat: '14.0'}
gun.user(user.is).get('posts').put(data).on(async data => { //store message in User Space then...
let soul = data._["#"] // gets us the Soul of the just stored node
let hash = await SEA.work(soul, null, null,{name:'SHA-256'}) // gets us the hash of the above data
gun.get('#posts').get(hash).put(soul) // User puts a hashed soul of post1 in a public content-addressed node
})}
Fetching the posts
// Others can read the posts later with the soul
gun.get('#posts').map().on()
SEA.certify
Immutable links to mutable user content withContent-addressed post, editable by user, other users can react to our post
Creating (storing) the post
function onSubmit{
// Logged in user(123) stores a post in his signed graph. Notice, it should be an object in order to have a soul
let data = {name: 'Alex', city: 'New York', description: 'blabla', zoomLink: 'https://zoom.us/5sdf4w', long: '50.00', lat: '14.0'}
// user.is and user(123) are the same
gun.user(user.is).get('posts').put(data).on(async data => { //store message in User Space of user(123) then...
let soul = data._["#"] // gets us the Soul of the just stored node
let hash = await SEA.work(soul, null, null,{name:'SHA-256'}) // gets us the hash of the above data
let certificate = await SEA.certify("*", {"*": "interestedpost1", "+": "*"}, user._.sea); // let users write into User Space of the post issuer. (user.is/user(123))
gun.get('#posts').get(hash).put(soul, {opt: {cert: certificate}}) // User puts a hashed soul of post1 in a public content-addressed node
})}
Fetching the posts
// Others can read the posts later with the soul
gun.get('#posts').map().on()
Now we have a content-addressed post and users will store their interest or participation in the user space of the post issuer/publisher. (user(123))
function onSubmit{
gun.user(123)
.get(interestedpost1).put({pubkey: {user.is}, interested: true/false}, {opt: {cert: certificate}}) //user.is = other user interested in user(123)'s post
}
Comparing the graph structure to a folder system
Allowed types
.put restricts the input to a specific subset:
- objects: partials, circular, and nested
- strings
- numbers
- booleans
- null
Saving array in Gun, but rather go for objects from the begin if possible)
Note: Other values, like undefined, NaN, Infinity, arrays, will be rejected. (You cannot save primitive values at the root level.
gun.put("oops"); // error
gun.get("odd").put("oops"); // error
You can circumvent this by specifying
// Initialize GUN and tell it we will be storing all data local, and sync with relay http://localhost:8765/gun, and under the rootnode 'yourappname' in the graph...
var gun = Gun(['http://localhost:8765/gun']).get('yourappname')
// ...then you can call the same content addresses as above without error
gun.put("oops"); // ok
gun.get("odd").put("oops"); // ok
let gun = Gun();
, but no-brainer descriptions are preferable (what was Gun again?) let db = Gun();
and then you can call your database like
btw: common is db.put("oops"); // ok
db.get("odd").put("oops"); // ok
Saving objects
gun.get('key').put({
property: 'value',
object: {
nested: true
}
})
Saving primitives
// strings
gun.get('person').get('name').put('Alice')
// numbers
gun.get('IoT').get('temperature').put(58.6)
// booleans
gun.get('player').get('alive').put(true)
Fetching and storing data
.get(name).get(name)
So your references will basically look like this: The following examples feature always the same reference, but handled with different methods:
Fetching data
.get(name).get(name).on(data) // subscribes to ```.get(name).get(name)``` in public space
user.get(name).get(name).on(data) //subscribes to ```.get(name).get(name)``` in user space
.get(name).get(name).once(data) // fetches ```.get(name).get(name)``` in public space once
user.get(name).get(name).once(data) // fetches ```.get(name).get(name)``` in user space once
Note: GUN is a functional reactive database for streaming event-driven data, gotta love/hate buzzwords - right? This means that .on subscribes to realtime updates, and may get called many times. Meanwhile .once grabs the data once, which is useful for procedural operations.
Storing data
.get(name).get(name).put(data) // store data in ```.get(name).get(name)``` in public space
user.get(name).get(name).put(data) //store data in ```.get(name).get(name)``` in user space
SEA - Security, Encryption, & Authorization
https://gun.eco/docs/SEA
Sign-up, login, authentication, session management, and log-out
- When a user signs in or creates an account, their username and password are stored in the GUN database for recall.
- The Passphrase the user chooses will be extended with PBKDF2 to make it a secure way to login.
- When a user logs out, their session is terminated.
Store data encrypted to User Space
This code encrypts the text 'secret text' with the user's public key using the Gun.SEA.encrypt method. The encrypted text is then stored in the user's private space using the db.user().get('encryptedText').put(encryptedText) method. NOTE: We need an authenticated (logged-in) user for this code to work.
Understanding your GUN Relay
Question 1
ok so this sounds like a very complex setup to be decentralized and I dont want to have to pay for a bunch of relays I own myself. I highly doubt any of my customers would "donate" a relay. Wouldnt that mean it has to be always on? Or on a decent amount of the time? And all this wiping a relay and concern about security and encryption....makes me think GUN is not ready for production use
Answer 1
I am actually about to go into my first production with my first Gun dapp, Couchsurfing Decentralized. Should be ready by the middle of the next week.
You say complex, but what does a centralized setup look like?
domain, dns provider, server
What does a decentralised setup look like?
domain, dns provider, relay
So not that different.
But full control, flexibility, speed(at least Gun), privacy, and endless potential because of the new possible design approachs.
😂 I hear that often first, but of course you need an incentive to activate your customer/user to donate a relay on, for instance, their desktop. A value could be a faster access due to your user hosting a relay for instance.
And no, relays do not have to be always on.
Gun is a local first database which works out of the box even without a relay. Then just limited to its local graph and does not sync.
For your understanding: you could have a local app which's database is Gun and which runs years local without even ever connecting to a relay.
By experience a relay runs days, weeks, rather month, before it gets wiped, but I estimate the median lifetime of a relay to be a few hours to 48hours max. (just for my worst case calculations!)
In this time(few hours to 48h) we can sync the local graphs(databases) of our clients with no problem.
If the origin client of a piece of data is offline, the relays will continue to distribute the synced piece of data.
This means in practice, our piece of data is cached minimum a few hours, 48 hours, or even month.
And there is a high chance the user comes back within 48 hours, making the piece of data available to the relays again.
Now imagine all of your 10 relays get wiped at once.
gun.get(something).get(something).on(data)
After, the clients and the relays will start it's operation like nothing had happened before. So the relays start again to sync the local graphs of the clients to each other, based on the clients subscription/content-address Question 2
you say they have a lifetime of 48 hours max....how does that work? Does the relay server actually shut down?
Answer 2
I say 48 hours max, only for my worst case calculations!
In reality you spin up a relay and it runs continously for weeks and month.
And its more about a reset which could cause a wipe, or a wipe causing a reset. Its not so much about relays would shut down(except in the following free tier example), they are stable, they run and they reset if necessary.
So almost Zero maintainence!
But possible:
- If its a free tier relay, with limitations, then it has maybe not 24h uptime, or gets wiped every x hours/days. (have minimum 3 of them at different free providers so they will balance each other out)
- You have no control over relays that you dont own. They could be wiped any time. (have minimum 3 foreign relays so they will balance each other out)
- Incentivised or "donated" desktop relays go on and off, new come, old go. (have minimum 3 donated relays so they will balance each other out.
Makes 9 individual "dangerous" relays, but if we would go for the following code, which combines all 9 of them, i promise you no problems at all. Thats decentralization at its best...
// this code says all your Gun app client users initialize their Gun instance with all 9 relays at once.
var db = Gun(['https://relay1.com/gun, https://relay2.com/gun, https://relay3.com/gun, https://relay4.com/gun, https://relay5.com/gun, https://relay6.com/gun, https://relay7.com/gun, https://relay8.com/gun, https://relay9.com/gun,'])
Incentivised or "donated" desktop relays go on and off, new come, old go. (have minimum 3 donated relays so they will balance each other out.
You could go for a script which fills the {drelays} variable at the begin of the following code from a list of donated relays. The criteria to add and delete relays from that list could be their monthly uptime.
var drelays = relayUptimeFilter();
var db = Gun(['{drelays}, https://relay1.com/gun, https://relay2.com/gun, https://relay3.com/gun, https://relay4.com/gun, https://relay5.com/gun, https://relay6.com/gun'])