GithubHelp home page GithubHelp logo

vitaly-t / iter-ops Goto Github PK

View Code? Open in Web Editor NEW
127.0 4.0 5.0 3.31 MB

Basic operations on iterables

Home Page: https://vitaly-t.github.io/iter-ops

License: MIT License

TypeScript 100.00%
iterables functional async typescript iterable-extensions

iter-ops's Introduction

iter-ops

Build Status

About

Basic operations on synchronous + asynchronous iterables, strictly for JavaScript native types.

image

We do not use any synthetic types / wrappers here, like Observable in RXJS, etc. It is strictly an iterable on the input, and an iterable on the output, for maximum performance, simplicity and compatibility (see Rationale).

Related repo: iter-ops-extras - addition to the main API.

Installation

$ npm i iter-ops

Usage

  • Synchronous pipeline:
import {pipe, filter, map} from 'iter-ops';

const input = [1, 2, 3, 4, 5];

const i = pipe(
    input,
    filter((a) => a % 2 === 0), // find even numbers
    map((value) => ({value})) // remap into objects
);

console.log(...i); //=> {value: 2}, {value: 4}
  • Asynchronous pipeline:
import {pipe, toAsync, distinct, delay} from 'iter-ops';

const input = [1, 2, 2, 3, 3, 4];

const i = pipe(
    toAsync(input), // make asynchronous
    distinct(), // emit unique numbers
    delay(1000) // delay each value by 1s
);
// or you can replace `pipe` + `toAsync` with just `pipeAsync`

(async function () {
    for await (const a of i) {
        console.log(a); //=> 1, 2, 3, 4 (with 1s delay)
    }
})();

See also...

API

Function pipe takes any iterable, applies all specified operators to it, and returns an extended iterable. For strict type of iterables, there are also pipeSync and pipeAsync.

Standard Operators:

All standard operators implement the same logic as Array does:

  • concat - merges current iterable with multiple values, iterators or iterables.
  • every - checks if all elements pass the predicate test.
  • filter - standard filter processor, filtering by predicate.
  • flat - flattens/expands sub-iterable elements.
  • flatMap - remaps + flattens sub-iterable elements.
  • map - standard mapping processor, remapping by predicate.
  • reduce - standard reduce processor.
  • some - checks if any element passes the predicate test.

Extended Operators:

  • aggregate - executes an aggregate on accumulated values - see Aggregates.
  • catchError - catches iteration errors - see Error Handling.
  • count - counts values, and produces a one-value iterable.
  • defaultEmpty - adds default to an empty iterable.
  • distinct - emits unique values, with optional key selector.
  • drain - drains the iterable, and then ends it.
  • empty - produces an empty iterable.
  • first - produces a one-value iterable, with the first emitted value.
  • indexBy - emits indexed values that pass the predicate test.
  • isEmpty - produces a one-value iterable, indicating if the source is empty.
  • last - produces a one-value iterable, with the last emitted value.
  • onEnd - notifies of the end of a successful iteration.
  • page - splits values into pages of fixed size (last page can be smaller).
  • repeat - repeats iterable values.
  • skip - starts emitting values after certain count.
  • skipUntil - skips until the predicate returns a truthy value.
  • skipWhile - skips while the predicate returns a truthy value.
  • split - splits values into separate lists - see Split.
  • spread - spreads iterable values.
  • take - emits up to certain number of values.
  • takeLast - emits up to certain number of the last values.
  • takeUntil - emits until the predicate returns a truthy value.
  • takeWhile - emits while the predicate returns a truthy value.
  • tap - taps into each value, without changing the output.
  • timeout - ends iteration after N milliseconds.
  • timing - measures timings for each value.
  • toArray - accumulates values into an array.
  • zip - zips values together, into an array.

Custom Operators:

See iter-ops-extras - a collection of custom operators (ones based on existing operators).

Resources:

iter-ops's People

Contributors

crossing avatar rebeccastevens avatar renovate[bot] avatar vitaly-t 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

iter-ops's Issues

Iterable made from an iterator can still only be iterated once

I've just noticed another potential issue.

