GithubHelp home page GithubHelp logo

apollographql / apollo-link-persisted-queries Goto Github PK

View Code? Open in Web Editor NEW
308.0 36.0 33.0 5.5 MB

Persisted Query support with Apollo Link

License: MIT License

TypeScript 98.48% JavaScript 1.52%

apollo-link-persisted-queries's Introduction

Automatic Persisted Queries

NOTE: This project has been merged into the @apollo/client core, which means it is now being maintained in the https://github.com/apollographql/apollo-client repo. Refer to the updated docs for usage instructions.

Problem to solve

Unlike REST APIs that use a fixed URL to load data, GraphQL provides a rich query language that can be used to express the shape of application data requirements. This is a marvellous advancement in technology, but it comes at a cost: GraphQL query strings are often much longer than REST URLS โ€” in some cases by many kilobytes.

In practice we've seen GraphQL query sizes ranging well above 10 KB just for the query text. This is actually significant overhead when compared with a simple URL of 50-100 characters. When paired with the fact that the uplink speed from the client is typically the most bandwidth-constrained part of the chain, large queries can become bottlenecks for client performance.

Automatic Persisted Queries solves this problem by sending a generated ID instead of the query text as the request.

For more information about this solution, read this article announcing Automatic Persisted Queries.

How it works

  1. When the client makes a query, it will optimistically send a short (64-byte) cryptographic hash instead of the full query text.
  2. If the backend recognizes the hash, it will retrieve the full text of the query and execute it.
  3. If the backend doesn't recognize the hash, it will ask the client to send the hash and the query text, so it can store them mapped together for future lookups. During this request, the backend will also fulfill the data request.

This library is a client implementation for use with Apollo Client by using custom Apollo Link.

Installation

npm install apollo-link-persisted-queries --save

Usage

The persisted query link requires using the http-link. The easiest way to use them together to to concat them into a single link.

import { createPersistedQueryLink } from "apollo-link-persisted-queries";
import { createHttpLink } from "apollo-link-http";
import { InMemoryCache } from "apollo-cache-inmemory";
import ApolloClient from "apollo-client";


// use this with Apollo Client
const link = createPersistedQueryLink().concat(createHttpLink({ uri: "/graphql" }));
const client = new ApolloClient({
  cache: new InMemoryCache(),
  link: link,
});

Thats it! Now your client will start sending query signatures instead of the full text resulting in improved network performance!

Options

The createPersistedQueryLink function takes an optional object with configuration. Currently the only supported configutations are a key called generateHash which receives the query and returns the hash, a function to conditionally disabled sending persisted queries on error

  • generateHash: a function that takes the query document and returns the hash. If not provided, generateHash defaults to a fast implementation of sha256 + hex digest.
  • useGETForHashedQueries: set to true to use the HTTP GET method when sending the hashed version of queries (but not for mutations). GET requests require apollo-link-http 1.4.0 or newer, and are not compatible with apollo-link-batch-http.

If you want to use GET for non-mutation queries whether or not they are hashed, pass useGETForQueries: true option to createHttpLink from apollo-link-http instead. If you want to use GET for all requests, pass fetchOptions: {method: 'GET'} to createHttpLink.

  • disable: a function which takes an ErrorResponse (see below) and returns a boolean to disable any future persited queries for that session. This defaults to disabling on PersistedQueryNotSupported or a 400 or 500 http error

ErrorResponse The argument that the optional disable function is given is an object with the following keys:

  • operation: The Operation that errored (contains query, variables, operationName, and context)
  • response: The Execution of the response (contains data and errors as well extensions if sent from the server)
  • graphQLErrors: An array of errors from the GraphQL endpoint
  • networkError: any error during the link execution or server response

Note: networkError is the value from the downlink's error callback. In most cases, graphQLErrors is the errors field of the result from the last next call. A networkError can contain additional fields, such as a GraphQL object in the case of a failing HTTP status code from apollo-link-http. In this situation, graphQLErrors is an alias for networkError.result.errors if the property exists.

