Giter VIP home page Giter VIP logo

flagsmith / flagsmith-nodejs-client Goto Github PK

View Code? Open in Web Editor NEW
24.0 15.0 21.0 1.09 MB

Flagsmith Node JS Client. Flagsmith lets you manage features flags across web, mobile and server side applications. Get builds out faster. Control who has access to new features.

Home Page: https://flagsmith.com/

License: BSD 3-Clause "New" or "Revised" License

JavaScript 0.23% Shell 0.09% TypeScript 99.68%
feature-flags feature-flag feature-flagging feature-flaggers bullet-train nodejs remote-config ci cd continuous-integration continuous-deployment continuous-delivery

flagsmith-nodejs-client's Introduction

Flagsmith NodeJS Client

npm version

The SDK clients for NodeJS https://www.flagsmith.com/. Flagsmith allows you to manage feature flags and remote config across multiple projects, environments and organisations.

Adding to your project

For full documentation visit https://docs.flagsmith.com/clients/server-side.

Contributing

Please read CONTRIBUTING.md for details on our code of conduct, and the process for submitting pull requests

Getting Help

If you encounter a bug or feature request we would like to hear about it. Before you submit an issue please search existing issues in order to prevent duplicates.

Testing

To run the local tests you need to run following command beforehand:

git submodule add [email protected]:Flagsmith/engine-test-data.git tests/engine/engine-tests/engine-test-data/

Get in touch

If you have any questions about our projects you can email [email protected].

Useful links

Website

Documentation

flagsmith-nodejs-client's People

Contributors

beeme1mr avatar benrometsch avatar dabeeeenster avatar dependabot[bot] avatar eder-zadrima avatar edsnloor avatar eilgin avatar eldar-gamisoniya avatar khvn26 avatar kyle-ssg avatar lukefanning avatar matthewelwell avatar novakzaballa avatar obax avatar palazari19 avatar raryson avatar rolodato avatar thebongy 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

Watchers

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

flagsmith-nodejs-client's Issues

Cannot use `DefaultFlag`

Both of the following snippets will log out undefined and attempting to instantiate a DefaultFlag object will throw an error, meaning that it's not possible to use the DefaultFlag constructor directly when defining default flag handler logic.

import { DefaultFlag } from "flagsmith-nodejs";

console.log(DefaultFlag);
const Flagsmith = require("flagsmith-nodejs");

console.log(Flagsmith.DefaultFlag);

Check if flag is "server-side only"

Hi,

is there a way to check if some feature is server-side only? I am trying to use this lib in my backend, but here for some business rules I need to know if some flag has the option "server-side only" set.

Thanks.

Local evaluation mode takes longer than expected

Reported by a customer that with local evaluation mode enabled, getting the state of an environment flag takes around 5/6ms which, given that it is just grabbing a key from an object, seems longer than it should be.

getIdentityFlags returns 405 on edge-proxy #51

Hi :)

The getIdentityFlags function returns a 405: Method Not Allowed error when calling the edge-proxy.
Calling through CURL with a POST request works, but fails the same way with GET.
This makes the SDK not compatible with the edge-proxy.

Note: Calling the self-hosted API directly works with both POST and GET.

@michaelrealt sorry we moved this into a private repo without realising you wouldnt be able to see it

gz#349

V2 Implement NodeCache

#16 allowed the ability to manage a cache of flags and users.

How it worked:

  1. When initialising the SDK with a cache parameter, it first validated the values here

  2. When retrieving a flag, it will query async cache.has("flags"), if it returns a value it will skip hitting the APIhere

  3. When flags have been retrieved it will set the cache with cache.set("flags") here

  4. When retrieving a flag for an identity, query async cache.has("identity_id"), if it returns a value it will skip hitting the API here

  5. When flags have been retrieved for an identity set the cache with cache.set("identity_id") here

[Question] What is the behaviour of `getIdentityFlags`

Hi,
I could run getIdentityFlags(email) and that automatically set the default features for that user. Is this the normal attended behaviour? I didn't insert the user! so there is not setIdentityFlags or initIdentityFlags etc to add user with default features manually ?

Thanks a lot :)