The iterable made from an iterator with this part of the code:

iter-ops/src/helpers.ts

Lines 122 to 143 in ccebb8e

const next = i?.next;
if (typeof next === 'function') {
const value = next.call(i); // this line may throw (outside the pipeline)
let s: any = isPromiseLike(value) && $A;
if (s || (typeof value === 'object' && 'value' in (value ?? {}))) {
s = s || $S;
return {
[s]() {
let started: boolean;
return {
next() {
if (started) {
return i.next();
}
started = true;
return value;
},
};
},
};
}
}

would only be iterable once. As once the original iterator has been exhausted; any new iterator created from the iterable will also be exhausted.

If this function only internally used it wouldn't be an issue as it can be guaranteed that the iterable is never iterated more than once. But if that is the case; there could be performance gains if instead the iterator was just used directly.

2.0.0 checkpoints

This is to track what's left before 2.0.0 is released.

  • Check that all operators work and cast types as expected.
  • Complete #103
  • Complete #135

I'm sure there can be more to add or improve, but those won't be breaking changes, so can be added later.

@RebeccaStevens Anything else you can think of as necessary for 2.0.0 release?

Initial value delayed in waitRace operator

Current implementation (v2.2.1) of the new operator waitRace delays initial resolution when the source is slower than the resolution speed.

The example below demonstrates that issue...

import {delay, map, pipeAsync, waitRace} from 'iter-ops';

function* gen(n) {
    while (n > 0) {
        yield n--;
    }
}

const i = pipeAsync(gen(10), delay(1000), map(a => Promise.resolve(a)), waitRace(5));

(async function () {
    for await(const a of i) {
        console.log(a);
    }
})();

In the example, the first value is expected to be resolved after 1s, but instead it is resolved after 2s, i.e. waiting for the next value to be resolved first, which is wrong. Something is wrong in the implementation logic.

For further details, see this discussion.


@RebeccaStevens this is a tricky one, could really use some help here ;)

@bergus I have tried to apply your solution from StackOverflow, but wasn't successful. Maybe it can be reduced to a simpler fix on the existing code?

Benchmark Readme statement about RXJS subscription is incorrect.

The benchmark readme states:

This library performs about 2.5x faster than rxjs synchronous pipeline. However, just as you add a single subscription in rxjs ( which is inevitable with rxjs), then iter-ops performance is about 5x times better. So ultimately, this library can process synchronous iterables about 5x times faster than synchronous rxjs.

However upon examining the benchmark code I think this is a misunderstanding.

image

image

The firstValueFrom source can be found here: https://github.com/ReactiveX/rxjs/blob/7.8.1/src/internal/firstValueFrom.ts#L11-L75

So there is actually no need to do a separate subscription. If you want to simulate where there are multiple subscribers you would want to convert the sequence over to a shared observable using the share operator.

Operator concurrencyFork should handle pipeline-construction errors

This feature is a follow-up on the new operator concurrencyFork, to make it handle any errors that a custom operator may throw during the pipeline construction, i.e. during concurrencyFork callback.

Normal error handling in this library is done via operator catchError, which however can only handle iteration-time errors. This feature is to handle pipeline construction-time, and turn them into iteration-time errors, so operator catchError can handle those.

start vs stop logic inconsistency

We may have a bit of issue (or not) with the inconsistency: start logic is inclusive, while stop is exclusive, i.e. we start from the element that passes the predicate, but we stop with the element that's prior to the predicate that passed.

Should they not be both inclusive or exclusive?

@RebeccaStevens What do you think?

toIterable() fails with completed iterators

I have a case where I need to wrap a possibly-completed iterator as an iterator to pipe() through some other things. I know the result will be an empty iterator, but I don't have a way to test for this before building the pipe() chain.

In debugging, I found that toIterable() calls a typeguard function isIteratorResult:

export function isIteratorResult<T, CastGeneric = unknown>(
and fails to see the next() result as an iterator result. It wraps it as a SingleIterator, which breaks my app because it isn't expecting an iterator result.

isIteratorResult tests for value, but a completed iterator does not return a value. It does return done in all cases:

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Iterators_and_Generators#iterators

The fix is to look for a done property instead of a value property.

Review toIterable logic

I recently reviewed all tests, to make sure we have 100% coverage across the board.

One last case that I could not figure out was the missing test for toIterable.

Test coverage says that else case is not covered, and I could not figure out how to get it covered.

@RebeccaStevens Since you rewrote this helper some time ago, I thought I'd assign it to you for review, to either update the code or add the missing test.

P.S. I wouldn't fuss about such things with operators, but this thing is quite fundamental for the library ;)

image

Further iterable extension with method `Add`

Type IterableExt may benefit greatly by allowing to append additional operators to the end of an existing pipeline, without creating a new pipeline:

let i = pipe([1, 2, 3], map(a => a  * 10));
if(/* some condition */) {
    a.add(filter(f => f % 2 === 0)); // append an operator conditionally
}

Method add should also support multiple operators, to be added in one call.

Extend boolean predicates for Promise<boolean>

All operators that provide a callback for a truthy/falsy value, should also support return of Promise<boolean> within an asynchronous pipeline:

  • filter - done in v1.4.3
  • indexBy - done in v1.4.3
  • start - done in v.1.4.4
  • stop - done in v.1.4.4
  • first - done in v.1.4.5
  • last - done in v.1.4.5
  • some - done in v.1.4.6
  • every - done in v.1.4.6
  • split - done in v.1.4.7

The only operators that supported optional Promise<boolean> from start: repeat and retry.

"reduce" callback gets incorrect index

import {pipe, reduce} from 'iter-ops';

const input = [3, 0, -2, 5, 9, 4];

const i = pipe(input, reduce((prev, curr, idx, state) => {
    console.log(idx);
    return prev;
}));

const r = [...i];

Expected Output:
1 2 3 4 5

Actual Output
2 4 6 8 10

As a test against the standard reduce...

  • without initial value:
input.reduce((prev, curr, idx)=>{
    console.log(idx);
    return prev;
});

Outputs: 1 2 3 4 5

  • with initial value:
input.reduce((prev, curr, idx)=>{
    console.log(idx);
    return prev;
}, 0);

Outputs: 0 1 2 3 4 5

Documentation issue for `pipe` method

PR #132 created a problem for generating documentation.

@RebeccaStevens You changed pipe signature from function to const, and typedoc cannot pick it up correctly anymore, the pipe is altogether skipped from documentation, which in turn results in unresolved links.

You can check for errors by running npm run doc.

Indexes for split operator

Callback parameter index = {start, list, split} for operator split, may need some work. Property start is definitely all good, but list and split may need some adjustment. They work as far as all the tests go, but I'm not sure if tests for list and split are all correct.

This is not necessarily a bug, just something that needs another close look into.

Typed with `undefined`

I've stumbled across this library as we're finding that we're doing a lot of conversions from x.values() to arrays in order to then run the appropriate functions on them. I figured I'd give this library a go in order to reduce the amount of code that we have to write.

However, it seems that there always appears to be a chance that pipe is going to return undefined for the first field (according to the types) that it exposes to the consumer. This means that I'm forever having to write checks to ensure that the value I'm pulling out is indeed there, and isn't undefined like the types indicate it might be.

Is there another way that I'm meant to be pulling out the value? This is especially apparent when calling functions that should return booleans such as some. Am I not meant to be using first?

I checked the tests defined in here for some guidance, but they all seem to assume that the value is going to be present, no matter what the case.

Any guidance on this would be fantastic!

pipeSync should throw an error on any asynchronous input

When pipeSync is called explicitly by the client, and passed an asynchronous iterable, it should throw an error:

TypeError(`A synchronous iterable was expected: ${JSON.stringify(input)`)

...because this represents an implementation error.

Presently, the input type is only controlled at compile time, and the function erroneously produces an asynchronous output.


@RebeccaStevens I'm assigning this to yourself, if you want to have more fun with this library ;)

Extend "last" operator with iteration state

Feature

Add support for IterationState to operator last.

Why?

Let's say we want to search for border values, like min / max inside an iterable. Because operator last doesn't support iteration state, like filter does, we have to use a combination of filter + last:

import {pipe, last, filter} from 'iter-ops';

const input = [3, 0, -2, 5, 9, 4];

const iMin = pipe(input, filter((value, idx, state) => {
    if (!idx || value < state.min) {
        state.min = value;
        return true;
    }
}), last());

const iMax = pipe(input, filter((value, idx, state) => {
    if (!idx || value > state.max) {
        state.max = value;
        return true;
    }
}), last());

console.log(...iMin, ...iMax); //=> -2 9

Once we have operator last support the iteration state, we will be able to simplify that example to just using operator last:

import {pipe, last} from 'iter-ops';

const input = [3, 0, -2, 5, 9, 4];

const iMin = pipe(input, last((value, idx, state) => {
    if (!idx || value < state.min) {
        state.min = value;
        return true;
    }
}));

const iMax = pipe(input, last((value, idx, state) => {
    if (!idx || value > state.max) {
        state.max = value;
        return true;
    }
}));

console.log(...iMin, ...iMax); //=> -2 9

Note that operator last already supports a callback, and it's just missing the iteration state support.


@Jason3S Per your question, a good example of why support for iteration state is quite beneficial, and makes processing logic much simpler ;)

