Giter VIP home page Giter VIP logo

reatom's Introduction

Reatom is a ultimate logic and state manager for small widgets and huge SPAs.

Key features

  • simple and powerful abstractions. There are only three main primitives: ctx, atom, action, all other features and packages works on top of that.
  • immutable and reliable. All pure computations processed with atomicity guaranties.
  • explicit reactivity without proxies. To archive maximum performance we have atomization pattern.
  • perfect effects management. Advanced async package allows you to describe complex async flows with caching, retrying and automatic cancellation with native await and AbortController.
  • nice debugging experience. Each atom and action update stores immutable cause (call) stack which is super helpful for debugging complex async flows. To simplify this we have logger package.
  • implicit DI. To run tests and SSR 100% safety you need an isolation layer, which ctx is! We have the extra testing package with pack of helpers for a mocking.
  • actor-like lifecycle hooks To archive a trhtully modularity you have ability to describe self-sufficient models
  • smallest bundle size: 2 KB gzipped Because of the power of the base primitives all ecosystem with A LOT of enterprize-grade helpers took only ~15KB - insane!
  • best TypeScript experience Automatic type inference is one of the main priority of Reatom developement.

The core package includes most these features and you may use it anywhere, from huge apps to even small libs, as the overhead is tiny. Also, you could reuse our carefully written helper tools to solve complex tasks in a couple lines of code. We are trying to build a stable and balanced ecosystem for perfect DX and predictable maintains even for years ahead.

Simple example

Let's define input state and compute a greeting from it.

Install

npm i @reatom/core

vanilla codesandbox

react codesandbox

Simple example model

The concept is dumb - if you want to make a variable reactive, wrap the init state in atom, you will alow to change the state by calling it as a function. Need reactive computed? - wrap it to atom!

actions needed to separate logic and batch atom updates.

All data processing should be immutable, all side-effects should be wrapped to ctx.schedule.

What is ctx? It is the most powerful feature of Reatom. It flows as the first argument across all Reatom functions, bringing you enterprise-grade features with only three extra symbols!

import { action, atom } from '@reatom/core'

const initState = localStorage.getItem('name') ?? ''
export const inputAtom = atom(initState)

export const greetingAtom = atom((ctx) => {
  // `spy` dynamically reads the atom and subscribes to it
  const input = ctx.spy(inputAtom)
  return input ? `Hello, ${input}!` : ''
})

export const onSubmit = action((ctx) => {
  const input = ctx.get(inputAtom)
  ctx.schedule(() => {
    localStorage.setItem('name', input)
  })
})

Simple example context

The context set up once for the whole app, or multiple times if you need isolation in SSR or tests.

import { createCtx } from '@reatom/core'

const ctx = createCtx()

Simple example view

import { inputAtom, greetingAtom, onSubmit } from './model'

ctx.subscribe(greetingAtom, (greeting) => {
  document.getElementById('greeting')!.innerText = greeting
})

document.getElementById('name').addEventListener('input', (event) => {
  inputAtom(ctx, event.currentTarget.value)
})
document.getElementById('save').addEventListener('click', () => {
  onSubmit(ctx)
})

Check out @reatom/core docs for detailed explanation of key principles and features.

Do you use React.js? Check out npm-react package!

Advanced example

The core package is already profitable and you could use only it as a most simple and featured solution for your state and logic management. If you want to solve common system logic with advanced libraries to care more optimizations and UX tasks - continue and check this small guide out.

This example is close to real life and shows the complexity of interactive UI. It is a simple search input with debouncing and autocomplete. It uses GitHub API to fetch issues by query. The limitations of this API are described in GitHub docs. We need to reduce the number of requests and retry them if we hit the limit. Also, we need to cancel all previous requests if a new one is created, to prevent race conditions - when the previous request resolves after the new one.

Install framework

npm i @reatom/framework @reatom/npm-react

codesandbox

Advanced example description

