GithubHelp home page GithubHelp logo

Comments (33)

garethhallnz avatar garethhallnz commented on May 16, 2024 1

@ardatan Test locally and yes it works Thanks.

from graphql-mesh.

garethhallnz avatar garethhallnz commented on May 16, 2024 1

Also, my linter was not too happy with the add-fields.js file

Erroring with

  7:3  error  Assignment to property of function parameter 'args'  no-param-reassign

Here the updated file

const graphqlFields = require('graphql-fields');

// This function gets requested fields from GraphQLResolveInfo
// And passes them through `fields` parameter
module.exports = (next) => (root, args, context, info) => {
  const fields = Object.keys(graphqlFields(info));
  const updatedArgs = { ...args, fields: fields.join(',') };
  return next(root, updatedArgs, context, info);
};

``

from graphql-mesh.

ardatan avatar ardatan commented on May 16, 2024

We changed the API for Swagger but haven't released it yet. You can use the alpha release for now. 0.0.20-alpha-aa57f27.19+aa57f27
You can pass headers for your schema by using schemaHeaders option like below with that alpha version;

sources:
  - name: Youtrack
    handler:
      openapi:
        source: https://youtrack.example.com/api/openapi.json
        schemaHeaders:
          Authorization: Bearer perm:******************

from graphql-mesh.

garethhallnz avatar garethhallnz commented on May 16, 2024

Thank you I've changed to the alpha release as suggested. Also I assume the Authorization: Bearer Bearer perm:****************** having Bearer twice was just a typo.

Anyway, the error has now changed to:
error: Unable to serve mesh: Request is not defined {"stack":"ReferenceError: Request is not defined\n at readUrlWithCache (/Users/garethhall/Sites/communica/taskhub/node_modules/@graphql-mesh/utils/index.cjs.js:69:52)\n at Object.readFileOrUrlWithCache (/Users/garethhall/Sites/communica/taskhub/node_modules/@graphql-mesh/utils/index.cjs.js:32:16)\n at Object.getMeshSource (/Users/garethhall/Sites/communica/taskhub/node_modules/@graphql-mesh/openapi/index.cjs.js:9:34)\n at /Users/garethhall/Sites/communica/taskhub/node_modules/@graphql-mesh/runtime/index.cjs.js:240:55\n at Array.map (<anonymous>)\n at Object.getMesh (/Users/garethhall/Sites/communica/taskhub/node_modules/@graphql-mesh/runtime/index.cjs.js:239:39)\n at Object.handler (/Users/garethhall/Sites/communica/taskhub/node_modules/@graphql-mesh/cli/bin.js:238:62)"}

from graphql-mesh.

ardatan avatar ardatan commented on May 16, 2024

@garethhallnz You're right. Could you try with this one?
0.0.20-alpha-18f10ab.20+18f10ab

from graphql-mesh.

garethhallnz avatar garethhallnz commented on May 16, 2024

Yay, a step forward :) That said there is a new failure much later down the stack.

Playground now launches but queries are failing because the uri is not correct.

apiadmin/projects should be api/admin/projects

https://youtrack.example.com/api/openapi.json defines the uri as

"servers": [
    {
      "url": "/api"
    }
  ],
....

Error message

{
  "errors": [
    {
      "message": "Invalid URI \"/apiadmin/projects\"",
      "locations": [
        {
          "line": 2,
          "column": 3
        }
      ],
      "path": [
        "getAdminProjects"
      ],
      "extensions": {
        "code": "INTERNAL_SERVER_ERROR",
        "exception": {
          "stacktrace": [
            "Error: Invalid URI \"/apiadmin/projects\"",
            "    at Request.init (/Users/garethhall/Sites/communica/taskhub/node_modules/request/request.js:273:31)",
            "    at new Request (/Users/garethhall/Sites/communica/taskhub/node_modules/request/request.js:127:8)",
            "    at request (/Users/garethhall/Sites/communica/taskhub/node_modules/request/index.js:53:10)",
            "    at /Users/garethhall/Sites/communica/taskhub/node_modules/openapi-to-graphql/lib/resolver_builder.js:238:13",
            "    at new Promise (<anonymous>)",
            "    at /Users/garethhall/Sites/communica/taskhub/node_modules/openapi-to-graphql/lib/resolver_builder.js:237:16",
            "    at /Users/garethhall/Sites/communica/taskhub/node_modules/@graphql-mesh/runtime/index.cjs.js:215:38",
            "    at field.resolve (/Users/garethhall/Sites/communica/taskhub/node_modules/graphql-extensions/dist/index.js:133:26)",
            "    at resolveFieldValueOrError (/Users/garethhall/Sites/communica/taskhub/node_modules/graphql/execution/execute.js:486:18)",
            "    at resolveField (/Users/garethhall/Sites/communica/taskhub/node_modules/graphql/execution/execute.js:444:16)"
          ]
        }
      }
    }
  ],
  "data": {
    "getAdminProjects": null
  }
}

