Giter VIP home page Giter VIP logo

lotion's Introduction


Lotion
✨ Lotion ✨

Smooth, easy blockchain apps. Powered by Tendermint consensus.

Travis Build NPM Downloads NPM Version chat on slack test coverage


Lotion is a new way to create blockchain apps in JavaScript, which aims to make writing new blockchains fast and fun. It builds on top of Tendermint using the ABCI protocol. Lotion lets you write secure, scalable applications that can easily interoperate with other blockchains on the Cosmos Network using IBC.

Lotion itself is a tiny framework; its true power comes from the network of small, focused modules built upon it. Adding a fully-featured cryptocurrency to your blockchain, for example, takes only a few lines of code.

Note: the security of this code has not yet been evaluated. If you expect your app to secure real value, please use Cosmos SDK instead.

Installation

Lotion requires node v7.6.0 or higher, and a mac or linux machine.

$ npm install lotion

Usage

app.js:

let lotion = require('lotion')

let app = lotion({
  initialState: {
    count: 0
  }
})

app.use(function(state, tx) {
  if (state.count === tx.nonce) {
    state.count++
  }
})

app.start()

Introduction

Lotion lets you build blockchains. At any moment in time, the whole state of your blockchain is represented by a single JavaScript object called the state.

Users will create transactions: JavaScript objects that tell the application how to mutate the blockchain's state.

Every user who runs your Lotion app will interact with the same blockchain. Anyone can create a transaction, and it will automagically find its way to everyone else running the app and mutate their state. Everyone's state objects will constantly be kept in sync with each other.

A Lotion application is often a single function of signature (state, tx) which mutates your blockchain's state in response to a transaction tx. Both are just objects.

This cosmic wizardry is made possible by a magic piece of software named Tendermint which exists specifically for synchronizing state machines across networks.

Blockchains and Tendermint

The goal of a blockchain is to represent a single state being concurrently edited. In order to avoid conflicts between concurrent edits, it represents the state as a ledger: a series of transformations (transactions) applied to an initial state. The blockchain must allow all connected nodes to agree about which transformations are valid, and their ordering within the ledger.

To accomplish this, a blockchain is composed of three protocols: the network protocol, consensus protocol, and transaction protocol.

The network protocol is how nodes in the network tell each other about new transactions, blocks, and other nodes; usually a p2p gossip network.

The consensus protocol is the set of rules that nodes should follow to determine which particular ordered set of transformations should be in the ledger at a given moment. In Bitcoin, the chain with the highest difficulty seen by a node is treated as authoritatively correct.

The transaction protocol describes what makes transactions valid, and how they should mutate the blockchain's state.

When you're writing a Lotion app, you're only responsible for writing the transaction protocol. Under the hood, Tendermint is handling the consensus and network protocols. When you start your lotion app, a Tendermint node is also started which will handle all of the communication with other nodes running your lotion app.

Modules

name description
coins fully-featured cryptocurrency middleware
htlc hashed timelock contracts on coins
shea on-chain client code management
merk merkle AVL trees in javascript

Contributors

Lotion is a cosmic journey for the mind brought to you by:


Judd

💻 📖 🤔 ⚠️

Matt Bell

💻 🤔 ⚠️ 🔌

Jordan Bibla

🎨

Gautier Marin

📝

Jackson Roberts

💻

Mark Price

📖 💡 📹

Evan Leong

🎨

Chjango Unchained

📝 📋

SaifRehman

💡

Lola Dam

🐛 💻

Djenad Razic

💻

Admir Sabanovic

💻

Arne Meeuw

💬 🤔

Props.love

💻

Peng Zhong

🎨 💡 🤔

Contributions of any kind welcome!

API

let app = require('lotion')(opts)

Create a new Lotion app.

Here are the available options for opts which you can override:

{
  initialState: {},            // initial blockchain state
  keyPath: 'keys.json',        // path to keys.json. generates own keys if not specified.
  genesisPath: 'genesis.json', // path to genesis.json. generates new one if not specified.
  peers: [],                   // array of '<host>:<p2pport>' of initial tendermint nodes to connect to. does automatic peer discovery if not specified.
  logTendermint: false,        // if true, shows all output from the underlying tendermint process
  p2pPort: 26658,              // port to use for tendermint peer connections
  rpcPort: 26657               // port to use for tendermint rpc
}

app.use(function(state, tx, chainInfo) { ... })

Register a transaction handler. Given a state and tx object, mutate state accordingly.

Transaction handlers will be called for every transaction, in the same order you passed them to app.use().

Transaction handlers must be deterministic: for a given set of state/tx/chainInfo inputs, you must mutate state in the same way.

chainInfo is an object like:

{
  time: 1541415248, // timestamp of the latest block. (unix seconds)
  validators: {
    '<base64-encoded pubkey>' : 20, // voting power distribution for validators. requires understanding tendermint.
    '<other pubkey>': 147 // it's ok if you're not sure what this means, this is usually hidden from you.
  }
}

If you'd like to change how much voting power a validator should have, simply mutate chainInfo.validators[pubKey] at any point!

app.useBlock(function(state, chainInfo) { ... })

Add middleware to be called once per block, even if there haven't been any transactions. Should mutate state, see above to read more about chainInfo.

Most things that you'd use a block handler for can and should be done as transactions.

app.start()

Starts your app.

Global Chain Identifiers and Light Clients

Lotion apps each have a unique global chain identifier (GCI). You can light client verify any running Lotion app from any computer in the world as long as you know its GCI.

let { connect } = require('lotion')
let GCI = '6c94c1f9d653cf7e124b3ec57ded2589223a96416921199bbf3ef3ca610ffceb'

let { state, send } = await connect(GCI)

let count = await state.count
console.log(count) // 0

let result = await send({ nonce: 0 })
console.log(result) // { height: 42, ok: true }

count = await state.count
console.log(count) // 1

Under the hood, the GCI is used to discover and torrent the app's genesis.json.

It's also used as the rendezvous point with peers on the bittorrent dht and through multicast DNS to find a full node light client verify.

You can get the GCI of an app being run by a full node like this:

let app = require('lotion')({ initialState: { count: 0 } })

let { GCI } = await app.start()
console.log(GCI) // '6c94c1f9d653cf7e124b3ec57ded2589223a96416921199bbf3ef3ca610ffceb'

Links

License

MIT

lotion's People

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

lotion's Issues

UnhandledPromiseRejectionWarning

When running the code on the ReadMe I get:

C:\Users\Yousif\Documents\Dev\my-block-chain-app>node app.js
(node:5824) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): Error: ENOENT: no such file or directory, open 'C:\Users\Yousif.lotion\networks\f0de10af52caa20754575a42899e005c7cb48f461219042bc43cffd614934863\genesis.json'
(node:5824) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.

Running Node 8.7.0. Windows 8 64-bit
There is no .lotion directory under my home directory.
I tried this in Git Bash and my Command Prompt, and I also tried my command prompt under admin privileges.

Tx-server only availably locally - how to run a lite node?

Hi everybody. So far I have been using lotion locally to develop my application. I recently deployed it to two EC2 instances and found out two things:

The express tx-server is run with 'localhost' as host and therefore only available on the host itself. Now that I need to have clients sending transactions and not being full validator nodes I have two options:

  1. Run the tx-server openly on a port for clients to send their transactions to the lotion application. (probably a security issue)
  2. Run a lite node on the client side and submit transactions via a local tx-server (currently not working for me)

I have tried to run a lite node but have not been successful. It seems that the supercop module is crashing. Is there a step that I have to include other than running lotion as lite and giving it the GCI as a target?

As I actually have all the information (genesis etc.) from the chain - is there a way to run a light client without utilising the GCI and the bittorrent magic behind it?