We will use @reatom/core, @reatom/async and @reatom/hooks packages in this example by importing it from the meta package @reatom/framework - it simplifies imports and dependencies management.

reatomAsync is a simple decorator which wraps your async function and adds extra actions and atoms to track creating promise statuses.

withDataAtom adds property dataAtom which saves the last effect result and allow you to subscribe to it. withCache enable function middleware which prevent it's extra calls based on passed arguments identity - classic cache. withAbort allows to define concurrent requests abort strategy, by using ctx.controller (AbortController) from reatomAsync. withRetry and onReject handler help to handle temporal rate limit.

Simple sleep helper (for debounce) gotten from utils package - it is a built-in microscopic lodash alternative for most popular and tiny helpers.

onUpdate is a hook which link to the atom and calls passed callback on every update.

Advanced example model

import { atom, reatomAsync, withAbort, withDataAtom, withRetry, onUpdate, sleep, withCache } from "@reatom/framework"; // prettier-ignore
import * as api from './api'

const searchAtom = atom('', 'searchAtom')

const fetchIssues = reatomAsync(async (ctx, query: string) => {
  await sleep(350) // debounce
  const { items } = await api.fetchIssues(query, ctx.controller)
  return items
}, 'fetchIssues').pipe(
  withAbort({ strategy: 'last-in-win' }),
  withDataAtom([]),
  withCache({ length: 50, swr: false, paramsLength: 1 }),
  withRetry({
    onReject(ctx, error: any, retries) {
      // return delay in ms or -1 to prevent retries
      return error?.message.includes('rate limit')
        ? 100 * Math.min(500, retries ** 2)
        : -1
    },
  }),
)

// run fetchIssues on every searchAtom update
onUpdate(searchAtom, fetchIssues)

Advanced example view

import { useAtom } from '@reatom/npm-react'

export const Search = () => {
  const [search, setSearch] = useAtom(searchAtom)
  const [issues] = useAtom(fetchIssues.dataAtom)
  // you could pass a callback to `useAtom` to create a computed atom
  const [isLoading] = useAtom(
    (ctx) =>
      // even if there are no pending requests, we need to wait for retries
      // let do not show the limit error to make him think that everything is fine for a better UX
      ctx.spy(fetchIssues.pendingAtom) + ctx.spy(fetchIssues.retriesAtom) > 0,
  )

  return (
    <main>
      <input
        value={search}
        onChange={(e) => setSearch(e.currentTarget.value)}
        placeholder="Search"
      />
      {isLoading && 'Loading...'}
      <ul>
        {issues.map(({ title }, i) => (
          <li key={i}>{title}</li>
        ))}
      </ul>
    </main>
  )
}

The whole logic definition is only about 15 LoC and it is not coupled to React and could be tested easily. What would the lines count be in a different library? The most impressive thing is that the overhead is less than 4KB (gzip) could you imagine?! And you are not limited to network cache, Reatom is powerful and expressive enough for describing any kind of state.

To get maximum of Reatom and the ecosystem just go to tutorial. If you need something tiny - check out the core package docs. Also, we have a package for testing!

Roadmap

  • Finish forms package
  • Finish persist, improve url packages
  • Add adapters for most popular ui frameworks: react, angular, vue, svelte, solid.
  • Port some components logic from reakit.io, to made it fast, light and portable.
  • Add ability to made async transaction and elaborate optimistic-ui patterns and helpers / package.

FAQ

Why not X?

Redux is awesome and Reatom is heavy inspired by it. Immutability, separation of computations and effects are good architecture designs principles. But there are a lot of missing features, when you trying to build something huge, or want to describe something small. Some of them is just impossible to fix, like batching, O(n) complexity or that selectors is not inspectable and breaks the atomicy. Others is really hard to improve. And boilerplate, yeah, the difference is a huge. Reatom solves all this problems and bring much more features by the almost same size.