Apollo Engine

Apollo Engine supports receiving and fulfulling Automatic Persisted Queries. Simply adding this link into your client app will improve your network response times when using Apollo Engine.

Protocol

Automatic Persisted Queries are made up of three parts: the query signature, error responses, and the negotiaion protocol.

Query Signature The query signature for Automatic Persisted Queries is sent along the extensions field of a request from the client. This is a transport independent way to send extra information along with the operation.

{
  operationName: 'MyQuery',
  variables: null,
  extensions: {
    persistedQuery: {
      version: 1,
      sha256Hash: hashOfQuery
    }
  }
}

When sending an Automatic Persisted Query, the client omits the query field normally present, and instead sends an extension field with a persistedQuery object as shown above. The hash must be a sha256 hash of the query string.

If the client needs to register the hash, the query signature will be the same but include the full query text like so:

{
  operationName: 'MyQuery',
  variables: null,
  query: `query MyQuery { id }`,
  extensions: {
    persistedQuery: {
      version: 1,
      sha256Hash: hashOfQuery
    }
  }
}

This should only happen once across all clients when a new query is introduced into your application.

Error Responses When the initial query signature is received by a backend, if it is unable to find the hash previously stored, it must send back the following response signature with a 200 OK status code:

{
  errors: [
    { message: 'PersistedQueryNotFound' }
  ]
}

If the backend doesn't support Automatic Persisted Queries, or does not want to support it for that particular client, it can send back the following which will tell the client to stop trying to send hashes all together:

{
  errors: [
    { message: 'PersistedQueryNotSupported' }
  ]
}

Negotiation Protocol In order to support Automatic Persisted Queries, the client and server must follow the negotiaion steps as outlined here:

Happy Path

  1. Client sends query signature with no query field
  2. Server looks up query based on hash, if found, it resolves the data
  3. Client receives data and completes request

Missing hash path

  1. Client sends query signature with no query field
  2. Server looks up query based on hash, none is found
  3. Server responds with NotFound error response
  4. Client sends both hash and query string to Server
  5. Server fulfills response and saves query string + hash for future lookup
  6. Client receives data and completes request

Build time generation

If you want to avoid hashing in the browser, you can use a build script to include the hash as part of the request. Then you pass a function to retrieve that hash when the operation is run. This works well with projects like this which uses webpack to generate the hashes at build time.

If you use the above loader, you can pass { generateHash: ({ documentId }) => documentId } to the createPersistedQueryLink call.

apollo-link-persisted-queries's People

Contributors

benjamn avatar daniel15 avatar dependabot[bot] avatar glasser avatar hwillson avatar j4chou avatar jason-cooke avatar kachkaev avatar leoasis avatar mkaraula avatar primigenus avatar timbotnik 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  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

apollo-link-persisted-queries's Issues

Option to only send APQ on queries not mutations

When we call APQ

var link = createPersistedQueryLink().concat(createHttpLink({ uri: "/graphql" }))

It sends persisted queries even on mutations! We need something like useGETForHashedQueries but for this use case. Maybe name it useAPQOnlyForQueries.

No reason to call APQ on a mutation.

PersistedQueryNotSupported errors will stop all other extensions

The persisted-queries link seems to be affecting sending of other extensions.

Particularly this line. If the server has responded that PQs are not supported the link is setting the includeExtensions in http context to false which means that all extensions are no longer sent. This causes an issue with the new clientInfo extension from apollo-server. The work around is to use headers to send it and write a custom extractor on the server side, but ideally AL-PQs should be a good Link citizen and not stop other extensions.

My suggestion is perhaps instead of killing extensions the API should just strip the extension from the operation before sending or possibly there could be an API change to the includeExtensions field such that it could take either a boolean or an array of strings or an object of string -> boolean key/values, where the latter type we you can simply do:

includeExtensions: {persistedQueries: false}

CORS GETs issue a PREFLIGHT OPTIONS call. Can we swtich the content-type or not?

I'm using this amazing package for persisted queries and { useGETForHashedQueries: true }.

Everything works good but I think something is happening if I use ApolloServer with CORS (which I hate and in the future I think I will avoid them with something like a proxy or what will be).

Now the problem is I see some GETs with PREFLIGHT OPTIONS calls.

I think this is happening because of content-type: application/json in Request Headers.

Can we switch this Content-Type to ex. text/plain.

I'm newbie, I don't know if there is a security concern changing the Content-Type.

image

Need help with persisted queries, is anyone available for hire?

Hello.

I hope it is ok to post this here...

I need some help communicating with a server that uses apollo persisted queries. I'm hoping to hire someone to help me get set up and communicating with the server, I expect it to take a few hours to a few days to solve. If you are an expert with apollo and have a little bit of time to spare, I would love to hire you. Please contact me at [email protected].

Thank you!
Tom

Struggling to configure the client

Hi! I'm trying to configure my client to send persisted queries, but my setup is a little different from examples. My Setup looks like that:

import { ApolloClient } from 'apollo-client';
import {
  InMemoryCache,
  IntrospectionFragmentMatcher,
} from 'apollo-cache-inmemory';
import { HttpLink } from 'apollo-link-http';
import { onError } from 'apollo-link-error';
import { withClientState } from 'apollo-link-state';
import { ApolloLink, Observable } from 'apollo-link';
import introspectionQueryResultData from './schema.json';
import { createPersistedQueryLink } from 'apollo-link-persisted-queries';
// import { persistCache } from 'apollo-cache-persist';
import { clinicianUnmount } from 'src/actions/account';
import { getItemFromStorage } from 'src/helpers/localStorage';

function configureApollo(dispatch) {
  const cache = new InMemoryCache({
    fragmentMatcher: new IntrospectionFragmentMatcher({
      introspectionQueryResultData,
    }),
    dataIdFromObject: o => o.id || null,
  });

  // persistCache({
  //   cache,
  //   storage: window.localStorage,
  //   key: 'apollo'
  // });

  const request = async operation => {
    const token = getItemFromStorage('jwtToken');
    operation.setContext({
      headers: {
        authorization: token ? `Bearer ${token}` : null,
      },
    });
  };

  const requestLink = new ApolloLink(
    (operation, forward) =>
      new Observable(observer => {
        let handle;
        Promise.resolve(operation)
          .then(oper => request(oper))
          .then(() => {
            handle = forward(operation).subscribe({
              next: observer.next.bind(observer),
              error: observer.error.bind(observer),
              complete: observer.complete.bind(observer),
            });
          })
          .catch(observer.error.bind(observer));

        return () => {
          if (handle) handle.unsubscribe();
        };
      }),
  );

  return new ApolloClient({
    link: ApolloLink.from([
      onError(({ graphQLErrors, networkError }) => {
        if (graphQLErrors) {
          // TODO: sendToLoggingService(graphQLErrors);
          console.log(graphQLErrors);
        }
        if (networkError) {
          dispatch(clinicianUnmount());
        }
      }),
      requestLink,
      withClientState({
        cache,
      }),
      new HttpLink({
        uri: process.env.REACT_APP_API,
        credentials: 'include',
      }),
    ]),
    cache,
  });
}

export default configureApollo;

what should I concat with createPersistedQueryLink()? Is it new HttpLink?

Thanks!

The query hash that is generated by apollo-android does not match the query hash that is generated by apollo-link-persisted-queries

Problem Description

The query hash that is generated by apollo-android does not match the query hash that is generated by apollo-link-persisted-queries.