from graphql-mesh.

ardatan avatar ardatan commented on May 16, 2024

Are you sure this swagger file is valid because when I paste it into Swagger Editor? I got an error like paths must start with '/'.
https://editor.swagger.io/
You can see the specifications; paths always starts with / character.
https://swagger.io/docs/specification/basic-structure/
We can add an option to override baseUrl. Working on it.

from graphql-mesh.

ardatan avatar ardatan commented on May 16, 2024

@garethhallnz
I added an option to override server config. So you can use it like below;

sources:
  - name: Youtrack
    handler:
      openapi:
        source: https://youtrack.example.com/api/openapi.json
        schemaHeaders:
          Authorization: Bearer perm:******************
        baseUrl: https://youtrack.example.com/api/

npx match-version @graphql-mesh 0.0.20-alpha-067bc75.23+067bc75

Could you try again with this configuration with that canary version?

from graphql-mesh.

garethhallnz avatar garethhallnz commented on May 16, 2024

@ardatan Your help has been awesome thanks!

Another step forward but still not working :(

The baseUrl does seem to resolve the previous problem. However, PlayGround now reports Unauthorized.

My graphql query

{
  getAdminProjects{
    shortName
  }
}

Response

{
  "errors": [
    {
      "message": "Could not invoke operation GET admin/projects",
      "locations": [
        {
          "line": 2,
          "column": 3
        }
      ],
      "path": [
        "getAdminProjects"
      ],
      "extensions": {
        "method": "get",
        "path": "admin/projects",
        "statusCode": 401,
        "responseHeaders": {
          "server": "nginx/1.14.0 (Ubuntu)",
          "date": "Tue, 07 Apr 2020 00:06:40 GMT",
          "content-type": "application/json;charset=utf-8",
          "content-length": "47",
          "connection": "close",
          "access-control-expose-headers": "Location",
          "x-xss-protection": "1; mode=block",
          "x-frame-options": "SAMEORIGIN",
          "x-content-type-options": "nosniff",
          "referrer-policy": "strict-origin-when-cross-origin",
          "content-encoding": "UTF-8",
          "cache-control": "no-cache, no-store, no-transform, must-revalidate"
        },
        "responseBody": {
          "error": "Unauthorized",
          "error_description": ""
        },
        "code": "INTERNAL_SERVER_ERROR",
        "exception": {
          "message": "Could not invoke operation GET admin/projects",
          "stacktrace": [
            "GraphQLError: Could not invoke operation GET admin/projects",
            "    at graphQLErrorWithExtensions (/Users/garethhall/Sites/communica/taskhub/node_modules/openapi-to-graphql/lib/resolver_builder.js:700:12)",
            "    at Request._callback (/Users/garethhall/Sites/communica/taskhub/node_modules/openapi-to-graphql/lib/resolver_builder.js:261:32)",
            "    at Request.self.callback (/Users/garethhall/Sites/communica/taskhub/node_modules/request/request.js:185:22)",
            "    at Request.emit (events.js:321:20)",
            "    at Request.<anonymous> (/Users/garethhall/Sites/communica/taskhub/node_modules/request/request.js:1154:10)",
            "    at Request.emit (events.js:321:20)",
            "    at IncomingMessage.<anonymous> (/Users/garethhall/Sites/communica/taskhub/node_modules/request/request.js:1076:12)",
            "    at Object.onceWrapper (events.js:427:28)",
            "    at IncomingMessage.emit (events.js:333:22)",
            "    at endReadableNT (_stream_readable.js:1201:12)"
          ]
        }
      }
    }
  ],
  "data": {
    "getAdminProjects": null
  }
}

I have also tried to pass the Authorization header in PlayGround but to no avail.

Here is the curl request that should match the above and it does work.

curl --location --request GET 'https://youtrack.example.com/api/admin/projects?fields=shortName' \
--header 'Authorization: Bearer perm:******************'

from graphql-mesh.

ardatan avatar ardatan commented on May 16, 2024

For operations, you need to use operationHeaders additionally.
schemaHeaders is used while fetching the Swagger/OpenAPI schema only but operationHeaders is used during the operations on runtime.

from graphql-mesh.

garethhallnz avatar garethhallnz commented on May 16, 2024

Sorry I am not sure I follow, what does that mean?

from graphql-mesh.

ardatan avatar ardatan commented on May 16, 2024

schemaHeaders is used only for fetching openapi.json. There is another option called operationHeaders used for operations/queries.

sources:
  - name: Youtrack
    handler:
      openapi:
        source: https://youtrack.example.com/api/openapi.json
        schemaHeaders:
          Authorization: Bearer perm:******************
        operationHeaders:
          Authorization: Bearer perm:******************
        baseUrl: https://youtrack.example.com/api/

That means you need to define your headers under operationHeaders as well.

from graphql-mesh.

garethhallnz avatar garethhallnz commented on May 16, 2024

Thanks that has inched things forward a little more. It's almost working;

My output is now

{
  "data": {
    "getAdminProjects": [
      {
        "shortName": null
      },
      {
        "shortName": null
      },
      {
        "shortName": null
      },
      {
        "shortName": null
      },
      {
        "shortName": null
      }
    ]
  }
}

from graphql-mesh.

ardatan avatar ardatan commented on May 16, 2024

It looks like an incompability between the schema and the actual result. How does the actual result look like?

Another question; does that swagger file work well within a Swagger UI? because it looks like broken as I said before.

from graphql-mesh.

garethhallnz avatar garethhallnz commented on May 16, 2024

Well clearly the Youtrack devs have not implemented the openapi standard correctly.

I too get an error in swagger.

So I downloaded the openapi file and edited it to make it pass. Basically I removed the license section and added "/" to the paths.

I then updated the mesh file to:

sources:
  - name: Youtrack
    handler:
      openapi:
        source: ./openapi.json
        schemaHeaders:
          Authorization: Bearer perm:***********
        operationHeaders:
          Authorization: Bearer perm:***********
        baseUrl: https://youtrack.example.com/api/

I was hoping your new baseUrl option would allow me to bypass prevouse with the url.

But that caused this error in PlayGround

{
  "errors": [
    {
      "message": "Could not invoke operation GET /admin/projects",
      "locations": [
        {
          "line": 2,
          "column": 3
        }
      ],
      "path": [
        "getAdminProjects"
      ],
      "extensions": {
        "method": "get",
        "path": "/admin/projects",
        "statusCode": 404,
        "responseHeaders": {
          "server": "nginx/1.14.0 (Ubuntu)",
          "date": "Tue, 07 Apr 2020 09:19:42 GMT",
          "content-type": "application/json;charset=utf-8",
          "content-length": "62",
          "connection": "close",
          "access-control-expose-headers": "Location",
          "x-xss-protection": "1; mode=block",
          "x-frame-options": "SAMEORIGIN",
          "x-content-type-options": "nosniff",
          "referrer-policy": "strict-origin-when-cross-origin",
          "set-cookie": [
            "YTJSESSIONID=node01sxh5pog88xdy1gt1vt3gmffet1350.node0; Path=/; Secure; HttpOnly"
          ],
          "expires": "Thu, 01 Jan 1970 00:00:00 GMT",
          "content-encoding": "UTF-8",
          "cache-control": "no-cache, no-store, no-transform, must-revalidate"
        },
        "responseBody": {
          "error": "Not Found",
          "error_description": "HTTP 404 Not Found"
        },
        "code": "INTERNAL_SERVER_ERROR",
        "exception": {
          "message": "Could not invoke operation GET /admin/projects",
          "stacktrace": [
            "GraphQLError: Could not invoke operation GET /admin/projects",
            "    at graphQLErrorWithExtensions (/Users/garethhall/Sites/communica/taskhub/node_modules/openapi-to-graphql/lib/resolver_builder.js:700:12)",
            "    at Request._callback (/Users/garethhall/Sites/communica/taskhub/node_modules/openapi-to-graphql/lib/resolver_builder.js:261:32)",
            "    at Request.self.callback (/Users/garethhall/Sites/communica/taskhub/node_modules/request/request.js:185:22)",
            "    at Request.emit (events.js:321:20)",
            "    at Request.<anonymous> (/Users/garethhall/Sites/communica/taskhub/node_modules/request/request.js:1154:10)",
            "    at Request.emit (events.js:321:20)",
            "    at IncomingMessage.<anonymous> (/Users/garethhall/Sites/communica/taskhub/node_modules/request/request.js:1076:12)",
            "    at Object.onceWrapper (events.js:427:28)",
            "    at IncomingMessage.emit (events.js:333:22)",
            "    at endReadableNT (_stream_readable.js:1201:12)"
          ]
        }
      }
    }
  ],
  "data": {
    "getAdminProjects": null
  }
}

I think the schema is correct

"Project": {
        "allOf": [
          {
            "$ref": "#/components/schemas/IssueFolder"
          },
          {
            "properties": {
              "startingNumber": {
                "type": "integer",
                "description": "Starting number for issues in project. This property can be set only during creation of the new project.",
                "format": "int64",
                "readOnly": false
              },
              "shortName": {
                "type": "string",
                "description": "The ID of the project. This short name is also a prefix for an issue ID.",
                "readOnly": false
              },
              "description": {
                "type": "string",
                "description": "The description of the project as shown on the project profile page.",
                "readOnly": false
              },
              "leader": {
                "description": "The user who is set as the project lead.",
                "$ref": "#/components/schemas/User"
              },
              "createdBy": {
                "description": "The user who created the project.",
                "$ref": "#/components/schemas/User"
              },
              "issues": {
                "type": "array",
                "description": "A list of all issues that belong to the project.",
                "items": {
                  "$ref": "#/components/schemas/Issue"
                }
              },
              "customFields": {
                "type": "object",
                "description": "The set of custom fields that are available in the project.",
                "readOnly": true
              },
              "archived": {
                "type": "boolean",
                "description": "If the project is currently archived, this property is `true`.",
                "readOnly": false
              },
              "fromEmail": {
                "type": "string",
                "description": "\n                The email address that is used to send notifications for the project. If a 'From' address is not set \n                for the project, the default 'From' address for the YouTrack server is returned.\n                ",
                "readOnly": false
              },
              "replyToEmail": {
                "type": "string",
                "description": "\n                The email address that is used as the reply email to send notifications for the project.\n                If it is not set for the project, the default address for the YouTrack server is returned.                                    \n                ",
                "readOnly": false
              },
              "template": {
                "type": "boolean",
                "description": "If `true`, this project is a template. ",
                "readOnly": false
              },
              "iconUrl": {
                "type": "string",
                "description": "The URL of the icon of the project.",
                "readOnly": true
              }
            },
            "description": "Represents a YouTrack project."
          }
        ]
      },

The equivalent curl request

curl --location --request GET 'https://youtrack.example.com/api/admin/projects?fields=shortName' \
--header 'Authorization: Bearer perm:***********' 

Result to

[
    {
        "shortName": "ANZW",
        "$type": "Project"
    },
    {
        "shortName": "APM",
        "$type": "Project"
    },
    {
        "shortName": "ARB",
        "$type": "Project"
    },
    {
        "shortName": "AM",
        "$type": "Project"
    }
]

from graphql-mesh.

Urigo avatar Urigo commented on May 16, 2024

@garethhallnz we are thinking about having the idea of "Mesh Modules" or something, where you can install ready to use, open source Mesh npm packages for APIs a lot of people need.

Do you think you can share your code on Github somehow and we can help you create something that first will work for you and also others will find useful? (YouTrack GraphQL API)

from graphql-mesh.

garethhallnz avatar garethhallnz commented on May 16, 2024

@Urigo I am happy to push something to Github; The problem is while you could get a free Youtrack account it's not really something a user can just setup to try.

AI user would have to:

  • Create a YouTrack account
  • Load some test data
  • Go into YouTrack and create an access token

Do you think people will do that?

from graphql-mesh.

Urigo avatar Urigo commented on May 16, 2024

@garethhallnz maybe who knows?
also after we'll have it maybe we can share it with JetBrains so they could offer their users also a GraphQL API!

from graphql-mesh.

petrovalex avatar petrovalex commented on May 16, 2024

@ardatan I think in my use case I can not really predefine operationHeaders, user logs in and acquires token which is then supposed to be used for querying data. Is is possible still to pass operation headers from the client?

from graphql-mesh.

garethhallnz avatar garethhallnz commented on May 16, 2024

@ardatan I think in my use case I can not really predefine operationHeaders, user logs in and acquires token which is then supposed to be used for querying data. Is is possible still to pass operation headers from the client?

Yeah I am going to face that issue too. The query header must be injectable.

from graphql-mesh.

garethhallnz avatar garethhallnz commented on May 16, 2024

@garethhallnz maybe who knows?
also after we'll have it maybe we can share it with JetBrains so they could offer their users also a GraphQL API!

Do you want to create a repo so you have ownership of it? Then I'll fork than and do a pull request; otherwise, I'll set it up under my account.

from graphql-mesh.

ardatan avatar ardatan commented on May 16, 2024

@garethhallnz I realized that you need add the name of field specifically in fields parameter in regular HTTP request. So since Mesh doesn't know this specific thing, you need to do the same thing in GraphQL operations. I am preparing an example for youtrack, you can take a look at.

@petrovalex We use openapi-to-graphql under the hood for Swagger/OpenAPI specifications, so they don't support additional headers that can be passed from GraphQL Args etc. But we're working on it. For now you can use environmental variables or runtime API to pass those variables. In environmental variables you can use the following syntax;
Authorization: Bearer ${YOUTRACK_TOKEN}

from graphql-mesh.

ardatan avatar ardatan commented on May 16, 2024

I submitted a PR that has some changes on Mesh to make things easier for OpenAPI.
#224

from graphql-mesh.

ardatan avatar ardatan commented on May 16, 2024

@garethhallnz I also added a working example;
https://codesandbox.io/s/strange-lewin-blnvq?file=/.meshrc.yml

from graphql-mesh.

garethhallnz avatar garethhallnz commented on May 16, 2024

@ardatan I spoke a little too soon; I did a bunch of testing and simple queries work but more complex onces fail.

These work

query getProjects{
  getAdminProjects {
     shortName
   }
 }

 query getIssuesByProject{
  getAdminProjectsIdIssues (id: "ANZW") {
     summary
   }
 }

 query getIssues{
   getIssues {
     summary
   }
 }

These fail or more specifically I get null

query getProjectsWithIssues{
  getAdminProjects {
     shortName
    	issues {
        summary
      }
   }
 }

 query getIssuesWithComments{
  getIssues{
    summary
    created
    reporter {
      email
    }
    comments {
      text
    }
   }
 }

query reallyComplex{
  getAdminProjectsIdIssues (
    id: "FLTI"
  ) {
    numberInProject
    idReadable
    summary
    comments {
      text
      author {
        fullName
      }
    }
    commentsCount
  }
}

Ideally my goal it to reconstruct

axios.get(`/api/admin/projects/${id}/issues?fields=numberInProject,idReadable,summary,comments(text,author(name)),commentsCount,timeTracking(workItems(id,duration(minutes,presentation))),customFields(projectCustomField(field(name)),value(minutes,presentation))&$skip=0&$top=10000`)

from graphql-mesh.

ardatan avatar ardatan commented on May 16, 2024

In codesandbox I sent to you, all of them are working. I didn't get null with any of them. Could you confirm? If yes, could you follow the same setup?
https://codesandbox.io/s/strange-lewin-blnvq?file=/.meshrc.yml

from graphql-mesh.

garethhallnz avatar garethhallnz commented on May 16, 2024

@ardatan My setup is the same other than hardcoded tokens. I think the problem is content in your example. You only have one project called "Kanban Test (KT)" but it has no issues or comments against said issues.

So in your example

 query getIssuesByProject{
  getAdminProjectsIdIssues (id: "KT") {
     summary
   }
 }

returns [] so there are no issues in KT

So to replicate the problem create an issue againt the KT project

from graphql-mesh.

ardatan avatar ardatan commented on May 16, 2024

@garethhallnz I added an issue to the project as you said, and the following query gives me the correct results. You can test in my codesandbox as well. Let me know if I'm missing something.

 query getIssuesByProject{
  getAdminProjectsIdIssues (id: "KT") {
     summary
     description
   }
 }

Result:

{
  "data": {
    "getAdminProjectsIdIssues": [
      {
        "summary": "Issue Test",
        "description": "Issue Test Description"
      }
    ]
  }
}

from graphql-mesh.

garethhallnz avatar garethhallnz commented on May 16, 2024

Yes, that works (as do my copy) but as we all know the value of Grapdql is the ability the pull data with depth.

So try

query getProjectsWithIssues{
  getAdminProjects {
     shortName
    	issues {
        summary
      }
   }
 }

You notice issues[0].summary us null

from graphql-mesh.

ardatan avatar ardatan commented on May 16, 2024

Ok so this is a different problem. We solved the previous ones as I understand :) This is good.
Again this is not related to GraphQL Mesh directly but YouTrack specific, so we need update our composer function to resolve the fields of YouTrack as I see in their documentation. So I updated the composer function to resolve subfields as well. You can do console.log(args) to see how we build our fields parameter.
https://codesandbox.io/s/strange-lewin-blnvq?file=/src/add-fields.js