Is opening a tx-server to the 'public' and using it as an end-point for my clients a wrong pattern?

Thank you very much.

curl hang

So I had app.js running and peer.js running in another term:

I tried curl http://localhost:3000/state in another term window and this is what showed up in the first window:

node app.js
(node:15154) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 5): Error: Request failed with status code 500
(node:15154) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.

sync progress event

let app = lotion({
  onSyncProgress: function (progress) { 
    // progress is a number from 0 - 1, estimated from block timestamps.
  }
})

perhaps?

Rewarding active validators; Punishing inactive validators;

@keppel

Sorry if this isn't the right place to discuss this, but I'm sure it's a question more "noobs" like myself have.

Tendermint is great because it allows us to achieve consensus without mining. However lets say I want to reward validators by a "currency".

So one way I can do this can be seen in my lotion-example/index.js:

app.useBlock((state, chainInfo) => {
  console.log({state, chainInfo})
  Object.keys(chainInfo.validators)
    .forEach(validator => {
      state.ledger.push({
        to: validator,
        from: '_network',
        at: chainInfo.height,
        amount: 0.0001
      })
    })
})

So this will, for every block, add a transaction of 0.0001. I'm aware that the transaction log itself could keep a track of this, meaning I could just have a key value pair for each address, but just for my learning I'm putting the transactions into the state itself. I guess I should be doing:

app.useBlock((state, chainInfo) => {
  console.log({state, chainInfo})
  Object.keys(chainInfo.validators)
    .forEach(validator => {
      state.accounts[validator] = state.accounts[validator] + 0.0001 || 0
    })
})

So, the above works great. However it always adds 0.0001 for every validator for every block.

Ideally I only want to reward a validator if they are online and have validated the block.

Maybe something like:

app.useBlock((state, chainInfo) => {
  console.log({state, chainInfo})
  Object.keys(chainInfo.validators)
    .forEach(validator => {
      if (chainInfo.activeValidator[validator]) {
        state.accounts[validator] = state.accounts[validator] + 0.0001 || 0
      } else {
        state.accounts[validator] = state.accounts[validator] - 0.001 || 0
      }
    })
})

tl;dr; I guess the brief question is: Is there any way to verify if a validator is active or not?

Thanks again

Where has 'lotion-stake' gone?

There is a mention of 'lotion-stake' on the lotionjs.com website but it seems like the repository has been taken down. Are there any updates on this repository?

block handler api

let lotion = require("lotion")({
  port: 3000,
  initialState: { txCount: 0, blockCount: 0 }
});

function txHandler(state, tx, chainInfo) {
  state.txCount++
}

function blockHandler(state, chainInfo) {
  state.blockCount++
}

let app = lotion(txHandler, blockHandler)

txHandler is required, blockHandler is optional. usually apps can do everything they need to with just a txHandler (for example, all of lotion coin is written with just a txHandler).

but sometimes you need state to change several blocks later in response to a tx. that's where you'd use a blockHandler.

Adding and Removing Validators

Currently from reading the docs I can see you can change the voting power dynamically by mutating the chainInfo.validators object.

However is there a way to add/remove a validator?

I was thinking maybe this is possible using the tendermint rest api? If so, we could add an app.validators object.

app.validators.push({
    "pub_key": {
    "type": "ed25519",
    "data": "BAA8FE961773E916FBE5DC4AB195F3DEB22FC9330D44880F1FC558EE71B8A47B"
  },
  "power": 10,
  "name": ""
})

Maybe this could encompass some of my findings from issue #73 too by adding a "blockLastSeen".

app.validators === [..., {
    "pub_key": {
    "type": "ed25519",
    "data": "BAA8FE961773E916FBE5DC4AB195F3DEB22FC9330D44880F1FC558EE71B8A47B"
  },
  "power": 10,
  "name": "",
  "blockLastSeen": 100
}]

