Giter Club home page Giter Club logo

kiota-typescript's Introduction

Project

This repo has been populated by an initial template to help get you started. Please make sure to update the content to build a great experience for community-building.

As the maintainer of this project, please make a few updates:

  • Improving this README.MD file to provide a great experience
  • Updating SUPPORT.MD with content about this project's support experience
  • Understanding the security reporting process in SECURITY.MD
  • Remove this section from the README

Contributing

This project welcomes contributions and suggestions. Most contributions require you to agree to a Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us the rights to use your contribution. For details, visit https://cla.opensource.microsoft.com.

When you submit a pull request, a CLA bot will automatically determine whether you need to provide a CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions provided by the bot. You will only need to do this once across all repos using our CLA.

This project has adopted the Microsoft Open Source Code of Conduct. For more information see the Code of Conduct FAQ or contact [email protected] with any additional questions or comments.

Trademarks

This project may contain trademarks or logos for projects, products, or services. Authorized use of Microsoft trademarks or logos is subject to and must follow Microsoft's Trademark & Brand Guidelines. Use of Microsoft trademarks or logos in modified versions of this project must not cause confusion or imply Microsoft sponsorship. Any use of third-party trademarks or logos are subject to those third-party's policies.

kiota-typescript's People

Contributors

andreatp avatar andrueastman avatar baywet avatar dependabot[bot] avatar gavinbarron avatar github-actions[bot] avatar koros avatar luismanez avatar microsoft-github-operations[bot] avatar microsoftopensource avatar nikithauc avatar rkodev avatar salmannotkhan avatar srrickgrimes 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

Watchers

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

kiota-typescript's Issues

Serialization Library attempts to Serialize a null / undefined object when get request returns no objects

Example

 const graphServiceClient = GraphServiceClient.init({authProvider});
 const result = async () => {
 	await graphServiceClient.admin.serviceAnnouncement.messages.get();
 }