MobX brings too big bundle to use it in a small widgets, Reatom is more universal in this case. Also, MobX has mutability and implicit reactivity, which is usefull for simple cases, but could be not obvious and hard to debug in complex cases. There is no separate thing like action / event / effect to describe some dependent effects sequences (FRP-way). There is not atomicy too.

Effector is too opinionated. There is no first-class support for lazy reactive computations and all connections are hot everytime, which is could be more predictable, but defenetly is not optimal. Effector is not friendly for fabric creation (because of it hotness), which disallow us to use atomization patterns, needed to handle immutability efficient. The bundle size is 2-3 times larger and performance is lower.

Zustand, nanostores, xstate and many other state managers have no so great combination of type inference, features, bundle size and performance, as Reatom have.

Why immutability?

Immutable data is much predictable and better for debug, than mutable states and wrapers around that. Reatom specialy designed with focus on simple debug of async chains and have a patterns to handle greate performance.

What LTS policy is used and what about bus factor?

Reatom always developed for long time usage. Our first LTS (Long Time Support) version (v1) was released in December 2019 and in 2022 we provided breaking changes less Migration guide to the new LTS (v3) version. 3 years of successful maintains is not ended, but continued in adapter package. We hope it shows and prove our responsibility.

Currently, there are four people in the development team: @artalar and @krulod are managing the core features, while @BANOnotIT and @Akiyamka help with documentation and issue management. A lot of people contributes to different packages.

What build target and browser support?

All packages are configured based on Browserslist's "last 1 year" query. If you need to support older environments, you should handle transpilation yourself.

All builds have two types of output formats: CJS (exports.require, main) and ESM (exports.default, module). You can check package.json for more details.

How performant Reatom is?

Here is the benchmark of complex computations for different state managers. Note that Reatom by default uses immutable data structures, works in a separate context (DI-like) and keeps atomicity, which means the Reatom test checks more features, than other state manager tests. Anyway, for the middle numbers Reatom faster than MobX which is pretty impressive.

Also, check out atomization guide.

Limitations

Of course there are no software without limitations. Reatom is trying to be a silver bullet but we still have some cases which you should know about.

  • Immutable data always have an additional performance impact and in critical cases you should think well about your structures and how you could handle it better. The good news is that you don't have to use normalization.
  • Laziness could be not obvious in some cases and will cause some updates missing. But it easy to debug a missing update, which is more explicit, than memory leaks and performance issues of hot observables. Anyway, we have hooks for hot linking.
  • Currently, there is no way to subscribe on error of any dependency, but we are working on it. In reatomAsync passed effects wraps to an error handler and allow you to handle errors, but again - you should wrap it explicit.
  • Currently, there is no asynchronous transactions support, but we are working on it. It is important feature for simplify building of optimistic UI and we really think it will improve UX a lot.
  • We have a lot of utils and the ecosystem is growing all the time, but the target of that is have a set of well done logic primitives, and there is no architecture framework or codestyle / declarative framework to fit you in one strict flow. Reatom trying to be in the middle of a library and a framework. We love procedural programming with minimum extra API and semantic overhead. Our defaults are good already to help to you to write a better code: immutability and lazyness, transactions and separation of pure computations and effects, ctx and connections and processes virtualizations.

Media

How to support the project?

https://www.patreon.com/artalar_dev

Zen

  • Good primitive is more than a framework
  • Composition beats configuration

Credits

Software development in 202X is hard and we really appreciate all contributors and free software maintainers, who make our life easier. Special thanks to:

  • React, Redux, Effector and $mol for inspiration
  • microbundle for handling all bundling complexity
  • Quokka and uvu for incredible testing experience
  • TURBO for simple monorepo management
  • Astro for best in class combine of performance and developer experience
  • Vercel for free hosting and perfect CI/CD (preview branches are <3)

reatom's People

Contributors

