Code Monkey home page Code Monkey logo

graphql-fields-list's Introduction

graphql-fields-list

Build Status codebeat badge Coverage Status Known Vulnerabilities License

Add-on to work with GraphQLResolveInfo which helps to extract requested fields list for a particular object resolver. This helps to bypass requested fields data to underlying services or data sources to extract only those minimal parts of data which was requested by end-user.

TypeScript Included!!!

Install

npm i graphql-fields-list

With JavaScript:

const { fieldsList, fieldsMap } = require('graphql-fields-list');

With TypeScript:

import { fieldsList, fieldsMap } from 'graphql-fields-list';

Simplest Usage Examples

For the following query :

{
  post { # post: [Post]
    id
    author: {
      id
      firstName
      lastName
    }
  }
}
resolve(source, args, context, info) { // resolver of Post.author, 
  console.log(fieldsList(info));       // [ 'id', 'firstName', 'lastName' ]
  console.log(fieldsMap(info));        // { id: false, firstName: false, lastName: false }
  console.log(fieldsProjection(info)); // { id: 1, firstName: 1, lastName: 1 };
}

// or, if there is high-level resolver does the work:

resolve(source, args, context, info) { // resolver of Post
  console.log(fieldsList(info));       // [ 'id', 'author' ]
  console.log(fieldsMap(info));        // { id: false, author: { id: false, firstName: false, lastName: false } }
  console.log(fieldsProjection(info)); // { id: 1, 'author.id': 1, 'author.firstName': 1, 'author.lastName': 1 };
}

Breaking Changes

Since version 2.0.0 there is breaking change in fieldsMap() function interface now it relies on the same options object as was defined for fieldsList() instead of bypassing separate arguments. You will need to change your code if fieldsMap being used.

For example, if there was a usage of path and withDirecives arguments, like:

fieldsMap(info, 'users.edges.node', false);

it should be changed to:

fieldsMap(info, { path: 'users.edges.node', withDirectives: false });

Advanced Usage

Let's assume we have the following GraphQL schema:

interface Node {
  id: ID!
}
type PageInfo {
  hasNextPage: Boolean!
  hasPreviousPage: Boolean!
  startCursor: String
  endCursor: String
}
type Query {
  node(id: ID!): Node
  viewer: Viewer
}
type User implements Node {
  id: ID!
  firstName: String
  lastName: String
  phoneNumber: String
  email: String
}
type UserConnection {
  pageInfo: PageInfo!
  edges: [UserEdge]
}
type UserEdge {
  node: User
  cursor: String!
}
type Viewer {
  users(
    after: String,
    first: Int,
    before: String,
    last: Int
  ): UserConnection
}

And using the query:

query UserNames query {
    viewer {
        users {
            pageInfo {
                startCursor
                endCursor
            }
            edges {
                cursor
                node {
                    id
                    firstName
                    lastName
                }
            }
        }
    }
}

Our goal is to extract and return ONLY id, firstName and lastName fields from the user data. To achieve that we would need to bypass required fields information to underlying service or database, for example, let's assume we want to select that kind of data from mongodb.

In this case we will need to implement a resolver which will fetch only requested fields from our database like this:

const { connectionFromArray } from 'graphql-relay';
const { fieldsList } = require('graphql-fields-list');
// ... assuming we implement resolver on 'viewer' node:
async resolve(src, args, context, info) {
    // we want to get a clue which user data fields are requested, so:
    const fields = fieldsList(info, { path: 'users.edges.node' });
    // RESULT: fields = ['id', 'firstName', 'lastName']
    // Now we can fetch from mongodb only required part of the data
    // instead of fetching entire user data document (assuming
    // userDb is initialized model of mongoose):
    const users = await userDb.find().select(fields.join(' ')).exec();
    return { viewer: { users: connectionFromArray(users, args) } };
}