Error : /test_build/node_modules/@microsoft/msgraph-sdk-javascript/node_modules/@microsoft/kiota-serialization-json/dist/cjs/src/jsonParseNode.js:89
Object.entries(this._jsonNode).forEach(([k, v]) => {
^

TypeError: Cannot convert undefined or null to object

[BUG]: Kiota fails to deserialize response when it contains array of enums

Kiota fails to de-serialize the response when it contains this array field of enum in OpenAPI:

xyz:
  type: array
  items:
    type: string
    enum:
      - foo
      - bar
      - baz

It generates code like this:

"xyz": n => { instance.xyz = n.getEnumValues(xyz); }

It calls getEnumValues method of @microsoft/kiota-serialization-json package

and it errors on

return rawValues.split(",").map((x) => type[toFirstCharacterUpper(x)] as T);
//              ^ Here, as the rawValues is already an array and array doesn't have split method

Set up pipeline for first npm publish

  • Add workflows to publish kiota packages into npm for the first time
  • Release abstractions packages
  • Update the dependent packages, that is, fetch, serialization, auth with the released abstraction package
  • Release all the packages

Typescript - Custom headers and fetch options configuration

Consider requests such as:

GET https://graph.microsoft.com/v1.0/me/events?$select=subject,body,bodyPreview,organizer,attendees,start,end,location
Prefer: outlook.timezone="Pacific Standard Time"

https://docs.microsoft.com/en-us/graph/api/user-list-events?view=graph-rest-1.0&tabs=http#request-headers

  1. Enable adding custom headers and options
  2. Create a configuration object that allows users to customize the request
  3. Test similar requests
    AB#10691

Serializers should be JS functions

Move serialization function off objects into pure functions so we can dynamically compose serializers for model interfaces during TypeScript generation.

When generating a client, a requestBuilder gets the `get` method twice

When using the following kiota command to generate a client kiota generate -l typescript -o nytmoviereviews -d https://raw.githubusercontent.com/nytimes/public_api_specs/master/movie_reviews/movie_reviews_v2.json -c NytMovieClient -n Nyt, we have twice the get implementation on the client.reviews.json request builder.

import {createJsonResponseFromDiscriminatorValue} from './createJsonResponseFromDiscriminatorValue';
import {JsonResponse} from './index';
import {JsonRequestBuilderGetRequestConfiguration} from './jsonRequestBuilderGetRequestConfiguration';
import {getPathParameters, HttpMethod, Parsable, ParsableFactory, RequestAdapter, RequestInformation, RequestOption, ResponseHandler} from '@microsoft/kiota-abstractions';

/** Builds and executes requests for operations under /reviews/{resource-type}.json */
export class JsonRequestBuilder {
    /** Path parameters for the request */
    private readonly pathParameters: Record<string, unknown>;
    /** The request adapter to use to execute the requests. */
    private readonly requestAdapter: RequestAdapter;
    /** Url template to use to build the URL for the current request builder */
    private readonly urlTemplate: string;
    /**
     * Instantiates a new JsonRequestBuilder and sets the default values.
     * @param pathParameters The raw url or the Url template parameters for the request.
     * @param requestAdapter The request adapter to use to execute the requests.
     */
    public constructor(pathParameters: Record<string, unknown> | string | undefined, requestAdapter: RequestAdapter) {
        if(!pathParameters) throw new Error("pathParameters cannot be undefined");
        if(!requestAdapter) throw new Error("requestAdapter cannot be undefined");
        this.urlTemplate = "{+baseurl}/reviews/search.json{?query*,critics%2Dpick*,reviewer*,publication%2Ddate*,opening%2Ddate*,offset*,order*}";
        const urlTplParams = getPathParameters(pathParameters);
        this.pathParameters = urlTplParams;
        this.requestAdapter = requestAdapter;
    };
    public createGetRequestInformation(requestConfiguration?: JsonRequestBuilderGetRequestConfiguration | undefined) : RequestInformation {
        const requestInfo = new RequestInformation();
        requestInfo.urlTemplate = this.urlTemplate;
        requestInfo.pathParameters = this.pathParameters;
        requestInfo.httpMethod = HttpMethod.GET;
        requestInfo.headers["Accept"] = "application/json";
        if (requestConfiguration) {
            requestInfo.addRequestHeaders(requestConfiguration.headers);
            requestInfo.setQueryStringParametersFromRawObject(requestConfiguration.queryParameters);
            requestInfo.addRequestOptions(requestConfiguration.options);
        }
        return requestInfo;
    };
    public get(requestConfiguration?: JsonRequestBuilderGetRequestConfiguration | undefined, responseHandler?: ResponseHandler | undefined) : Promise<JsonResponse | undefined> {
        const requestInfo = this.createGetRequestInformation(
            requestConfiguration
        );
        return this.requestAdapter?.sendAsync<JsonResponse>(requestInfo, createJsonResponseFromDiscriminatorValue, responseHandler, undefined) ?? Promise.reject(new Error('http core is null'));
    };
    public get(requestConfiguration?: JsonRequestBuilderGetRequestConfiguration | undefined, responseHandler?: ResponseHandler | undefined) : Promise<JsonResponse | undefined> {
        const requestInfo = this.createGetRequestInformation(
            requestConfiguration
        );
        return this.requestAdapter?.sendAsync<JsonResponse>(requestInfo, createJsonResponseFromDiscriminatorValue, responseHandler, undefined) ?? Promise.reject(new Error('http core is null'));
    };
}

typescript core (and generation) should be using UUID

[ Tracking] Set up tests infrastructure

  • Add node and browser tests to abstractions
  • Add node and browser tests to fetch
  • Add node and browser tests to serialization json
  • Add node and browser tests to serialization text
  • Add node and browser tests to authorization azure
  • Add integrated run time tests

swap uri template for std uri template

@andreaTP as soon as you think you're ready, please go ahead and submit a PR here. One thing to be careful about for TypeScript is the type of module you're bundling to. For now we're supporting both commonjs and ESM with a target of es2017. If you have questions around those @koros can help with that.

getCollectionOfObjectValues throws: TypeError: Cannot read properties of null (reading 'map')

For an array property marked as nullable, the json serializer does not check for null.

Versions:
"@microsoft/kiota-abstractions": "^1.0.0-preview.26",
"@microsoft/kiota-serialization-json": "^1.0.0-preview.25",

Relevant OpenApi document snippets (note that the foos property is nullable):

"Models.FooObjectResponse": {
  "type": "object",
  "properties": {
    "foos": {
      "type": "array",
      "items": {
        "$ref": "#/components/schemas/Models.FooResponse"
      },
      "nullable": true
    }
  },
  "additionalProperties": false
},

"Models.FooResponse": {
  "type": "object",
  "properties": {
    "id": {
      "type": "string"
    },
    "bars": {
      "type": "array",
      "items": {
        "$ref": "#/components/schemas/Models.BarResponse"
      },
      "nullable": true
    }
  },
  "additionalProperties": false
},

"Models.BarResponse": {
  "type": "object",
  "properties": {
    "propA": {
     "type": "string"
    },
    "propB": {
      "type": "string",
      "nullable": true
    },
    "propC": {
      "type": "string",
      "format": "date-time",
      "nullable": true
    }
  },
  "additionalProperties": false
},

Generated model:

export interface FooObjectResponse extends Parsable {
    /**
     * The foos property
     */
    foos?: FooResponse[] | undefined;
}

export interface FooResponse extends Parsable {
    /**
     * The id property
     */
    id?: string | undefined;
    /**
     * The bars property
     */
    bars?: BarResponse[] | undefined;
}

export interface BarResponse extends Parsable {
    /**
     * The propA property
     */
    propA?: string | undefined;
    /**
     * The propB property
     */
    propB?: string | undefined;
    /**
     * The propC property
     */
    propC?: Date | undefined;
}

Response:

{
    "foos": [
        {
            "id": "b089d1f1-e527-4b8a-ba96-094922af6e40",
            "bars": null
        }
    ]
}

Callstack:

Uncaught (in promise) TypeError: Cannot read properties of null (reading 'map')
    at r.getCollectionOfObjectValues (jsonParseNode.js:49:18)
    at bars (deserializeIntoFooResponse.ts:10:64)
    at jsonParseNode.js:70:21
    at Array.forEach (<anonymous>)
    at r.assignFieldValues (jsonParseNode.js:67:44)
    at r.getObjectValue (jsonParseNode.js:57:18)
    at jsonParseNode.js:50:31
    at Array.map (<anonymous>)
    at r.getCollectionOfObjectValues (jsonParseNode.js:50:18)
    at tenants (deserializeIntoFooObjectResponse.ts:9:59)

In failed requests, next to error, return information about the failed request

In failed requests, next to the error, we should return the information about the failed requests. This will help developers understand which request failed, if they're issuing multiple requests in parallel.

Consider the following scenario: I have several external items queued up that I want to add to a Graph connector. To increase the throughput, I issue several requests in parallel using the following code:

image

To avoid blocking the main thread, I'm using promises instead of async/await. The problem is, that because I'm in a loop, by the time I arrive to line 141 where I handle error, the sample object on line 128 is likely referring to another item than the one that failed. This could be saved, if the error object that we throw, contained information about the original request.

Right now, one way to solve it, is to move the Graph call to a separate function to create a closure and persist the information about the request, but it seems like an unnecessary complexity that should be solved by the SDK.

Date property handling

Im not sure if this is a kiota issue or an issue in my OpenAPI spec generation (i.e. Swagger) - let me know where the true issue lies

So I have an OpenAPI spec that has models with Date fields, the OpenAPI fragment of the model definition looks like this:

...
        "created": {
          "format": "date-time",
          "type": "string"
        },
        "modified": {
          "format": "date-time",
          "type": "string"
        },
...

And the server (ASP.NET Core WebAPI) is returning ISO 8601 strings.

The problem I have is that the responses from the server remain as strings when they come through to the generated API client code, which has JS Date types.

Thus, when I try to write back to the server I get the error:

value.toISOString is not a function
TypeError: value.toISOString is not a function

Because the serializer is expecting the types to be of Date rather than the strings they've been hydrated with.

I feel this is due to the deserializer not properly handling a model property of type JS Date and just using the ISO string returned from the server.

Encoding Issue with Polish Characters in Request Payload

I am experiencing an encoding issue with Polish characters when sending requests. Before sending the request, I verified the value of my string variable by logging it to the console using console.log, and it displayed correctly as "Błonie". However, in the request payload, the variable value is incorrectly encoded as "BBonie". This issue persists with other words containing Polish characters. I am unable to determine the cause of this encoding error.

I have encountered this issue while using TypeScript Client Generator versions v1.8.0-preview.202310260001 and v1.7.0. Any assistance in resolving this encoding issue would be greatly appreciated.

Serializing body with string property values in generated POST requests

I have a client generated with Kiota from an OpenAPI file, and several POST methods will send up objects with string value properties, that have all sorts of characters such as newlines, quotes etc.

It seems these string values aren't serialized correctly when making a POST method.

Before kiota, we'd just JSON.stringify the payload and send it up, which would encode string value properties correctly, but it looks like Kiota is writing string value properties verbatim, which breaks the payload and it's no longer valid JSON.

Example:

Say we have an object that we're wanting to POST:

const myObj: SomeObjectType = {
  property1: true,
  property2: `This is a "complex" string type with some items on a \r\ndifferent line`
};

await client.api.someController.someAction.post(myObj);

Then request will fail as the double quotes and the new line character in that string invalidate the generated JSON.

I've noticed that jsonSerializationWriter does this:

    value && this.writer.push(`"${value}"`);

https://github.com/microsoft/kiota-typescript/blob/fb4c6848cb05c7d0b5f2aa642776f47103da4860/packages/serialization/json/src/jsonSerializationWriter.ts#L32C5-L32C45

whereas it probably should do this:

  value && this.writer.push(JSON.stringify(value));

`uri-template-lite` breaks when using a bundler

Trying to use kiota-typescript from a UI application that is bundled using vite we have been experiencing issues with uri-template-lite.

unnamed

The current workaround is to exclude the package from the bundler:

  build: {
    ...
    rollupOptions: {
      external: ['uri-template-lite'],
    }
  },

but is less than ideal and it compromises the "getting started" experience.

We have various options here:

  • include the single source of uri-template-lite in this codebase
  • rely on a different library (that better plays with modules etc.)
  • open to other ideas!

[Tracking] Abstractions and core libraries tooling - TypeScript

In order for us to move with confidence to GA, we need better tooling for
the abstraction and core libraries. This includes:

  • unit tests for the libraries
  • eslint configuration for linting
  • browserlist configuration for compat
  • doc comment for all the things that are not already commented in the abstraction libs (constructors, static things, not implementing/inheriting...)
  • cobertura reports generation
  • udpate the readmes to remove the tasks
    AB#11101

`validateProtocol` is not isomorphic

When using validateProtocol on NodeJS, we get errors around window is not defined.

I am using the following kiota command to generate my client : kiota generate -l typescript -o nytmoviereviews -d https://raw.githubusercontent.com/nytimes/public_api_specs/master/movie_reviews/movie_reviews_v2.json -c NytMovieClient -n Nyt

/home/slevert/source/sebastienlevert/KiotaApp/src/js/node_modules/@microsoft/kiota-abstractions/src/authentication/validateProtocol.ts:8
    return window && window.location && (window.location.protocol as string).toLowerCase() !== 'https:';
    ^
ReferenceError: window is not defined
    at windowUrlStartsWithHttps (/home/slevert/source/sebastienlevert/KiotaApp/src/js/node_modules/@microsoft/kiota-abstractions/src/authentication/validateProtocol.ts:8:5)
    at validateProtocol (/home/slevert/source/sebastienlevert/KiotaApp/src/js/node_modules/@microsoft/kiota-abstractions/src/authentication/validateProtocol.ts:2:60)
    at ApiKeyAuthenticationProvider.authenticateRequest (/home/slevert/source/sebastienlevert/KiotaApp/src/js/ApiKeyAuthenticationProvider.ts:37:21)
    at FetchRequestAdapter.<anonymous> (/home/slevert/source/sebastienlevert/KiotaApp/src/js/node_modules/@microsoft/kiota-http-fetchlibrary/dist/cjs/src/fetchRequestAdapter.js:250:47)
    at Generator.next (<anonymous>)
    at /home/slevert/source/sebastienlevert/KiotaApp/src/js/node_modules/tslib/tslib.js:118:75
    at new Promise (<anonymous>)
    at Object.__awaiter (/home/slevert/source/sebastienlevert/KiotaApp/src/js/node_modules/tslib/tslib.js:114:16)
    at FetchRequestAdapter.getHttpResponseMessage (/home/slevert/source/sebastienlevert/KiotaApp/src/js/node_modules/@microsoft/kiota-http-fetchlibrary/dist/cjs/src/fetchRequestAdapter.js:241:72)
    at FetchRequestAdapter.<anonymous> (/home/slevert/source/sebastienlevert/KiotaApp/src/js/node_modules/@microsoft/kiota-http-fetchlibrary/dist/cjs/src/fetchRequestAdapter.js:110:41)

Read-only properties should be read-only but are "set"

When using kiota with the Bing News API, we get errors that doesn't allow assigning read-only props because they are read-only :

bingNews/models/response.ts:43:14 - error TS2540: Cannot assign to '_webSearchUrl' because it is a read-only property.

43         this._webSearchUrl = value;
                ~~~~~~~~~~~~~

    at createTSError (/home/slevert/source/sebastienlevert/KiotaApp/src/js/node_modules/ts-node/src/index.ts:859:12)
    at reportTSError (/home/slevert/source/sebastienlevert/KiotaApp/src/js/node_modules/ts-node/src/index.ts:863:19)
    at getOutput (/home/slevert/source/sebastienlevert/KiotaApp/src/js/node_modules/ts-node/src/index.ts:1077:36)
    at Object.compile (/home/slevert/source/sebastienlevert/KiotaApp/src/js/node_modules/ts-node/src/index.ts:1433:41)
    at Module.m._compile (/home/slevert/source/sebastienlevert/KiotaApp/src/js/node_modules/ts-node/src/index.ts:1617:30)
    at Module._extensions..js (node:internal/modules/cjs/loader:1203:10)
    at Object.require.extensions.<computed> [as .ts] (/home/slevert/source/sebastienlevert/KiotaApp/src/js/node_modules/ts-node/src/index.ts:1621:12)
    at Module.load (node:internal/modules/cjs/loader:1027:32)
    at Function.Module._load (node:internal/modules/cjs/loader:868:12)
    at Module.require (node:internal/modules/cjs/loader:1051:19) {
  diagnosticCodes: [ 2540 ]

The generated model :

import {Identifiable} from './index';
import {Parsable, ParseNode, SerializationWriter} from '@microsoft/kiota-abstractions';

/** Defines a response. All schemas that could be returned at the root of a response should inherit from this */
export class Response extends Identifiable implements Parsable {
    /** The URL To Bing's search result for this item. */
    private readonly _webSearchUrl?: string | undefined;
    /**
     * Instantiates a new Response and sets the default values.
     */
    public constructor() {
        super();
    };
    /**
     * The deserialization information for the current model
     * @returns a Record<string, (node: ParseNode) => void>
     */
    public getFieldDeserializers() : Record<string, (node: ParseNode) => void> {
        return {...super.getFieldDeserializers(),
            "webSearchUrl": n => { this.webSearchUrl = n.getStringValue(); },
        };
    };
    /**
     * Serializes information the current object
     * @param writer Serialization writer to use to serialize this model
     */
    public serialize(writer: SerializationWriter) : void {
        if(!writer) throw new Error("writer cannot be undefined");
        super.serialize(writer);
    };
    /**
     * Gets the webSearchUrl property value. The URL To Bing's search result for this item.
     * @returns a string
     */
    public get webSearchUrl() {
        return this._webSearchUrl;
    };
    /**
     * Sets the webSearchUrl property value. The URL To Bing's search result for this item.
     * @param value Value to set for the webSearchUrl property.
     */
    public set webSearchUrl(value: string | undefined) {
        this._webSearchUrl = value;
    };
}

I would assume this._webSearchUrl should not be a read-only prop as it's being set later.

I used the following command to generate the models :

kiota generate -l typescript -o bingNews -d https://raw.githubusercontent.com/Azure/azure-rest-api-specs/master/specification/cognitiveservices/data-plane/NewsSearch/stable/v1.0/NewsSearch.json -c BingNewsClient -n Bing

Bug: investigate abstractions karma browser test failure

Test:

const baseUrl = "https://graph.microsoft.com/v1.0";

const requestInformation = new RequestInformation();
requestInformation.pathParameters["baseurl"] = baseUrl;
requestInformation.urlTemplate = "{+baseurl}/users";
assert.isNotNull(URL);
assert.equal(
  requestInformation.URL,
  "https://graph.microsoft.com/v1.0/users"
);

Error :
Chrome Headless 100.0.4896.60 (Windows 10) RequestInformation Should set request information uri FAILED
TypeError: Cannot read properties of undefined (reading 'Template')
at RequestInformation.get URL [as URL] (dist/es/test/index.js:12640:54)
at Context. (dist/es/test/index.js:12749:41)
Chrome Headless 100.0.4896.60 (Windows 10): Executed 1 of 1 (1 FAILED) (0.011 secs / 0.001 secs)
TOTAL: 1 FAILED, 0 SUCCESS

RequestBuilderGetQueryParameters sometimes have methods?

When using the following kiota command to generate a client kiota generate -l typescript -o nytmoviereviews -d https://raw.githubusercontent.com/nytimes/public_api_specs/master/movie_reviews/movie_reviews_v2.json -c NytMovieClient -n Nyt, the JsonRequestBuilderGetQueryParameters has a method and becomes impossible to use without reimplementing the method in the call we are doing (when using partial objects in TypeScript).

image

export class JsonRequestBuilderGetQueryParameters {
    /** Set this parameter to Y to limit the results to NYT Critics' Picks. To get only those movies that have not been highlighted by Times critics, specify critics-pick=N. (To get all reviews regardless of critics-pick status, simply omit this parameter.) */
    public criticsPick?: string | undefined;
    /** Positive integer, multiple of 20 */
    public offset?: number | undefined;
    /** Single date: YYYY-MM-DDStart and end date: YYYY-MM-DD;YYYY-MM-DDThe opening-date is the date the movie's opening date in the New York region. */
    public openingDate?: string | undefined;
    /** Sets the sort order of the results.Results ordered by-title are in ascending alphabetical order. Results ordered by one of the date parameters are in reverse chronological order.If you do not specify a sort order, the results will be ordered by publication-date. */
    public order?: string | undefined;
    /** Single date: YYYY-MM-DDStart and end date: YYYY-MM-DD;YYYY-MM-DDThe publication-date is the date the review was first published in The Times. */
    public publicationDate?: string | undefined;
    /** Search keywords; matches movie title and indexed termsTo limit your search to exact matches only, surround your search string with single quotation marks (e.g., query='28+days+later'). Otherwise, responses will include partial matches ("head words") as well as exact matches (e.g., president will match president, presidents and presidential).    If you specify multiple terms without quotation marks, they will be combined in an OR search.    If you omit the query parameter, your request will be equivalent to a reviews and NYT Critics' Picks request. */
    public query?: string | undefined;
    /** Include this parameter to limit your results to reviews by a specific critic. Reviewer names should be formatted like this: Manohla Dargis. */
    public reviewer?: string | undefined;
    /**
     * Maps the query parameters names to their encoded names for the URI template parsing.
     * @param originalName The original query parameter name in the class.
     * @returns a string
     */
    public getQueryParameter(originalName: string | undefined) : string {
        if(!originalName) throw new Error("originalName cannot be undefined");
        switch(originalName) {
            case "criticsPick": return "critics%2Dpick";
            case "openingDate": return "opening%2Ddate";
            case "publicationDate": return "publication%2Ddate";
            default: return originalName;
        }
    };
}

Case insensitive Records for headers and queryParameters

Some TODOs exists to have case insensitive records for headers and query parameters

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.