@RebeccaStevens What do you reckon? ;)

Add operator flatMap

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/flatMap

Related to #107

There is already the map and spread operators which together is almost replicate the behavior of flatMap but I believe it would be best to add a specific flatMap operator; for the same reasoning flatMap was added to Array when it already had map and flat.

For example:

const data = [1, 2, 3, 4];

pipe(data, map(value => [value, value * 10]]), spread());      // => [1, 10, 2, 20, 3, 30, 4, 40]
pipe(data, flatMap(value => [value, value * 10]));             // => [1, 10, 2, 20, 3, 30, 4, 40]

Removing async/await code

When I started on this library, a lot of time asynchronous implementation was simply copied from synchronous, with added async/await usage, in order to save time (plus some operators have fairly complex logic).

However, performance tests showed that native .then.catch solution performs way better than async/await, so I've been converting async operators to use .then.catch syntax only (see #76).

There are still some operators remain that rely on async/await syntax. Mostly it is because they have fairly complex logic, which makes conversion quite a challenge.

This issue is to track the async operators that still need to be converted:

  • concat - this should take priority, as the most frequently used one ๐Ÿ
  • defaultEmpty
  • repeat
  • split - the most complex operator in the library, likely with some issues - #33

P.S. @RebeccaStevens In case nothing else challenges you here ๐Ÿ˜‰


Related: #76

ES6 target / ESM distribution

Hi, would it be possible to release a version of the library compiled to ES6, so using and exporting native modules?
Recent versions of Angular complain about non-ESM dependencies (i.e. AMD or CommonJS).

Improve onEnd details

Operator onEnd can be improved with extra details about duration, by providing min/max duration, along with indexes of where those were reached.

This is to help locate deviations in iterable performance.

For example, have duration inside IIterationSummary of the following type:

export interface IDuration {
    average: number; // averge duration
    max?: {delay, index, value} // delay = waiting time, index when the value was reached, the value
    min?: {delay, index, value}; // same as above
    total: number; // total duration
}

This would be a breaking change, because duration property changes.

`flat` is not consistent with `Array.flat`

The docs say that the flat operator is consistent with Array.flat, but then show an example that is not consistent, as Array.flat leaves non-array values alone and would not expand one into separate characters.

console.log( ...pipe( [ 'one', [ 2, 3, [ 4, 5 ] ] ], flat( 2 ) ) );

o n e 2 3 4 5

console.log( ...[ 'one', [ 2, 3, [ 4, 5 ] ] ].flat( 2 ) );

one 2 3 4 5

is Promise or is Promise-like?

Should this be checking if the type is a Promise (as the name suggests) or if it is just Promise-like (as the implementation suggests)?

iter-ops/src/utils.ts

Lines 112 to 117 in 523b588