In the example above we assume our user model in database contains the same field names as defined by a graphql request. BTW, in a real world, there could be a need to re-map field names from a graphql query to some different names stored in a database. For example, we would need to use automatically created _id field in mongodb as id field in a graphql request. This can be easily achieved specifying a transform map option:

const fields = fieldsList(info, {
    path: 'users.edges.node',
    transform: { id: '_id' },
});
// RESULT: fields = ['_id', 'firstName', 'lastName']

By the way, in some particular cases there could be a need to retrieve a whole fields name hierarchy from a graphql request. This could be achieved using fieldsMap function:

const { fieldsMap } = require('graphql-fields-list');
// ... inside the resolver as we did above:
const map = fieldsMap(info);
/*
RESULT:
map = {
  users: {
    pageInfo: {
      startCursor: false,
      endCursor: false
    },
    edges: {
      cursor: false,
      node: {
        id: false,
        firstName: false,
        lastName: false
      }
    }
  }
}
*/

Function fieldsMap also accepts same optional arguments as fieldsList:

const map = fieldsMap(info, { path: 'users.pageInfo' });
/*
RESULT:
map = {
  startCursor: false,
  endCursor: false
}
*/

For leafs of the fields tree it will return false value, which is usable when you need to detect that the end of a tree branch is reached during traversal.

Both fieldsMap and fieldsList work as expected with graphql query fragmentation, so can be safely used within any possible queries.

Since version 1.1.0 it also supports @skip and @include directives in queries. This is enabled by default. If you need to disable directives support for some reason it may be turned off using withDirectives = false option correspondingly:

fieldsList(info, { withDirectives: false });
fieldsMap(info, { withDirectives: false });

Please, note, currently fieldsMap accepts transform option argument, but DOES NOT USE IT for transformations. This function will return always the map of the actual query fields. All transformations accepted only by fieldsList and fieldsProjection functions!

Since version 2.0.0

In some cases it could be useful to operate with fields projections instead of mapping object. For example, projection could be used with MongoDB queries. To extract fields projection object from GraphQLResoleInfo you can utilize fieldsProjection() function:

const projection = fieldsProjection(info, { path: 'users.edges.node' });
/*
RESULT:
projection = {
  id: 1,
  firstName: 1,
  lastName: 1,
  phoneNumber: 1,
  email: 1,
  address: 1,
}
*/

Projections use dot-notation for a fields and always returned as a flat object:

const projection = fieldsProjection(info, { path: 'users.edges' });
/*
RESULT:
projection = {
  'node.id': 1,
  'node.firstName': 1,
  'node.lastName': 1,
  'node.phoneNumber': 1,
  'node.email': 1,
  'node.address': 1,
}
*/

Projections also accepts keepParentField option, which should return the parents included in the object not only the leaves.

const projection = fieldsProjection(info, {
    path: 'users',
    keepParentField: true,
});
/*
RESULT:
projection = {
  'edges': 1,                 // parent node
  'edges.node': 1,            // parent node
  'pageInfo': 1,              // parent node
  'pageInfo.startCursor': 1,
  'pageInfo.endCursor': 1,
  'pageInfo.hasNextPage': 1,
  'edges.node.id': 1,
  'edges.node.firstName': 1,
  'edges.node.lastName': 1,
  'edges.node.phoneNumber': 1,
  'edges.node.email': 1,
  'edges.node.address': 1,
}
*/

Projections also accepts transform option, which should be a mapping object between projections paths:

const projection = fieldsProjection(info, {
    path: 'users.edges',
    transform: {
        'node.id': 'node._id',
        'node.firstName': 'node.given_name',
        'node.lastName': 'node.family_name',
    },
});
/*
RESULT:
projection = {
  'node._id': 1,
  'node.given_name': 1,
  'node.family_name': 1,
  'node.phoneNumber': 1,
  'node.email': 1,
  'node.address': 1,
}
*/

Since version 2.1.0

It supports skip option to filter output of fieldsList(), fieldsMap() and fieldsProjection() functions.

See motivation