"Contains" and "Does not contain" operators throw on non-string/array trait values

Minimal reproduction example:

import Flagsmith from "flagsmith-nodejs"

const flagsmith = new Flagsmith({
  environmentKey: process.env.FLAGSMITH_SERVER_ENVIRONMENT_KEY,
  enableLocalEvaluation: true,
});

const { flags } = await flagsmith.getIdentityFlags("foo", {
  my_trait: null
})

console.log(flags)

with the following segment definition: my_trait Contains whatever

Result:

% bun index.ts
50 |     _a[constants_1.CONDITION_OPERATORS.LESS_THAN_INCLUSIVE] = function (thisValue, otherValue) {
51 |         return thisValue >= otherValue;
52 |     },
53 |     _a[constants_1.CONDITION_OPERATORS.NOT_EQUAL] = function (thisValue, otherValue) { return thisValue != otherValue; },
54 |     _a[constants_1.CONDITION_OPERATORS.CONTAINS] = function (thisValue, otherValue) {
55 |         return otherValue.includes(thisValue);
                    ^
TypeError: null is not an object (evaluating 'otherValue.includes')
      at /Users/rolodato/source/flagsmith/sandbox/node/node_modules/flagsmith-nodejs/build/flagsmith-engine/segments/models.js:55:16
      at /Users/rolodato/source/flagsmith/sandbox/node/node_modules/flagsmith-nodejs/build/flagsmith-engine/segments/models.js:110:16
      at traitsMatchSegmentCondition (/Users/rolodato/source/flagsmith/sandbox/node/node_modules/flagsmith-nodejs/build/flagsmith-engine/segments/evaluators.js:43:20)
      at /Users/rolodato/source/flagsmith/sandbox/node/node_modules/flagsmith-nodejs/build/flagsmith-engine/segments/evaluators.js:22:20
      at map (:1:21)
      at traitsMatchSegmentRule (/Users/rolodato/source/flagsmith/sandbox/node/node_modules/flagsmith-nodejs/build/flagsmith-engine/segments/evaluators.js:21:35)
      at /Users/rolodato/source/flagsmith/sandbox/node/node_modules/flagsmith-nodejs/build/flagsmith-engine/segments/evaluators.js:27:20
      at filter (:1:21)
      at traitsMatchSegmentRule (/Users/rolodato/source/flagsmith/sandbox/node/node_modules/flagsmith-nodejs/build/flagsmith-engine/segments/evaluators.js:26:9)
      at /Users/rolodato/source/flagsmith/sandbox/node/node_modules/flagsmith-nodejs/build/flagsmith-engine/segments/evaluators.js:15:20

This happens the same for Does not contain as well. We should be checking for null-sy values here:

otherValue.includes(thisValue),

return !traitValue.includes(this.value);

`import {Flags} from 'flagsmith-nodejs'` has Flags as undefined

Here's a sample program:

import Flagsmith, {Flags} from 'flagsmith-nodejs';
import * as f from 'flagsmith-nodejs';

console.log('Flagsmith', Flagsmith); // prints a function
console.log('Flags', Flags); // prints 'undefined', even though the types say that this is a class
console.log('f', f); prints {... Flags: undefined, ..., Flagsmith: [Function: Flagsmith] ...}

I'm using flagsmith-nodejs@^2.1.1 (from my yarn lock). Looks like the js is not matching up with the typings.

Please LMK how I can address this issue. Thanks!

Local evaluation doesn't return consistent results for multivariate features

Assuming that the variants do not change, the value of a multivariate feature for an identity should be the same value every time it is evaluated. This is not the case when using the NodeJS client in local evaluation mode. This simple (if crude) script demonstrates the issue:

// package.json

{
    "dependencies": {
        "flagsmith-nodejs": "*"
    }
}
// index.js

const Flagsmith = require('flagsmith-nodejs');

const flagsmithClient = new Flagsmith({
  environmentKey: '<token>',
  enableLocalEvaluation: true,
  enableAnalytics: true,
});

function sleep(ms) {
  return new Promise((resolve) => {
    setTimeout(resolve, ms);
  });
}

const run = async function () {
  const identifier = "my-identity";
  const featureName = "mv_feature";
  const initialIdentityFlags = await flagsmithClient.getIdentityFlags(identifier);
  const expectedIdentityValue = initialIdentityFlags.getFeatureValue("mv_feature");

  while (true) {
    console.log("-----------------");

    var envFlags = await flagsmithClient.getEnvironmentFlags();
    var envValue = envFlags.getFeatureValue(featureName);
    console.log(`value of mv_feature in environment: ${envValue}`);

    var identityFlags = await flagsmithClient.getIdentityFlags(identifier);
    var identityValue = identityFlags.getFeatureValue(featureName);
    console.log(`value of mv_feature for identity: ${identityValue}`);

    if (identityValue != expectedIdentityValue) {
      console.log("Identity value is not consistent!");
    }

    await sleep(1000);
  }
}

run();

Version 3.1 that added 10 second default timeout has bug.

This PR https://github.com/Flagsmith/flagsmith-nodejs-client/pull/128/files#diff-f4f0d60a954687ddb23b817e910fec0c113105f276c9d4aba5866f3c511466ebL23

The workaround is to just initialize the data.requestTimeoutSeconds to 10 seconds.

The intended default of 10 seconds is never passed to the retryFetch which is subsequently expecting a value in milliseconds. My guess this was added because without a positive numeric value it would never timeout, which has also caused me grief I just didn't know it until i reviewed this code.

from index.ts
These 2 lines

this.requestTimeoutSeconds = data.requestTimeoutSeconds;
this.requestTimeoutMs = data.requestTimeoutSeconds ? data.requestTimeoutSeconds * 1000 : undefined;

Should be changed to

this.requestTimeoutSeconds = data.requestTimeoutSeconds ?? this.requestTimeoutSeconds;
this.requestTimeoutMs = data.requestTimeoutSeconds ? data.requestTimeoutSeconds * 1000 : (this.requestTimeoutSeconds ? this.requestTimeoutSeconds * 1000 : undefined;)

or possibly

this.requestTimeoutSeconds = data.requestTimeoutSeconds ?? this.requestTimeoutSeconds;
this.requestTimeoutMs = this.requestTimeoutSeconds ? this.requestTimeoutSeconds * 1000 : undefined;

The this.requestTimeoutMs is always undefined and when passed into the retry fetch it defaults it 10 milliseconds.

This is defined in utils.ts

export const retryFetch = (
    url: string,
    fetchOptions: RequestInit,
    retries: number = 3,
    timeout: number = 10// set an overall timeout for this function. <-------- this value is Milliseconds and should be 10 * 1000
):

and called from here in index.ts

const data = await retryFetch(
    url,
    {
        agent: this.agent,
        method: method,
        body: JSON.stringify(body),
        headers: headers
    },
    this.retries,
    this.requestTimeoutMs || undefined,
);

SDK use wrong endpoint

https://github.com/Flagsmith/flagsmith-nodejs-client/blob/main/sdk/index.ts#L17

SDK use an edge endpoint but I think this is only used for the beta user so for non-beta user I am getting following error:

[PATH_IN_COMPUTER]\node_modules\flagsmith-nodejs\build\sdk\errors.js:30
        return _super !== null && _super.apply(this, arguments) || this;
                                         ^
Error: Invalid request made to Flagsmith API. Response status code: 404
    at new FlagsmithAPIError ([PATH_IN_COMPUTER]\node_modules\flagsmith-nodejs\build\sdk\errors.js:30:42)
    at Flagsmith.<anonymous> ([PATH_IN_COMPUTER]\node_modules\flagsmith-nodejs\build\sdk\index.js:339:35)
    at step ([PATH_IN_COMPUTER]\node_modules\flagsmith-nodejs\build\sdk\index.js:33:23)
    at Object.next ([PATH_IN_COMPUTER]\node_modules\flagsmith-nodejs\build\sdk\index.js:14:53)
    at fulfilled ([PATH_IN_COMPUTER]\node_modules\flagsmith-nodejs\build\sdk\index.js:5:58)
    at processTicksAndRejections (node:internal/process/task_queues:96:5)

If I change to https://api.flagsmith.com/api/v1/ URL, its working.

Typescript support

hi do you plan to introduce typescript support any time soon?

it seems we can't get the library to work with the import syntax

it throws an TypeError: _flagsmithNodejs.Flagsmith is not a constructor error

Set User Trait

In flagsmith-nodejs v1, it has function: setTrait(identity, key, value) ... this allows to store user's identity. Now, it's no longer there... can we have it back?

Have Typescript Module Support

Hi, I have started to use bullet-train-nodejs and I dont find Types module support. With this, I have created my own module type abstracting the bullet-train docs.

I think that can be extremely helpful if have natively in this module.
Here is the code:
https://gist.github.com/raryson/1c8e1345a7183209c9739a63361c5e6f

or

/**
 * Initialise the sdk against a particular environment
 */
export function init(config: {
  environmentID: string
  onError?: Function
  defaultFlags?: string[]
  api?: string
}): void

/**
 * Get the value of a particular feature e.g. bulletTrain.hasFeature("powerUserFeature")
 */
export function hasFeature(key: string): Promise<boolean>

/**
 * Get the value of a particular feature for a user e.g. bulletTrain.hasFeature("powerUserFeature", 1234)
 */
export function hasFeature(key: string, userId: string): Promise<boolean>

/**
 * Get the value of a particular feature e.g. bulletTrain.getValue("font_size")
 */
export function getValue(key: string): Promise<string>

/**
 * Get the value of a particular feature for a specificed user e.g. bulletTrain.getValue("font_size", 1234)
 */
export function getValue(key: string, userId: string): Promise<string>

/**
 * Trigger a manual fetch of the environment features
 */
export function getFlags(): Promise<IFlags>

/**
 * Trigger a manual fetch of the environment features for a given user id
 */
export function getFlagsForUser(userId: string): Promise<IFlags>

/**
 * Trigger a manual fetch of both the environment features and users' traits for a given user id
 */
export function getUserIdentity(userId: string): Promise<IUserIdentity>

/**
 * Trigger a manual fetch of a specific trait for a given user id
 */
export function getTrait(userId: string, key: string): Promise<ITraits>

/**
 * Set a specific trait for a given user id
 */
export function setTrait(
  userId: string,
  key: string,
  value: string
): IUserIdentity

interface IBulletTrainFeature {
  enabled: boolean
  value?: string
}

interface IFlags {
  [key: string]: IBulletTrainFeature
}

interface ITraits {
  [key: string]: string
}

interface IUserIdentity {
  flags: IBulletTrainFeature
  traits: ITraits
}

Regression between v2.1.1 and v2.2.0

This expression is not constructable.
Type 'typeof import(".../node_modules/flagsmith-nodejs/build/index")' has no construct signatures.ts(2351)

The same error was there with v2.1.0 and solved in v2.1.1, now with v2.2.0, we have the same error.

v2.1.0
image

v2.1.1
image

v2.2.0
image

Using "IN" operator in a trait with a number type causes nodejs (local evaluation) to throw

How are you running Flagsmith

  • Self Hosted with Docker
  • Self Hosted with Kubernetes
  • Self Hosted at flagsmith.com
  • Some other way (add details in description below)

Describe the bug

FlagSmith (local evaluation) will fail to evaluate flags when a segment does a IN condition in a trait that has an integer value.

Failed to get Flagsmith flags TypeError: otherValue.includes is not a function
[dev:*server]     at exports.matchingFunctions._a.<computed> (webpack-internal:///(sc_server)/../node_modules/flagsmith-nodejs/build/flagsmith-engine/segments/models.js:70:23)
[dev:*server]     at SegmentConditionModel.matchesTraitValue (webpack-internal:///(sc_server)/../node_modules/flagsmith-nodejs/build/flagsmith-engine/segments/models.js:132:16)
[dev:*server]     at traitsMatchSegmentCondition (webpack-internal:///(sc_server)/../node_modules/flagsmith-nodejs/build/flagsmith-engine/segments/evaluators.js:45:30)
[dev:*server]     at eval (webpack-internal:///(sc_server)/../node_modules/flagsmith-nodejs/build/flagsmith-engine/segments/evaluators.js:22:16)
[dev:*server]     at Array.map (<anonymous>)
[dev:*server]     at traitsMatchSegmentRule (webpack-internal:///(sc_server)/../node_modules/flagsmith-nodejs/build/flagsmith-engine/segments/evaluators.js:21:98)
[dev:*server]     at eval (webpack-internal:///(sc_server)/../node_modules/flagsmith-nodejs/build/flagsmith-engine/segments/evaluators.js:25:16)
[dev:*server]     at Array.filter (<anonymous>)
[dev:*server]     at traitsMatchSegmentRule (webpack-internal:///(sc_server)/../node_modules/flagsmith-nodejs/build/flagsmith-engine/segments/evaluators.js:24:44)
[dev:*server]     at eval (webpack-internal:///(sc_server)/../node_modules/flagsmith-nodejs/build/flagsmith-engine/segments/evaluators.js:16:16)
[dev:*server]     at Array.filter (<anonymous>)
[dev:*server]     at evaluateIdentityInSegment (webpack-internal:///(sc_server)/../node_modules/flagsmith-nodejs/build/flagsmith-engine/segments/evaluators.js:15:54)
[dev:*server]     at eval (webpack-internal:///(sc_server)/../node_modules/flagsmith-nodejs/build/flagsmith-engine/segments/evaluators.js:10:16)
[dev:*server]     at Array.filter (<anonymous>)
[dev:*server]     at getIdentitySegments (webpack-internal:///(sc_server)/../node_modules/flagsmith-nodejs/build/flagsmith-engine/segments/evaluators.js:9:41)
[dev:*server]     at getIdentityFeatureStatesDict (webpack-internal:///(sc_server)/../node_modules/flagsmith-nodejs/build/flagsmith-engine/index.js:92:65)
[dev:*server]     at getIdentityFeatureStates (webpack-internal:///(sc_server)/../node_modules/flagsmith-nodejs/build/flagsmith-engine/index.js:162:39)
[dev:*server]     at Flagsmith.eval (webpack-internal:///(sc_server)/../node_modules/flagsmith-nodejs/build/sdk/index.js:650:89)
[dev:*server]     at step (webpack-internal:///(sc_server)/../node_modules/flagsmith-nodejs/build/sdk/index.js:107:23)
[dev:*server]     at Object.eval (webpack-internal:///(sc_server)/../node_modules/flagsmith-nodejs/build/sdk/index.js:48:20)
[dev:*server]     at eval (webpack-internal:///(sc_server)/../node_modules/flagsmith-nodejs/build/sdk/index.js:26:71)
[dev:*server]     at new Promise (<anonymous>)
[dev:*server]     at __awaiter (webpack-internal:///(sc_server)/../node_modules/flagsmith-nodejs/build/sdk/index.js:8:12)
[dev:*server]     at Flagsmith.getIdentityFlagsFromDocument (webpack-internal:///(sc_server)/../node_modules/flagsmith-nodejs/build/sdk/index.js:639:16)
[dev:*server]     at eval (webpack-internal:///(sc_server)/../node_modules/flagsmith-nodejs/build/sdk/index.js:402:55)

Steps To Reproduce

  1. Create a flag
  2. Create a segment override for that flag
  3. Setup a rule. example trait IN 2024
  4. In the code, try to fetch flags, passing example trait as integer 2024
  5. See an error being thrown

Expected behavior

FlagSmith shouldn't let an invalid rule break all flags

Screenshots

No response

Feature values always have type string when using local evaluation mode

We've started seeing an issue where number values like this one are being returned as type string when enableLocalEvaluation is set to true.
Screenshot 2024-03-01 at 11 38 49

Using this test script I've validated that the local evaluation causes the change in behaviour (thanks Matt for the help ๐Ÿ™Œ )

This has been tested on flagsmith-nodejs versions 2.5.1 and 3.2.0

import Flagsmith from 'flagsmith-nodejs';

async function main(): Promise<void> {
    const flagClient = new Flagsmith({
        environmentKey: 'ser.key',
        enableLocalEvaluation: true,
        enableAnalytics: false,
    });

    const environmentFlags = await flagClient.getEnvironmentFlags();
    const flag = environmentFlags.getFlag('daisy.settlements.timeout');
    console.log(`flagClient.apiUrl=${flagClient.apiUrl}`);
    console.log(`flagClient.enableLocalEvaluation=${flagClient.enableLocalEvaluation}`);
    console.log(JSON.stringify(flag));
    console.log(`typeof flag.value=${typeof flag.value}`);

    await flagClient.close();
}

void main();

enableLocalEvaluation=true

flagClient.apiUrl=https://edge.api.flagsmith.com/api/v1/
flagClient.enableLocalEvaluation=true
{"value":"60000","enabled":true,"isDefault":false,"featureId":62178,"featureName":"daisy.settlements.timeout"}
typeof flag.value=string

enableLocalEvaluation=false

flagClient.apiUrl=https://edge.api.flagsmith.com/api/v1/
flagClient.enableLocalEvaluation=false
{"value":60000,"enabled":true,"isDefault":false,"featureId":62178,"featureName":"daisy.settlements.timeout"}
typeof flag.value=number

Cache TypeScript support

I am using this library with nodecache as below.

import nodecache from "node-cache";

const flagsmith = new Flagsmith({
    environmentKey: "xxx",
    cache: new nodecache({
        stdTTL: 10,
        checkperiod: 10,
    }),
});

export default flagsmith;

I get the following type error on cache:

Type 'NodeCache' is not assignable to type 'FlagsmithCache'.
  The types returned by 'get(...)' are incompatible between these types.
    Type 'Promise<Flags> | undefined' is not assignable to type 'Promise<Flags>'.
      Type 'undefined' is not assignable to type 'Promise<Flags>'.

I wonder if this is a problem with the Flagsmith Types? It seems to make sense that the get method will return undefined when the key does not exist in the cache.

requestTimeoutSeconds has no default value

Docs specify the following:

 /*
   The network timeout in seconds.
   Optional.
   Defaults to 10 seconds
   */
 requestTimeoutSeconds: 30,

But it seems like the default value for nodejs client is missing:

this.requestTimeoutSeconds = data.requestTimeoutSeconds;
this.requestTimeoutMs = data.requestTimeoutSeconds ? data.requestTimeoutSeconds * 1000 : undefined;

timeout?: number // set an overall timeout for this function

Any API call is failing while server is up and running

Hi,
I'm trying to connect between docker images and having this code

new Flagsmith({
        environmentKey: configService.get('FLAGSMITH_KEY') || '',
        apiUrl: 'http://172.17.0.1:9000/',
      });

The first call to the API using:
flagsmith?.getEnvironmentFlags(); fails with

Error: Invalid request made to Flagsmith API. Response status code: 500

The issue is I can curl outside Docker, that means server is up and running fine:
curl 'localhost:9000/api/v1/flags/' -H 'x-environment-key: ##########xN4y7cm'

[{"id":1,"feature":{"id":1,"name":"test_feature","created_date":"2023-01-23T14:32:09.986573Z","description":null,"initial_value":"holla","default_enabled":false,"type":"STANDARD"},"feature_state_value":"holla","environment":1,"identity":null,"feature_segment":null,"enabled":false}]

Thanks a lot :)

IN segment operator

Add IN option from the drop-down and in the value they could enter a comma-separated list of strings/numbers.

gz#68

Add Proxy Support

Problem

The Flagsmith Node client does not currently support usage behind a proxy. It does not currently expose any way for users to pass proxy config through to Node fetch from the client configuration.

Possible Solution

Allow users to pass the proxy configuration through the client on init. It could look something like this:

flagsmith.init({ proxy: { host: 'myHost', port:'myPort' }})

The client would then provide the appropriate HTTP agent to fetch based on the config given.

An error importing in TypeScript under Nest

Hi,
I'm having the following error when importing the package to initialize the first connection.

TypeError: flagsmith_nodejs_1.default is not a constructor

Typescript compiles
new Flagsmith({})
to:
const flagsmith = new flagsmith_nodejs_1.default({})

Thanks a lot for your consideration

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.