/**
* Verifies if value is a promise.
*/
export function isPromise(a: any): boolean {
return a && typeof a.then === 'function';
}

The following part of the code would indicated that it's supposed to check that it is an actual Promise (due to the return type of the next function).

iter-ops/src/helpers.ts

Lines 144 to 162 in 523b588

if (isPromise(i)) {
return {
[$A](): AsyncIterator<T> {
let finished: boolean;
return {
next(): Promise<IteratorResult<T>> {
if (finished) {
return Promise.resolve({
value: undefined,
done: true,
});
}
finished = true;
return i.then((value: T) => ({value, done: false}));
},
};
},
};
}

timeout operator

New operator timeout that ends iteration after a specified number of milliseconds:

timeout(ms: number)

@RebeccaStevens What do you think? ๐Ÿ˜‰

UPDATE

Previously, I thought this was achievable via operator stop, but then I realized it would not account for the time spent getting the first value, so it's no good.

This new operator simplifies it a lot, and it accounts for the first iteration value also.

Operators "wait" and "waitRace" should simply forward in sync mode

Now that I have spent some time writing custom operators - see iter-ops-extras, it has become evident that operator wait throwing an error when inside a synchronous pipeline creates a limitation for custom operators.

There are cases when a custom operator supports an asynchronous callback, and needs to make use of wait regardless of the pipeline type (it cannot know the pipeline type).

For example, in a sequence of map(async ()=>{}) -> wait() -> spread, where an operator allows asynchronous callback, then uses map, which then if not waited for, will throw inside spread because it requires iterables only.

Therefore, operator wait inside synchronous pipeline should just forward into the source iterable, and not throw any error. And for consistency reasons, operator waitRace should do the same.

Add operator flat

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/flat

There is already the spread operator which is almost the same as flat; except that spread throws when it encounters a non-iterable while flat should keep the value as is.

For example:

const data = [1, [2, 3], 4, [[5, 6]]];

pipe(data, spread());      // => throws "Value at index 0 is not iterable: 1"
pipe(data, flat());        // => [1, 2, 3, 4, [5, 6]];

Alternatively the behavior of spread could be altered to work like flat.

Operator timeout should forward callback errors

Currently, if you throw an error while handling callback of operator timeout, it is not handled. It should be caught and passed on down the chain.

Why it matters: you may want to handle a callback, and turn it into an error that will be handled in a generic way, via catchError.

It can be considered both a bug (more likely) and an enhancement.

NOTE: This issue primarily affects the async version of the operator.

Yarn 3

Do we want to update to yarn 3?

Error handlers should be able to return nothing

Initial implementation of error handlers (catchError operator) requires that the callback either throws an error, or returns a compatible-type value.

This seems to have a logical flaw. Catch-error callbacks should be allowed to return nothing and not throw any error, and that should result in skipping the value that resulted in an error.

A change is required for this, which will be a breaking change for the error handling.

Dependency Dashboard

This issue lists Renovate updates and detected dependencies. Read the Dependency Dashboard docs to learn more.

Ignored or Blocked

These are blocked by an existing closed PR and will not be recreated unless you click a checkbox below.

Detected dependencies

github-actions
.github/workflows/ci.yml
  • fkirc/skip-duplicate-actions v5.2.0
  • actions/checkout v3
  • actions/setup-node v3
  • actions/checkout v3
  • actions/setup-node v3
  • actions/setup-node v3
  • codecov/codecov-action v3
npm
benchmarks/package.json
  • rxjs 7.5.7