Skip option accepts an array of field projections to skip. It allows usage of wildcard symbol * within field names. Please, note, that skip occurs before transformations, so it should reflect original field names, transformations would be applied after skip is done.

Typical usage as:

const map = fieldsMap(info, { skip: [
    'users.pageInfo.*',
    'users.edges.node.email',
    'users.edges.node.address',
    'users.edges.node.*Name',
]});
/*
RESULT:
map = {
  users: {
    edges: {
      node: {
        id: false,
        phoneNumber: false,
      },
    },
  },
}
*/
const projection = fieldsProjection(info, {
   skip: [
       'users.pageInfo.*',
       'users.edges.node.email',
       'users.edges.node.address',
       'users.edges.node.*Name',
   ],
   transform: {
       'users.edges.node.id': 'users.edges.node._id',
   },
});
/*
RESULT:
projection = {
 'users.edges.node._id': 1,
 'users.edges.node.phoneNumber': 1,
};
*/

Frequent Questions and Answers

Q1. Can we exclude __typename from fieldsList?

const some = fieldsList(info)
// some output
[ 'id', 'name', '__typename' ]

A1. Usually this problem occurs with using Apollo clients. Sure, you can overcome this with use of skip option:

const some = fieldsList(info, { skip: ['__*'] })

This is exactly the case, why skip option is created for.

License

ISC Licence

graphql-fields-list's People

Contributors

akxe avatar dependabot[bot] avatar dlannoye avatar mikhus avatar mohamedgamalabugalala 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

graphql-fields-list's Issues

How to handle computed values?

Suppose you have a User type that has firstName and lastName but the schema also has a fullName that concatenates the first two. If you don't request firstName and lastName, fullName would be null. How can I get around this?

# schema.graphql
type User {
  firstName: String!
  lastName: String!
}
// resolvers.js
{
  fullName: {
    resolve(parent) {
      return `${parent.firstName} ${parent.lastName}`
    }
  }
}

Typing issue since graphql v15 update

Since upgrading to graphql 15.0.0 the following TS compile error is reported:
Argument of type 'import("/Users/markcrawshaw/Code/metamap-server/node_modules/graphql/type/definition").GraphQLResolveInfo' is not assignable to parameter of type 'import("/Users/markcrawshaw/Code/metamap-server/node_modules/@types/graphql-type-json/node_modules/graphql/type/definition").GraphQLResolveInfo'.

I believe this could be resolved by upgrading the graphql dependency in this package.

Great stuff

Just wanted to thank you for this cool project, keep up the great work ☺

Decommissioned @types/graphql is being used by this package

The graphql package now includes types. This can cause the following error:
node_modules/graphql-fields-list/index.ts:21:5 - error TS2614: Module '"graphql"' has no exported member 'SelectionNode'. Did you mean to use 'import SelectionNode from "graphql"' instead

Just a matter of updating index.ts from:

import {
    ArgumentNode,
    DirectiveNode,
    SelectionNode,
    FragmentDefinitionNode,
    GraphQLResolveInfo,
    FieldNode,
} from 'graphql';

to

import {
    GraphQLResolveInfo,
} from 'graphql/type';
import {
    ArgumentNode,
    DirectiveNode,
    SelectionNode,
    FragmentDefinitionNode,
    FieldNode,
} from 'graphql/language';

"Cannot read property 'value' of undefined" on InlineFragment node

At L209, the code accessing node.name.value throws as node.name is undefined. This occurs when a node is an inline fragment node

function traverse(
nodes: ReadonlyArray<FieldNode>,
root: any,
opts: TraverseOptions,
) {
for (let node of nodes) {
if (opts.withVars && !verifyDirectives(node.directives, opts.vars)) {
continue;
}
const name = node.name.value;
if (opts.fragments[name]) {
traverse(getNodes(opts.fragments[name]), root, opts);
continue;
}
const nodes = getNodes(node);
root[name] = root[name] || (nodes.length ? {} : false);
nodes.length && traverse(nodes, root[name], opts);
}
return root;
}