from graphql-mesh.

garethhallnz avatar garethhallnz commented on May 16, 2024

@petrovalex We use openapi-to-graphql under the hood for Swagger/OpenAPI specifications, so they don't support additional headers that can be passed from GraphQL Args etc. But we're working on it. For now you can use environmental variables or runtime API to pass those variables. In environmental variables you can use the following syntax;
Authorization: Bearer ${YOUTRACK_TOKEN}

@ardatan Now that I am thinking about it the above is a massive problem. The Youtrack token is directly related to the user and their permissions. Therefore some users might have access to operations they should not have; like deleting something. So in a sense, we can only use mesh like an administration connection. This severely limits it's use-case ..... for now at least.

from graphql-mesh.

ardatan avatar ardatan commented on May 16, 2024

@garethhallnz I assume that we have solved another problem as well because you didn't say anything about it :)

With that PR, now we are able to pass custom headers using args and context. So using the following configuration, you can pass different API Keys using apiKey argument on your root operations.

sources:
  - name: YouTrack
    handler:
      openapi:
        source: ${YOUTRACK_SERVICE_URL}/api/openapi.json
        baseUrl: ${YOUTRACK_SERVICE_URL}/api/
        schemaHeaders: # this is admin token to fetch the schema before actual runtime
          Authorization: Bearer ${YOUTRACK_TOKEN}
        operationHeaders: # This is user-specific api token
          Authorization: Bearer {args.apiToken}
        # `license` field is not valid
        # But it's ok because all YouTrack schemas are in the same structure
        # So using this option is safe.
        skipSchemaValidation: true
    transforms:
      # Each YouTrack needs `fields` query parameter
      # So thanks to GraphQL, we know which fields client needs
      # And we can prevent overfetching in this way
      - resolversComposition:
          - resolver: Query.*
            composer: ./src/add-fields

Or you can pass it using context,

sources:
  - name: YouTrack
    handler:
      openapi:
        source: ${YOUTRACK_SERVICE_URL}/api/openapi.json
        baseUrl: ${YOUTRACK_SERVICE_URL}/api/
        schemaHeaders:
          Authorization: Bearer ${YOUTRACK_TOKEN}
        operationHeaders:
          Authorization: Bearer {context.apiToken}
        # `license` field is not valid
        # But it's ok because all YouTrack schemas are in the same structure
        # So using this option is safe.
        skipSchemaValidation: true
    transforms:
      # Each YouTrack needs `fields` query parameter
      # So thanks to GraphQL, we know which fields client needs
      # And we can prevent overfetching in this way
      - resolversComposition:
          - resolver: Query.*
            composer: ./src/add-fields

I think this would work for you and I think this doesn't limit your use-case then. But Mesh should never be your public schema in my opinion. Mesh is just a way to query your database with GraphQL using SDK etc.

from graphql-mesh.

ardatan avatar ardatan commented on May 16, 2024

Available in 0.1.0!

from graphql-mesh.

Related Issues (20)

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.