akiyamka avatar allcontributors[bot] avatar arkatriymfalnaya avatar arswarog avatar artalar avatar atassis avatar banonotit avatar bataevdaniil avatar belozer avatar crashmax-dev avatar dependabot[bot] avatar github-actions[bot] avatar godested avatar ikabirov avatar ilyaryabchinski avatar kirillku avatar krulod avatar lid0a avatar mungell avatar nin-jin avatar osovv avatar pivaszbs avatar romadryud avatar stenin-nikita avatar tamrazov avatar vitalybaev avatar vovanezha avatar whiteand avatar wroud avatar xavescor avatar

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

reatom's Issues

react: subscribe to atoms not updated after props update

    test('updates dynamic atom state after props change', () => {
      const store = createStore(countAtom, { count: 10 });
      const { result, rerender } = renderHook(({
        multiplier 
      }) => useAtom(map(countAtom, count => count * multiplier)), {
        initialProps: { multiplier: 2 }, 
        wrapper: props => <Provider {...props} store={store} />
      })

      expect(result.current).toBe(20);

      rerender({ multiplier: 3 })
      expect(result.current).toBe(30); // actual: 20
    })

    test('does not update state if flag "isUpdatesNotNeeded" is set', () => {
      const store = createStore(countAtom, { count: 10 });
      const { result, rerender } = renderHook(({
        isUpdatesNotNeed
      }) => useAtom(countAtom, isUpdatesNotNeed), {
        initialProps: { isUpdatesNotNeed: false }, 
        wrapper: props => <Provider {...props} store={store} />
      })

      act(() => { store.dispatch(increment()) })
      expect(result.current).toBe(11);

      rerender({ isUpdatesNotNeed: true })
      act(() => { store.dispatch(increment()) })
      expect(result.current).toBe(11); // actual: 12
    })

    test('unsubscribes from previous dynamic atom', () => {
      const store = createStore(countAtom, { count: 10 });
      const subscriber = jest.fn()
      const _subscribe = store.subscribe
      // @ts-ignore
      store.subscribe = ((atom) => _subscribe(atom, subscriber))

      const { rerender } = renderHook(({
        multiplier 
      }) => useAtom(map(countAtom, count => count * multiplier)), {
        initialProps: { multiplier: 2 }, 
        wrapper: props => <Provider {...props} store={store} />
      })

      act(() => { store.dispatch(increment()) })
      expect(subscriber.mock.calls.length).toBe(1)

      rerender({ multiplier: 3 })

      act(() => store.dispatch(increment()))
      expect(subscriber.mock.calls.length).toBe(1) // actual: 2
    })

PR #125

Maybe need add deps for useAtom

Improve undefined state error

const Dict = declareAtom('dict', {}, on => []);
const getItemById = (id) => map(`item ${id}`, A, a => a[id])

store.getState(getItemById(10), state => {})

Expected (example)

Error: Atom "item 10 {24}". State can't be undefined

Actual

Error: State can't be undefined

Proposal: static subscribers

export const fetchData = createAction()
fetchData.subscribe(async (store, payload) => {
  const data = await fetch(payload.url)
  store.dispatch(receiveData(data))
})
// ...
fetchData(0) // {type: '...', payload: 0, subscribers: [function]}

Rename createAtom and createActionCreator

old new
createAtom declareAtom
createActionCreator declareAction

Reason:
Atoms and actions creates only when interacting with the store. The current naming does not match the expected behavior.

Reactions for actions

We need API for describing reactions at action declaration
Example using:

const fetchUserDone = declareAction()
const fetchUser = declareAction(
  null,
  (store, payload) => fetch('/user', payload)
    .then(response => store.dispatch(fetchUserDone(response)))
)

Services (Async actions / Effects)

Example:

export type Service<T = any> = (store: Store) => (input: T) => void

Example

export const GotoPage: Service<number> = ({ getState, dispatch }) => async (page) => {
    dispatch(setPage(page))

    const api = new Api();
    const category = getState(Category);
    const res = await api.get('/products/filter', { page, category_id: category.id });

    dispatch(addProducts(res.data))
    dispatch(setProductIds(res.data.map((p: any) => p.id)))
}