Investigation

  1. I took a query and the query hash generated by apollo-android and found that query hash was different from the query hash that apollo-link-persisted-queries generates for the same query.

  2. I wrote a command-line utility that performed the same steps as apollo-link-persisted-queries:

    a. Read a query string.

    b. Perform a graphql parse on the output of a.

    c. Perform a graphql print on the output of b.

    d. Compute the SHA256 hash on the output of c.

  3. I found that my hash matched the one that apollo-link-persisted-queries generates.

  4. I looked at the code and saw that https://github.com/apollographql/apollo-android/blob/master/apollo-compiler/src/main/kotlin/com/apollographql/apollo/compiler/OperationTypeSpecBuilder.kt#L58 filters out newlines while https://github.com/apollographql/apollo-link-persisted-queries/blob/master/src/index.ts#L28-L31 does not.

  5. I added newline stripping to my utility in 2.

  6. I found that my hash now matched the one generated by apollo-android.

Proposed Solutions

There are two possible, mutually-exclusive solutions:

Either make the change inside apollo-android

Change https://github.com/apollographql/apollo-android/blob/master/apollo-compiler/src/main/kotlin/com/apollographql/apollo/compiler/OperationTypeSpecBuilder.kt#L58 to be:

        .initializer("\$S", operation.sourceWithFragments?.sha256())

Or make the change inside apollo-link-persisted-queries

Change https://github.com/apollographql/apollo-link-persisted-queries/blob/master/src/index.ts#L30 to be:

    .update(print(query).replace(/\n/g, ''))

Related PR

I raised the same issue in apollo-android here: apollographql/apollo-kotlin#1283 .

ApolloLink is missing onError and setOnError properties

Currently setting up a new project with:

  • @apollo/client ^3.0.0-beta.44
  • apollo-link-http ^1.5.17
  • apollo-link-persisted-queries ^0.2.2

When setting up APQ, I'm getting this typescript error on missing onError and setOnError properties:

I'm not sure if the issue is on my side (bad configuration) or on the library needing some update to work with apollo client 3.
How do you suggest I resolve this issue?

Support for [email protected].

npm audit fix shows that graphql version 15.3.0 is not supported

npm WARN [email protected] requires a peer of graphql@^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 but none is installed. You must install peer dependencies yourself.

Possible memory leak in query cache

Hello guys !

I'm currently developing a GraphQL hub that serves as a proxy to many other GraphQL servers. These servers accept persisted queries and I thought it would be really nice to use this package for sending them hashed queries. However, after some time in production, my GraphQL hub started running out of memory. After much investigation, our team found out that this package was the evil of our memory leak.

The leak occurs because this package tries to optimize the query computation by using a Map as a cache, and this map grows indefinitely. leak

My fix proposal is to accept a cache implementation with the interface defined by apollo-server-caching so we can use the cache implementation that best fits our needs (with Map as the default value for this cache)

What do you guys think ?

Thank you !

How to precompute query hashes, store it, and use it to whitelist persisted queries.

I want disallow arbitrary queries from client to prevent abusive queries.
How do I extract and compute query hashes and save it as a query white list file and use load it on apollo server to whitelist them?
I've tried apollo-cli apollo queries:extract, but its hashed value does not match the one created by apollo-link-persisted-queries.

  • has-reproduction
  • feature
  • blocking
  • good first issue

Possible to combine with apollo-link-batch-http?

Is there a way to combine automatic persisted queries with a batched link? Guessing not yet, at least it didn't work if I replaced the concat to the HTTP link with a batched instance, but thought I'd ask.

Great idea, really reduces bandwidth :)

Edit: to be more detailed, this is how I tried to combine them:

import { BatchHttpLink } from 'apollo-link-batch-http';
import { createPersistedQueryLink } from 'apollo-link-persisted-queries';

const http = new BatchHttpLink({ uri: httpEndpointUrl });
const persistHttp = createPersistedQueryLink().concat(http);

It does send the queries, but the negotiation of hashes doesn't happen so there is no bandwidth savings. The batch elements have both the complete query text as well as the hash extension added to them.