If it's just not implemented yet, I'd be more than happy to if you can point me in the right direction.

Tendermint silently fails to launch when adding additional validators.

I have an application with a genesis.json and a keys.json which holds the keys for the validator. I can repeatedly delete the folder from my .lotion directory and it spins up perfectly each time.

However, if I add a second validator to the list it creates the data folder, but no requests to Tendermint on port 3000 from my web browser go through, they all fail to connect.

There is no debug message in my terminal, and logTendermint is on. localhost:3000/state gives me nothing, just fails to load.

I do not know how to begin debugging this issue.

[Bug] Signing error

Hello,
When generating a new genesis & keys file by Lotion, everything works perfectly.
After copying the genesis & privkey file to the root of the Lotion project, and setting the genesis to be loaded from the genesis.json file and keys such as loaded by the privkey file, after setting to true the logs, I got this:
E[01-21|17:28:18.153] Error signing vote module=consensus height=1 round=0 vote="Vote{0:12DCDF9D24F2 1/00/1(Prevote) 000000000000 {<nil>} @ 2018-01-21T17:28:18.153Z}" err="Error signing vote: Height regression"

The height seems to be defined to 1 (see the log above), but it's 478 in my priv_validator.json

Is it a bug or am I doing it wrong?

Thanks for the help!

Best regards,
AlexMog.

Handle connect() api websocket disconnects

Using the connect() api I am having trouble when the websocket to the full node disconnects.
Now that in v0.1.17 the event-emitter bus is also returned by the connect() method, I am listening on errors and try to catch the ones that are "websocket disconnected" type. I then rerun the connect() method to yield a new client and work with that one but it seems to have deeper troubles in the pumpify/duplexify modules...

Where could this problem lie? Any plans on handling disconnects and maybe autoreconnect in the connect() api?

My code to handle disconnect:

let client = await lotion.connect(null, {genesis, nodes})
if (client.bus != undefined) {
  client.bus.on('error', async(err)=>{
    if (err.toString().indexOf("websocket disconnected") > -1) {
      console.log("Reconnecting...")
      client = await lotion.connect(null, {genesis, nodes})
      txServer.lc = client
    }
  })
}

Feature: Lotion logo/branding update proposal

Logo Revision
I would like to propose some modifications to the logo and branding for lotion to set it forward as a professional-quality framework that companies can feel comfortable using - at least at first glance visually.

I've attached some logos that we (Devslopes) have created for Lotion. Created by our VP of Product Evan Leong. I'd love to get your thoughts about the variants and look into possibly picking one 😄

Open Source LotionJS.com
I would also like to propose open-sourcing lotionjs.com so we can actively develop on it. It would be the place where traffic gets directed and have documentation, tutorials etc. Me, and some other web developers on my team would LOVE to help build it out. Take it to the level you see on https://electronjs.org/ and other websites.

Might be cool to consider something more open-sourcey like lotionjs.io , lotion.io or something similar if that makes sense.

Logo Samples

Download Logo Samples

Empty items getting pushed into array with new version of lotion (0.1.13)

After updating my version of lotion I found empty items getting pushed into state arrays.

I just altered the base example from the README and yield null valued elements in my array.

app.js:

let lotion = require('lotion')

let app = lotion({
  initialState: {
    count: 0,
    elements: []
  }
})

app.use(function (state, tx) {
  if(state.count === tx.nonce) {
    state.count++;
    state.elements.push(tx.nonce);
  }
})

app.listen(3000)

localhost:3000/state after 2 transactions with increasing nonces:
{"count":2,"elements":[null,0,null,1]}

So far I have not been able to make out what and where the causing changes are - @keppel: Do you have any clue what it could be?

best,
arne

app.useBlock prevents app from working

Hello

It is entirely possible I'm using this completely wrong so I apologise if I'm doing something stupid here.

I have made an example app (based of your example) but split it out a bit.
https://github.com/markwylde/lotion-example