The object looks something like this:

node {
    kind: "InlineFragment",
    typeCondition: {/*...*/},
    loc: {/*...*/},
    directives: [],
    selectionSet: {/*...*/}
}

A quick workaround that worked for me was to check for the name object on the node, and to continue if it's missing:

if (!node.name || (opts.withVars && !verifyDirectives(node.directives, opts.vars))) {
   continue;
}

but that then ignores the fields in an Inline Fragment.

Another solution is to add this block to inline the inline fragment's node on the current root

if (node.kind === "InlineFragment") {
    const nodes = getNodes(node);
    nodes.length && traverse(nodes, root, opts);
    continue;
}

Feature Request [fieldsProjection]

Thanks for the library ❤️


In this example fieldsProjection will result { id: 1, 'author.id': 1, 'author.firstName': 1, 'author.lastName': 1 }

But I want to set an option like fieldsProjection(info, { keepParentField: true }) to make it return this { id: 1, 'author': 1, 'author.id': 1, 'author.firstName': 1, 'author.lastName': 1 }

{
  post { # post: [Post]
    id
    author: {
      id
      firstName
      lastName
    }
  }
}

resolve(source, args, context, info) { // resolver of Post
  console.log(fieldsProjection(info)); // { id: 1, 'author.id': 1, 'author.firstName': 1, 'author.lastName': 1 };
}

This will be very helpful in case you have a document like this in DB


@ObjectType()
export default class Member implements IMember {
  @prop({ ref: User, required: true, index: true })
  User: Ref<User>

  @Field(() => TeamMemberRole)
  @prop({ type: String, required: true, default: TeamMemberRole.MEMBER })
  Role: TeamMemberRole

  @Field()
  @prop({
    required: true,
    default: () => new Date().getTime()
  })
  AddedOn: number
}

@ObjectType()
export default class Company extends BaseModel implements ICompany {
  @Field()
  @prop({ required: true })
  Name: string

  @Field(() => Member)
  @prop({ items: Member, _id: false, default: [] })
  Members: Member[]
}

And you have a field resolver on Member.User and you want to do a projection for this user, in the Company Resolver you will have Member.User.ID but not Member.User so in the Member.User resolver the User value will be null.
but if you had Member.User.ID and Member.User this will fix the problem.

For now, I do a workaround to get the result I want

   
    const projection = fieldsProjection(info)

    AppendBaseKeyWithRegixCheckForChilds(projection, 'Members.User')

_____________________________________________________
const AppendBaseKeyWithRegixCheckForChilds = (
  projection: {},
  baseKey: string
): void => {
  Object.keys(projection).forEach(k => {
    if (RegExp(`${baseKey}.`).test(k)) {
      delete projection[k]
      projection[baseKey] = 1
    }
  })
}


Maybe my use case is too personalized and I don't mind if it is.

Thanks again for your work ❤️

Type 'undefined' cannot be used as an index type.

I'm getting 8 errors when loading this package:

node_modules/graphql-fields-list/index.ts:292:27 - error TS2538: Type 'undefined' cannot be used as an index type.