Question: use only GET but avoid send full query and combine GET and POST

Hello.

  1. It's possible use only hashed queries, without send full query (even of client side error is triggered)? I want prepare and store all hashed query on server myself, without use 'automatic query resend'.

  2. I use useGETForHashedQueries: true and if persisted query is not stored on server side client try send full query (which is big) through GET (which create error fastcgi request record is too big on NGINX). It's possible configure link to send full query through POST and hashed query through GET?

Thanks.

Better errors if backend is misconfigured

If you use this link against an apollo-server backend but don't enable Engine or another PQ-supporting backend, you can end up with something like:

Error: Must provide document
 at invariant (/Users/glasser/Projects/Meteor/galaxy-server/node_modules/graphql/jsutils/invariant.js:18:11)
 at Object.validate (/Users/glasser/Projects/Meteor/galaxy-server/node_modules/graphql/validation/validate.js:58:34)
 at doRunQuery (/Users/glasser/Projects/Meteor/galaxy-server/node_modules/apollo-server-core/dist/runQuery.js:88:38)

It would be user-friendly if apollo-server-core recognized the persisted query extension enough to return the special PersistedQueryNotSupported rather than throwing with a broken invariant.

Elaboration on usage of useGETForHashedQueries with apollo-link-batch-http

There is a note in the readme

useGETForHashedQueries: set to true to use the HTTP GET method when sending the hashed version of queries (but not for mutations). GET requests require apollo-link-http 1.4.0 or newer, and are not compatible with apollo-link-batch-http.

Could some explain what exactly the issue is with this option and the batching lib?

This package breaks refetchQueries in Mutations

When I use this package, Mutations' refetchQueries doesn't work.

I don't even see the refetchQueries in Chromes's network tab.

It was hard to find because when in dev mode, the persisted queries are disabled and everything work.
As soon as we deployed to production, refetchQueries would not trigger at all.

Removing the package fixed the issue right away.

Help with Sample Apollo Client code to test APQ (Automated Persistent Queries)

I was trying to test APQ with a server written in haskell. The following is the sample Apollo client code, I wrote to test it:

const { createPersistedQueryLink } = require("apollo-link-persisted-queries")
const { createHttpLink } = require("apollo-link-http")
const { InMemoryCache } = require("apollo-cache-inmemory")
const { ApolloClient } = require("apollo-client")
const { gql } = require('apollo-server');
const { ApolloLink } = require("apollo-link")
const fetch  = require("node-fetch")

const link = ApolloLink.from([
  createPersistedQueryLink(),
  createHttpLink({ 
    uri: "http://localhost:8080/v1/graphql",
    fetch: fetch,
    headers: {
    "admin-secret":"password"
    } 
  })
]);



const client = new ApolloClient({
 cache: new InMemoryCache(),
 link: link
})


async function main() {
  const response = await client
  .query({
    query: gql`
      query {
        child {
          name
        }
      }`
  })
  console.log(response.data)
}

main().catch(err => console.log("Err:", err))

But whenever I run this file, I get the following error:

graphQLErrors: [
    {
      extensions: [Object],
      message: "the key 'query' was not present"
    }
  ],

When I check the Request body sent in POST Body, I get the following thing:

{"operationName":null,"variables":{},"extensions":{"persistedQuery":{"version":1,"sha256Hash":"0832c514aef4b1a6d84702e8b2fab452cbb0af61f0a1c4a4c30405e671d40527"}}}

it tells that the query is not sent in the Post Body. Which might be the reason I'm getting the above error.

Hence, I am confused at this point ๐Ÿ™ˆ

I read through a tons of blogs, but It's not clear as to what HTTP method is used when { useGETForHashedQueries: true } option is not given. From my experiment above, it looks as if - POST method is used.

But if POST method is used, why isn't the query sent in the POST body.

BUT

When I use the { useGETForHashedQueries: true } option, it works correctly. What might I be doing wrong here?