Usage

const gotoPage = GotoPage(myStore);

gotoPage(10);

Action name with mapper is not optional

Current behavior

// ActionName required!
export const addTodo = declareAction('AddTodo', text => ({ id: nextTodoId++, text }))

// ActionName optional...
export const setVisibilityFilter = declareAction();

Expected

// ActionName optional
export const addTodo = declareAction(text => ({ id: nextTodoId++, text }))

// ActionName optional
export const setVisibilityFilter = declareAction();

Using snapshots

After this feature we use mutable store data and immutable atoms data
image

Reasons

We use only atoms data. store.getState(MyAtom, atomState => {}). store.getState() is used in very rare cases.

Using snapshots is a rare operation and it is not always necessary. But each time the store creates a new domain and creates a store for the sake of data immutability.

This is an extra loss in performance.

How it works

const snapshot = { domainName:  { atomName: 'data' } }
createStore(null, snapshot)

will converted to flatten view

{ domainName@atomName: 'data' }

Data Flow

store - dispatch - handleAtoms [ - onSnapshot]

Hook onSnapshot called only if store has shapshot subscribers

const increment = declareAction();
const { dispatch, onShapshot } = createStore(
  declareAtom(['my', 'counter'], 0, reduce => [
    reduce(increment, state => state + 1))
  ]
)
dispatch(incement())

// onShapshot is not called
// internal store state: { my@counter: 1 }

// Subscribe to snapshots
const usubscribe = onSnapshot(console.log)

dispatch(incement())
// internal store state: { my@counter: 2 }
// log: { my: { counter: 2 } }

Perf: lazy unsubscribe (experemental)

Simple test
const performance = require('perf_hooks').performance

let cbs = []
let start
let unsubscribe
const SIZE = 10
const noop = () => {}

cbs = Array.from({ length: SIZE }).map(() => () => {})
unsubscribe = (i) => cbs.splice(i, 1)
start = performance.now()

for (let i = 0; i < cbs.length; i++) {
    unsubscribe(cbs.length - 1)
    // for (const cb of cbs) cb()
}

console.log(performance.now() - start)


cbs = Array.from({ length: SIZE }).map(() => () => {})
unsubscribe = (i) => cbs[i] = noop

start = performance.now()
for (let i = 0; i < cbs.length; i++) {
    unsubscribe(i)
    // for (const cb of cbs) (cb !== noop) && cb()
}
console.log(performance.now() - start)

Results

Splice from right

0 - array length
1 - splice (ms)
2 - noop (ms)

10
0.07041299343109131
0.026488006114959717

100
0.08264900743961334
0.03248600661754608

1000
0.21527300775051117
0.09022299945354462

10000
1.6929330080747604
2.040920987725258

100000
13.230428010225296
2.770241007208824

Splice from left

10
0.04736599326133728

100
0.05733400583267212

1000
0.15499000251293182

10000
1.3177340030670166

100000
22322.47744600475

Splice from middle

0 - array length
1 - splice (ms)

10
0.04904499650001526

100
0.07507400214672089

1000
0.20109699666500092

10000
4.261344000697136

100000
20298.738750994205

Unsubscribe is replaces callback fn to noop and and starts collapsing the cbs list by debounce

cbs = cbs.filter(fn => fn !== noop)

Generate unit prefix by LoC

We need the option to replace (at runtime) the unique name generator (nameToId) for adding postfix based on a line of code (it is possible to define in env at V8 by error instance stack).

Tip

Example usage

// shared.ts
let nameToId = () => { /*...*/ }
export function replaceNameToId = _nameToId => nameToId = _nameToId

// user-space.ts
import { replaceNameToId } from '@reatom/core'
if (process.env.DEV) replaceNameToId(
  // https://stackoverflow.com/questions/13227489/how-can-one-get-the-file-path-of-the-caller-function-in-node-js
)

docs: naming convensions

Thinks...