It's working perfectly, and I can add transaction which increase the blocks while running the app and peer.

But if you look in the app/index.js file I have commented out the following code:

// app.useBlock((state, chainInfo) => {
//   state.blocks++
// })

What I believe this should do is every time a block is created add 1 to the blocks property in state.

But the act of uncommenting this code stop's me being able to post new transactions. When I run the command below it simply hangs, but when it's commented it works fine.

curl -s http://localhost:3000/txs -d '{ "nonce": 0 }' | jq

I've added to the README.md file exactly how I'm running my "environment" if that helps.

Thanks for any help you can spare.

Node version: v9.4.0
OS: macOS High Sierra 10.13.2

chain info parameter

state machines can accept an optional third info parameter. right now I just need a way to know the current block height, but it seems likely there'll be other blockchain info apps might need.

let handler = (state, tx, info) => {
  console.log(info.height) // 52
}

crash & p2p question

I copied the counter.js file to counter2, counter3 and so on to listen on a few different ports. When I started counter.js on port 3000 and then started counter2.js on port 3001 I received this error:

/tmp/lotion/example/counter/node_modules/lotion/lib/tendermint.js:48
    throw new Error('Tendermint node crashed')
    ^

Error: Tendermint node crashed
    at ChildProcess.tendermintProcess.on /tmp/lotion/example/counter/node_modules/lotion/lib/tendermint.js:48:11)
    at emitTwo (events.js:125:13)
    at ChildProcess.emit (events.js:213:7)
    at maybeClose (internal/child_process.js:927:16)
    at Socket.stream.socket.on (internal/child_process.js:348:11)
    at emitOne (events.js:115:13)
    at Socket.emit (events.js:210:7)
    at Pipe._handle.close [as _onclose] (net.js:547:12)

Once this issue gets solved, all I need to do is /dial_seeds to get them linked up right?

Cannot properly close the lotion after pending promise objecrt

Cannot close immediatly lotion instance after still pending promise object
for example:
let nodeListener = lotion(........); nodeListener.listen(3000); nodeListener.close();
the line of code:
TypeError: Cannot read property 'close' of undefined
at Object.close (node_modules/lotion/index.js:236:17)

Global Chain Identifiers and light client API

Lotion apps should have a global chain identifier (GCI) which will simply be the IPFS hash of the underlying Tendermint node's genesis.json.

For full nodes, peers can be discovered via DHTs, multicast DNS, etc using the GCI as the rendezvous point. Light clients can also discover full nodes to connect to this way.

Eventually, all Lotion apps should, by convention, checkpoint recent block hashes into a single proof-of-work chain. The GCI will be used to identify checkpoints on that chain.

The GCI can also be used to as the prefix to a single immutable global address space. For instance, if coins supports IBC, you could create an output prefixed with the chain ID that should handle that output.

Lotion apps with on-chain client code should be connected to using their GCI.

Light client API once GCI's are implemented:

const GCI = 'QmT4AeWE9Q9EaoyLJiqaZuYQ8mJeq4ZBncjjFH9dQ9uDVA'
let { connect } = require('lotion')
let { state, send } = connect(GCI)

// query for part of the state. does all the merkle proof verification:
let myBalance = await state.accounts[myPubKey]
console.log(myBalance) // 42

// send a transaction
let { height, ok } = await send({ foo: 'bar' })
console.log(height) // 10

Note the lack of hardcoded full node addresses. Full nodes are discovered using the GCI and connections to multiple nodes are automatically managed for you. Developers should not ever have to think about the host:port of individual nodes.

Merkleized state

The goal is to move away from one big JSON blob of state towards some sort of merkle structure. Would be nice to preserve the API of state being just an object (using ES6 proxies) so that app code doesn't have to change.

Benefits:

  • Light-clients won't have to download/parse entire state
  • Cheaper to update state since we only have to reserialize the changed keys and merkle branches
  • Scalability 👐