It would be really great, if someone would clear this out for me.

Questions about hashing cost client side.

I'm referring to this: https://github.com/apollographql/apollo-link-persisted-queries#build-time-generation

Build time generation
If you want to avoid hashing in the browser, you can use a build script to include the hash as part of the request. Then you pass a function to retrieve that hash when the operation is run. This works well with projects like this which uses webpack to generate the hashes at build time.
If you use the above loader, you can pass { generateHash: ({ documentId }) => documentId } to the createPersistedQueryLink call.

Can I ask you how much do you think is the cost of hashing operation on client side (without using scripting at compile time for documents hash)?

Has it a strong impact on performances?

It gets generated just the first time in a browser session?

No guidance for how to use APQ to do query whitelisting

The Apollo glossary defines query whitelisting as follows:

A technique for preventing unwanted attacks by maintaining a list of approved queries that are allowed in your application. Any query not present in the list that is run against the server will not be allowed. Automatic Persisted Queries is a feature of Apollo Server 2 that enables query whitelisting and persisted queries.

https://www.apollographql.com/docs/resources/graphql-glossary/#query-whitelisting

Yet there is no guidance or docs provided at all for query whitelisting using APQ.

Status code for PersistedQueryNotFound

The readme says:

Error Responses When the initial query signature is received by a backend, if it is unable to find the hash previously stored, it must send back the following response signature:

{
 errors: [
   { message: 'PersistedQueryNotFound' }
 ]
}

What should the HTTP response code be? Should it still be 200 OK, or should it be something else?

Running Apollo Engine in development for persisted query support

EDIT: This turned out to not be an issue with the RetryLink! See the resulting conversation with @jbaxleyiii. Old issue text:

I'm not sure how to use the persisted query link in combination with the retry link. The problem is that the retry link ends up retrying the first query with the hash over and over, even if Apollo Engine hasn't seen it yet and returns an error.

I tried a custom retry link configuration that doesn't retry the happy path from persisted queries, but if the retry link doesn't retry the query the persisted query link doesn't send the full query text either ๐Ÿ˜•

Any clues how to use them together?

Click to see snippet of my apollo client code
  const retryLink = new RetryLink({
    attempts: (count, operation, error) => {
      const isPersistedQuery =
        operation.extensions &&
        operation.extensions.persistedQuery &&
        operation.extensions.persistedQuery.sha256Hash;
      // Don't retry persisted query tries since the persisted query link will
      // retry those will the full query text
      if (isPersistedQuery) return false;

      const isMutation =
        operation &&
        operation.query &&
        operation.query.definitions &&
        Array.isArray(operation.query.definitions) &&
        operation.query.definitions.some(
          def =>
            def.kind === 'OperationDefinition' && def.operation === 'mutation'
        );

      // Retry mutations for a looong time, those are very important to us so we want them to go through eventually
      if (isMutation) {
        return !!error && count < 25;
      }

      // Retry queries for way less long as this just ends up showing
      // loading indicators for that whole time which is v annoying
      return !!error && count < 6;
    },
  });

  // HTTP Link for queries and mutations including file uploads
  const httpLink = createPersistedQueryLink().concat(
    retryLink.concat(
      createUploadLink({
        uri: API_URI,
        credentials: 'include',
        headers,
      })
    )
  );

Persisted Query server state

An awesome feature to add to the Apollo ecosystem! On the Apollo Engine side, is the state managed by the engine or by the graphql server instance? I use GraphQL in an ephemeral compute environment and would like to know if I will only get the queries persisting for single GQL server instances or across all instances connected to the same Apollo Engine via the side-car proxy.

Fix the build?

The CI build is failing because of maxFileSize of the bundle.

3.61 Kb > 3.6 Kb.

This can easily be fixed by increasing maxFileSize to 3.7 Kb, but I don't want to make a PR if there is a super valid reason for this particular maxFileSize.

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.