292             if (!propTree[prop]) {
                              ~~~~

node_modules/graphql-fields-list/index.ts:293:26 - error TS2538: Type 'undefined' cannot be used as an index type.

293                 propTree[prop] = i === s - 1 || all ? true : {};
                             ~~~~

node_modules/graphql-fields-list/index.ts:297:33 - error TS2538: Type 'undefined' cannot be used as an index type.

297             propTree = propTree[prop] as SkipTree;
                                    ~~~~

node_modules/graphql-fields-list/index.ts:316:9 - error TS2322: Type 'SkipValue | undefined' is not assignable to type 'SkipValue'.
  Type 'undefined' is not assignable to type 'SkipValue'.

316         return (skip as SkipTree)[node];
            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

node_modules/graphql-fields-list/index.ts:327:13 - error TS2322: Type 'SkipValue | undefined' is not assignable to type 'SkipValue'.

327             nodeTree = (skip as SkipTree)[pattern];
                ~~~~~~~~

node_modules/graphql-fields-list/index.ts:371:31 - error TS2345: Argument of type 'FragmentDefinitionNode | undefined' is not assignable to parameter of type 'FragmentDefinitionNode | SelectionNode'.
  Type 'undefined' is not assignable to type 'FragmentDefinitionNode | SelectionNode'.

371             traverse(getNodes(opts.fragments[name]), root, opts, skip);
                                  ~~~~~~~~~~~~~~~~~~~~

node_modules/graphql-fields-list/index.ts:386:17 - error TS2345: Argument of type 'MapResultKey | undefined' is not assignable to parameter of type 'MapResultKey'.
  Type 'undefined' is not assignable to type 'MapResultKey'.

386                 (root as MapResult)[name],
                    ~~~~~~~~~~~~~~~~~~~~~~~~~

node_modules/graphql-fields-list/index.ts:587:17 - error TS2322: Type 'string | undefined' is not assignable to type 'string'.
  Type 'undefined' is not assignable to type 'string'.

587                 dotName = transform[dotName];

This package seemed promising, but I'm not sure how I can use it if I can't import it without these errors.
Might be relevant to show my config:

{
   "$schema": "https://json.schemastore.org/tsconfig", // For code completion and type checking inside this file.
   "include": ["src/**/*", "test/**/*"],
   "exclude": ["node_modules", "dist"],
 
   "compilerOptions": {
     "target": "ES2017", // Specify ECMAScript target version: 'es3' (default), 'es5', 'es2015', 'es2016', 'es2017','es2018' or 'esnext'.
     "module": "commonjs", // Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'.
     "lib": ["DOM", "DOM.Iterable", "ESNext"], // Specify library files to be included in the compilation.
     "outDir": "./dist", // Redirect output structure to the directory.
 
     "strict": true, // Enable all strict type-checking options.
     "useUnknownInCatchVariables": true, // Type catch variables as 'unknown' by default.
     "alwaysStrict": true, // Parse in strict mode and emit "use strict" for each source file.
     "exactOptionalPropertyTypes": false, // Disallow assigning 'undefined' to optional properties.
     "noUncheckedIndexedAccess": true, // Add 'undefined' to the type of index signatures.
 
     "useDefineForClassFields": false,
     "experimentalDecorators": true, // Enables experimental support for ES7 decorators.
 
     "forceConsistentCasingInFileNames": true, // Disallow inconsistently-cased references to the same file.
     "noUnusedLocals": true, // Report errors on unused locals.
     "noUnusedParameters": true, // Report errors on unused parameters.
     "noImplicitOverride": true, // Force explicit 'override' keyword.
     "noImplicitReturns": true, // Report error when not all code paths in function return a value.
     "noFallthroughCasesInSwitch": true, // Report errors for fallthrough cases in switch statement.
     "noPropertyAccessFromIndexSignature": true, // Force consistency in dot operator versus indexing.
 
     "moduleResolution": "node", // Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6).
     "baseUrl": "./", // Base directory to resolve non-absolute module names.
     "types": ["validator", "jest", "node"], // Type declaration files to be included in compilation.
     "esModuleInterop": true, // Emit '__importStar' and '__importDefault' helpers for runtime babel ecosystem compatibility and enable '--allowSyntheticDefaultImports' for typesystem compatibility.
     "resolveJsonModule": true, // Include modules imported with '.json' extension.

     "incremental": true, // Enable incremental compilation by reading/writing information from prior compilations to a file on disk.

     "emitDecoratorMetadata": true, // Enables experimental support for emitting type metadata for decorators.
 
     "sourceMap": true // Generates corresponding '.map' file.
   },
 }

Update the readme to show the most basic usage first

Hi,
First of all, thanks for creating and publishing the lib.
Since the readme is the main documentation for it, you should update it a bit.
Here is what I recommand:

  • Put the "Breaking changes" section lower.
  • Put the simplest usage example in the first position

For the following query :

{
  post { # post: [Post]
    id
    author: {
      id
      firstName
      lastName
    }
  }
}
resolve(source, args, context, info) { // resolver of Post.author, 
  console.log(fieldsList(info)); // ['id', 'firstName', 'lastName']
}

For a newcomer to the librairy, this was my use case. I spent ~30 minutes reading the whole readme with all the use cases just to wonder if I could find a way not to use the path option, to just get the first children fields. Finally, I gave up, installed the repo and tried myself the snippet above. I still don't know if it's safe btw, because it's not documented.

Export interface GraphQLResolveInfo

I think interface GraphQLResolveInfo should be exported for external usage as we sometimes need to specify type for the related variable.

Update Graphql to version 16

Hi, thanks for this library! The keys of the projections work perfectly for MikroORM queries.

I have a type error for GraphQLResolveInfo since I'm using version 16. I don't think any changes are required to support it as I've just added //@ts-ignore and it seems to be working fine.

__typename in fieldsList

Can we exclude '__typename' from fieldsList?

const some = fieldsList(info)
// some output
[ 'id', 'name', '__typename' ]

Question about fieldsMap Function

Why does it return false in case the field is queried for? Wouldn't it be more logical to set the value to true in case the field is queried?

fieldList Nested field handling.

I've been trying to get fieldList to work with nested fields, i.e.

query{
   user(id:123){
      organization{
         address{
            streetName
         }
      }
   }
}
const fields = fieldsList(info);

The above returns ['organization'] but skips address. Is there a way to return ['organization.address']? This would align perfectly with TypeORM's relation argument.

about field __typename

Hi

With apollo cache, a field __typename is always added.

I don't know if it should be the responsibility of this package to remove it.
Maybe a note in your README could help people to understand this is a "normal" behaviour.

Thanks

Inversion of skip?

Hi, thank you for the great library again.

I would like to propose a new API only, which is the inversion of skip.

The use case: We have a database table and GraphQL schema has "virtual" columns.

GraphQL:

type User {
  id: ID!
  firstName: String
  lastName: String
  fullName: String
}

Database:

- users:
  - id
  - firstName
  - lastName

Resolver:

const UserResolver = {
  lastName(root: User) {
    return `${user.firstName} ${user.lastName}`
  }
}

In this case, it would be really helpful if we have only

fieldsToList(info, {
  only: ['id', 'firstName', 'lastName'],
})

If you are okay, I'll create a PR for this.

Thanks

Comparison with prior art

Since this package was created well after similar packages, it would be nice to mention in the README the reasons for authoring it, and to guide users in choosing the right package for their situation.

When I'm comparing solutions, the ones that mention other projects and how they differentiate, win.

That's because those authors show awareness of existing alternatives, and likely have learned from their mistakes. Also as a user, I can better decide which tool is best for the job, and I appreciate the author's thoughtfulness in comparing the solutions in the space.

Why fieldsMap returns false ?

Hello,
Why did you choose to return false instead of true for the fieldsMap function ?

From the user POV it's a bit strange as we are looking for the fields requested by the query and they are mapped to a false value when, as a user of the library, I was intuitively expecting a true value.

Kudos and a question

First of all thanks so much for this library. It kinda helped me convince my teammates that graphql is the way forward.

And the question. Let's say I have a query like so:

  profiles {
    id
    users {
      name
    }
  }

Here I want 'id' to be "sent" to profiles as a projection but I don't really care about 'users' since that's a separate collection and preferably sent along to another place which could do the same projection but only for users. Is there a way to "skip" keys or something. I know I can just delete the keys I don't want but....

Ideally I would like it to be something like:
['id'] --- profiles projections
['name'] --- users projections

Come to think of it now that I've worked around the issue is that maybe it could support a white list instead. "only map these"...optional of course.
And maybe a way to configure always excluded keys...like "__typename" as an example.

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.