package.json
  • @istanbuljs/nyc-config-typescript 1.0.2
  • @rollup/plugin-node-resolve 15.0.1
  • @rollup/plugin-typescript 9.0.2
  • @types/chai 4.3.3
  • @types/estree 1.0.0
  • @types/mocha 10.0.0
  • @types/node 18.11.9
  • @types/rollup-plugin-auto-external 2.0.2
  • @typescript-eslint/eslint-plugin 5.42.0
  • @typescript-eslint/parser 5.42.0
  • chai 4.3.6
  • cross-env 7.0.3
  • cspell 6.13.3
  • eslint 8.26.0
  • eslint-config-prettier 8.5.0
  • eslint-plugin-prettier 4.2.1
  • mocha 10.1.0
  • nyc 15.1.0
  • prettier 2.7.1
  • prettier-plugin-packagejson 2.3.0
  • rimraf 3.0.2
  • rollup 2.79.1
  • rollup-plugin-auto-external 2.0.0
  • rollup-plugin-dts 4.2.3
  • rollup-plugin-gzip 3.1.0
  • rollup-plugin-terser 7.0.2
  • ts-node 10.9.1
  • tsd 0.24.1
  • tslib 2.4.1
  • typedoc 0.23.19
  • typescript 4.8.4
  • node >=14
  • yarn 3.2.4

  • Check this box to trigger a request for Renovate to run again on this repository

This doesn't look right

iter-ops/src/helpers.ts

Lines 144 to 162 in f752a64

if (isPromiseLike(i)) {
return {
[$A](): AsyncIterator<T> {
let finished: boolean;
return {
next(): Promise<IteratorResult<T>> {
if (finished) {
return Promise.resolve({
value: undefined,
done: true,
});
}
finished = true;
return i.then((value: T) => ({value, done: false}));
},
};
},
};
}

So the code is saying:
If the value to get an iterable of is a promise. Wait for the process to fulfill. Return the promise's result wrapped in an iterator result; marked as not the last element of the iterator. The next value of the iterator is undefined and is the last element of the iterator.

This probably wasn't what was intended.

If I remove that code; the only test that fails is the one specifically testing this. So none of the actual operations are using this behavior and thus I believe this is probably left over code that isn't actually used anymore.

Type issues

I've looked into the type issues we are having, and it seems like we may be hitting a TypeScript bug. I've made an issue here.

I'll see if I can come up with a work-around.

Operator waitRace should deactivate when cacheSize < 2

At the moment, operator waitRace applies generic processing logic when cacheSize is < 2.

It should instead check if cacheSize < 2, and then deactivate fully, i.e. behave like operator wait.

This is because there is no point applying all the complex processing logic to something that won't be able to change the output anyway, because the cache size of 1 does not permit any resolution racing.

This is just a performance optimization ๐Ÿš€


This is some improvement, following #182

Unknown type issue from Beta 4

@RebeccaStevens

After PR #132, it seems, we start getting unknown type resolved...

image

See 2.0.0.0-beta 4 version published.

UPDATE

Actually, after some time, my IDE started showing spread with correct type, but not flat:

image

Add support for infinite timeouts

Similar to operator delay, new operator timeout should support infinite timeouts when the delay is passed in as a negative number.

This will let you effectively deactivate the timeout without having to remove the operator from the pipeline.

pipeAsync should be able to accept Promise as input

At the moment, we have to use toIterable when input is a promise. But for pipeAsync there is no reason not to do so automatically.

Function pipeAsync should allow Promise as input, and automatically call toIterable (internally):

import {pipeAsync, flat} from 'iter-ops';

const p = Promise.resolve([1, 2, 3, 4, 5]);

const i = pipeAsync(p, flat()); // this should just work, without using toIterable(p)

(async function () {
    for await (const a of i) {
        console.log(a); // 1, 2, 3, 4, 5
    }
})();

@RebeccaStevens More fun, if you are interested ;)

Remove IterationState from timeout

I didn't give it sufficient thought when I added support for IterationState within the optional callback of operator timeout.

The callback function is not involved into the iteration, it is called only once, and only if timeout occurs.

I need to remove support IterationState from there, it is completely pointless in that context.

Unnecessary aliasing

Aliasing Symbol.iterator and Symbol.asyncIterator to shorter names no longer has any benefit. Terser will automatically take care of this.

iter-ops/src/types.ts

Lines 133 to 137 in 40315f0

/**
* These are for code abbreviation + smaller bundles:
*/
export const $S: typeof Symbol.iterator = Symbol.iterator;
export const $A: typeof Symbol.asyncIterator = Symbol.asyncIterator;

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.