Favorite
see: #26 (comment)
PascalCase for atoms (Like ClassName, becouse store using "instance" of Atom)
camelCase for actions (Like methodName)

Products = declareAtom()
Banners = declareAtom()

addProduct = declareAction()

// instances
products = useAtom(Products)
banners = useAtom(Banners)
doAddProduct = useAction(addProduct)

Postfix declaration type

productsAtom = declareAtom()
bannersAtom = declareAtom()
addProductAction = declareAction()

products = useAtom(productsAtom)
banners = useAtom(bannersAtom)

addProductAction = useAction(addProductAction)

store.dispatch(addProductAction())

Prefix $

$products = declareAtom()
$banners = declareAtom()
$addProduct = declareAction()

products = useAtom($products)
banners = useAtom($banners)
addProduct = useAction($addProduct)

store.dispatch($addProduct())

Prefix $ only for atoms

$products = declareAtom()
$banners = declareAtom()
addProduct = declareAction()

// instances
products = useAtom($products)
banners = useAtom($banners)
doAddProduct = useAction(addProduct)

store.dispatch(addProduct())

Prefix A and PascalCase only for atoms

AProducts = declareAtom()
ABanners = declareAtom()

Add packages table in main README

package version size
@reatom/core {verison} {size}
@reatom/react {verison} {size}
@reatom/redux {verison} {size}
@reatom/vue {verison} {size}

package is link to repository, version and size - shields

Rename package to reatom

The current name bad are the reports Manager. In Russian it is hard to read.
Name length in Russian flaxom !== флаксом

Proposal: reatom

Reasons:
Reusable atom (one atom for multi stores support)
Reactive atom (combine, map)
Reactive as good performance and atom as small bundle size
declare atom as declarative state manager

Name length in Russian reatom === реатом

babel-plugin for automatic names generateion

We need to implement babel-macro package (@reatom/macro) that will be set name of declaration subject to subject arguments. Simple example:

This code

const myAwesomeAtom = declareAtom()

Must to transpile to

const myAwesomeAtom = declareAtom('myAwesomeAtom')

some tips for this task

Rename reduce and reducer

Reduce

Reduce mistakenly considered equivalent reducer. It about big -> small
Need use another name. Like on or use or track.

Reducer

reducer in redux world takes actions and creates new data

But we only creates new data.
Need use another name. Like produce or producer.

Total proposal

before

declareAtom({}, reduce => [
  reduce(action, reducer)
])

after

declareAtom({}, on => [
  on(action, produce)
  // in russian: на действие произвести
])

on

declareAtom({}, on => [
  on(action, producer)
  // in russian: на действие производитель
])

track

declareAtom({}, track => [
  track(action, producer)
])

use

declareAtom({}, use => [
  use(action, producer)
])

Reactions + DI & IoC proposal

Purposes

  1. simple mocks
  2. override between stores
  3. atomic
  4. safe use in stores / services #36 (comment) (use default value)
  5. Good type inference

Implementation

version 1

let id = 0;
export const declareEnv = (value) => {
  const key = ++id;
  return Object.assign(override => ({ key, value: override }), { key, value })
}

const Network = declareEnv(fetch)
const store = createStore(RootAtom, {}, [
  Network(myCustomFetch) // Override defalut value in this store
]);

version 2

it's harder to override inside the store.
getEnv(Network(cutomFetch)) vs getEnv(Network.Provide(cutomFetch))

let id = 0;
export const declareEnv = (value) => {
  const key = ++id;
  return { 
    Provide: override => ({ key, value: override }), 
    key, 
    value
  }
}

const Network = declareEnv(fetch)
const store = createStore(RootAtom, {}, [
  Network.Provide(myCustomFetch) // Override defalut value in this store
]);

Inside createStore

const _env = {};
for(const e of env) _env[e.key] = e.value

const getEnv = ({ key, value }) => _env[key] || value

Example usage

const MyService = ({ getEnv }: Store) => payload => {
  getEnv(Network)(payload)
};

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.