Graceful shutdown

There should probably be an app.exit() method so that nodes can gracefully shutdown. This can be useful for scripts that dynamically start/stop nodes at runtime, and is also needed for tests (we currently use process.exit() at the end of the tests, which is ghetto).

a question about get txs

After read code, I found that txs store in a nother database (txCache)which will not sync by Tendermint. So that means when I query a tx information in one peer, I can get it. If I query this information in another peer, I can’t get it, right? Emmmm, I have another question : how to let one lotion peer connect another one? I can’t find this way in document 😅😅😅

async state machines

this is important for a few reasons:

  • allows lotion to be the glue between tendermint and a program written in any other language. this is the sort of thing where Node shines.
  • allows a merkle-ized data store for the app state.

transactions will still be handled serially, they just don't have to mutate the state synchronously.

enable unsafe tendermint rpc features, bind to localhost

/dial_peers endpoint requires unsafe mode for rpc. since only the lotion transaction and abci servers talk to the tendermint node, it's ok to run in unsafe mode as long as the tendermint node isn't exposed to the outside world.

not a issue but a feature

Lotion apps can now be easily deployed in any cloud premise. Most importantly cloud supporting cloud foundry. It is just matter of cf push to deploy a whole blockchain with sexy frontend.
Read more about it in readme.
link: https://github.com/SaifRehman/tendermint-chat-app
I will be soon writting a blog and hosting meetup in dubai regarding this. I have googled the entire internet, nobody was able to host a blockchain on cloud foundry, this is possible now with lotion/tendermint. Great job guys, i loved the tech!
@keppel

[Question] Production process

Hello,
I have created some tests with Lotion, and I would like to know the process to finally use a production Blockchain instead of the "test-*" generated one.
(it's not to pass in production right now, jsute to understand how to do it)
I have no idea how to do this.

Thanks for the answers!

Best regards,
AlexMog.

State persistence

Currently, the state is only held in memory, so a full chain replay is done every time a node starts up. It's possible to persist the state in a way that doesn't break the synchronous app handler APIs, we just need to flush the changes to disk in between each block.

initial state from genesis key

if initialState isn't provided in the lotion configuration object, lotion should try to read the initial state from initial-state.json in the lotion home directory.

this allows connecting to networks running the same state machine but with a different initial validator set and state by just changing the genesis key.

priv_validator.json Error: ENOENT: no such file or directory

I'm trying to run a simple example but I get the following error:

image

"UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): Error: ENOENT: no such file or directory, open 'C:\Users\lucas.vieira.lotion\networks\583c7041c2ce21ac5778207c08c3f4a755397364552927bea49218eb475f13e9412962989\priv_validator.json'
(node:6944) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js
process with a non-zero exit code."

here is my code:

options = {
    initialState: {
        count: 0
    },
    genesis: 'genesis.json',
    keys: 'keys2.json',
    peers: ['localhost:46661'],
    p2pPort: 46660,
    devMode: true
}

let app = require('lotion')(options)
  
app.use((state, tx) => {
    state.count++
})
  
app.listen(3001)

I'm using windows 10. Already tried to run rm -rf ~/.lotion

Chain Resets if i change the app Logic

If i change the app logic on all the peers.And restart all the peers.The chain is starting from Block 0.
what's happening seems correct but,
Its bad if i have to start from the beginning.
Is there any solution for this?

How can I specify a config.toml?

I cannot figure out how to specify a config.toml for a lotion app. I've tried placing the file in both ~/.lotion and the /bin directory containing Tendermint, but both seem to have been ignored.

initializers for middleware authors

alternate way to initialize a bit of state:

module.exports = [
  { type: 'initializer', middleware: function(state) { state.partINeed = {} }
]

app instantiation promise resolution

currently the promise returned from lotion() resolves once the tx server and tendermint node are running. instead, the promise should be resolved when the tendermint node has finished syncing.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.