Code Monkey home page Code Monkey logo

submillisecond's Introduction

submillisecond

A lunatic web framework for the Rust language.

Submillisecond is a backend web framework around the Rust language, WebAssembly's security and the lunatic scheduler.

This is an early stage project, probably has bugs and the API is still changing. It's also important to point out that many Rust crates don't compile to WebAssembly yet and can't be used with submillisecond.

If you would like to ask for help or just follow the discussions around Lunatic & submillisecond, join our discord server.

Features

  • Fast compilation times
  • async-free - All preemption and scheduling is done by lunatic
  • strong security - Each request is handled in a separate lunatic process
  • Batteries included
    • Cookies
    • Json
    • Logging
    • Websockets
  • Submillisecond LiveView - Frontend web framework

Code example

use submillisecond::{router, Application};

fn index() -> &'static str {
    "Hello :)"
}

fn main() -> std::io::Result<()> {
    Application::new(router! {
        GET "/" => index
    })
    .serve("0.0.0.0:3000")
}

Getting started with lunatic

To run the example you will first need to download the lunatic runtime by following the installation steps in this repository. The runtime is just a single executable and runs on Windows, macOS and Linux. If you have already Rust installed, you can get it with:

cargo install lunatic-runtime

Lunatic applications need to be compiled to WebAssembly before they can be executed by the runtime. Rust has great support for WebAssembly and you can build a lunatic compatible application just by passing the --target=wasm32-wasi flag to cargo, e.g:

# Add the WebAssembly target
rustup target add wasm32-wasi
# Build the app
cargo build --release --target=wasm32-wasi

This will generate a .wasm file in the target/wasm32-wasi/release/ folder inside your project. You can now run your application by passing the generated .wasm file to Lunatic, e.g:

lunatic target/wasm32-wasi/release/<name>.wasm

Better developer experience

To simplify developing, testing and running lunatic applications with cargo, you can add a .cargo/config.toml file to your project with the following content:

[build]
target = "wasm32-wasi"

[target.wasm32-wasi]
runner = "lunatic"

Now you can just use the commands you are already familiar with, such as cargo run, cargo test and cargo is going to automatically build your project as a WebAssembly module and run it inside lunatic.

Getting started with submillisecond

Add it as a dependency

submillisecond = "0.3.0"

Handlers

Handlers are functions which return a response which implements IntoResponse.

They can have any number of arguments, where each argument is an extractor.

fn index(body: Vec<u8>, cookies: Cookies) -> String {
    // ...
}

Routers

Submillisecond provides a router! macro for defining routes in your app.

#[derive(NamedParam)]
struct User {
    first_name: String,
    last_name: String,
}

fn hi(user: User) -> String {
    format!("Hi {} {}!", user.first_name, user.last_name)
}

fn main() -> std::io::Result<()> {
    Application::new(router! {
        GET "/hi/:first_name/:last_name" => hi
        POST "/update_data" => update_age
    })
    .serve("0.0.0.0:3000")
}

The router macro supports:

Nested routes

Routes can be nested.

router! {
    "/foo" => {
        GET "/bar" => bar
    }
}

Url parameters

Uri parameters can be captured with the Path extractor.

router! {
    GET "/users/:first/:last/:age" => greet
}

fn greet(Path((first, last, age)): Path<(String, String, u32)>) -> String {
    format!("Welcome {first} {last}. You are {age} years old.")
}

You can use the NamedParam derive macro to define named parameters.

router! {
    GET "/users/:first/:last/:age" => greet
}

#[derive(NamedParam)]
struct GreetInfo {
    first: String,
    last: String,
    age: u32,
}

fn greet(GreetInfo { first, last, age }: GreetInfo) -> String {
    format!("Welcome {first} {last}. You are {age} years old.")
}

Alternatively, you can access the params directly with the Params extractor.

Catch-all

The _ syntax can be used to catch-all routes.

router! {
    "/foo" => {
        GET "/bar" => bar
        _ => matches_foo_but_not_bar
    }
    _ => not_found
}

Guards

Routes can be protected by guards.

struct ContentLengthLimit(u64);

impl Guard for ContentLengthLimit {
    fn check(&self, req: &RequestContext) -> bool {
        // ...
    }
}

router! {
    "/short_requests" if ContentLengthGuard(128) => {
        POST "/super" if ContentLengthGuard(64) => super_short
        POST "/" => short
    }
}

Guards can be chained with the && and || syntax.

Middleware

Middleware is any handler which calls next_handler on the request context. Like handlers, it can use extractors.

fn logger(req: RequestContext) -> Response {
    println!("Before");
    let result = req.next_handler();
    println!("After");
    result
}

fn main() -> std::io::Result<()> {
    Application::new(router! {
        with logger;

        GET "/" => hi
    })
    .serve("0.0.0.0:3000")
}

Middleware can be chained together, and placed within sub-routes.

router! {
    with [mid1, mid2];

    "/foo" => {
        with [foo_mid1, foo_mid2];
    }
}

They can also be specific to a single route.

router! {
    GET "/" with mid1 => home
}

Testing

Lunatic provides a macro #[lunatic::test] to turn your tests into processes. Check out the tests folder for examples.

License

Licensed under either of

at your option.

submillisecond's People

Contributors

bkolobara avatar dependabot[bot] avatar hougesen avatar kianmeng avatar mpope9 avatar pinkforest avatar pmartin-cogility avatar shamilsan avatar squattingsocrates avatar tqwewe avatar

Stargazers

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

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

submillisecond's Issues

Add templating support

I would suggest we use Askama for the templating part. People are familiar with Jinja and compile-time templates means that we get the template embedded in the compiled .wasm file. So it's easy to distribute the app.

We just would need to add an Askama impl for IntoResponse.

RUSTSEC-2021-0139: ansi_term is Unmaintained

ansi_term is Unmaintained

Details
Status unmaintained
Package ansi_term
Version 0.12.1
URL ogham/rust-ansi-term#72
Date 2021-08-18

The maintainer has adviced that this crate is deprecated and will not receive any maintenance.

The crate does not seem to have much dependencies and may or may not be ok to use as-is.

Last release seems to have been three years ago.

Possible Alternative(s)

The below list has not been vetted in any way and may or may not contain alternatives;

Dependency Specific Migration(s)

See advisory page for additional details.

Add support for wildcard routing `/*`

The router should support wildcards with the * character.

The difference between /* and /:foo are the following:

let router = router! {
    GET "/:foo" => ... // matches /id, /hello, /...
    GET "/*" => ... // matches /id, /id/123, /any/thing/here
};

Some questions we need to answer:

  1. Do we want to support /foo-* to catch anything starting with /foo-? Or should * be limited only to directly after the /?
  2. Do we want to name the wildcard with /*id? Similar to matchit.
    I think with our approach, naming the wildcard has no use.

Additionally, we should analyze the paths and error if the user placed a wildcard route above a specific route as it would never match.

router! {
    GET "/*" => any_handler
    GET "/about" => about // error here, since this will never match
}

Serving static files

I would love to have a convention where you drop some files into the static folder and have them available under /static/* with the right MIME type.

I would also love to be able to actually have this files be part of the compiled .wasm file, so we could just deploy an app by using one .wasm file. This part is a bit tricky, because Rust will not actually re-compile an app if there are no changes to .rs files. With templates this works because templates use the include_bytes! internally and this macro watches for changes on files, but there is no equivalent for directories. So if we actually added a few more files and did cargo build, they would not end up in the final .wasm file if at the same time no .rs files changed.

Some Rust libraries solve this by using a build.rs file that watches folder: https://docs.rs/sqlx/0.5.13/sqlx/macro.migrate.html#stable-rust-cargo-build-script

But as of today there is no way in stable rust to do it without a build.rs file. We could just do the same as sqlx here and document this behaviour, asking developers to include a build.rs file to their project.

Handling panics & timeouts

Currently the process handling the request (middleware & handler) is the one holding the TcpStream, so if it fails (panics) the browser will not get a response.

We should introduce a "supervisor" process. This process wouldn't be of the Supervisor type as it wouldn't have generic behaviour.

  • I would spawn it as a AbstractProcess.
  • It should set host::api::process::die_when_link_dies(1) in the init method and spawn a linked sub process.
  • Both the sub-process (handler) and the "supervisor" should hold onto the TcpStream.
  • The sub-process should use the stream to parse the incoming request data.
  • Once the Response data is available, it should send it as a message to the supervisor.
  • The supervisor then should write the Response to the TcpStream.
  • If at any time the sub-process should fail (link breaks and handle_link_trapped gets called), the supervisor should return a 500 Internal Server Error.
  • We could also use the newly added send_after function, so that the supervisor can send a timeout message to itself. If the sub-process doesn't finish in lets say 60 sec, the supervisor should write back to the browser Request Timed Out and panic so it kills the linked sub-process too.
  • We could also use this mechanism to put memory and compute limitations on the sub-process by spawning it with a specific configuration (ProcessConfig)

Add WebSocket support

I think websockets could be a trait:

trait WebSocket {
    fn handle(msg: Message);
}

Everything that implements WebSocket should also implement Handle so that we can plug it simply in the router:

router! {
    WS "/ws" => ws_handler
})

But because everything else in submillisecond is a function, we could follow axum's example and do something similar: https://docs.rs/axum/latest/axum/extract/ws/index.html

This also makes WebSocket handlers similar to processes that have a mailbox to receive messages.

Server-Sent Events

Do you plan to support Server-Sent Events?

If it isn't a priority, would a PR be welcomed in the future?

Use middleware instance instead of type

Currently, middleware in the router macro uses the type, and calls Default::default() on it to get an instance.
This makes it difficult and inconvenient to pass options to the middleware.

Instead, we should treat the middleware provided as an expression, and use the expression directly when creating the middleware.

For example:

routert! {
    use LoggingMiddleware::with_level(Level::Warn);
    // or
    use LoggingMiddleware { level: Level::Warn };
}

Add Router

The router is usually one of the core parts of the web framework. Developers are familiar with the concept and I wouldn't innovate much here. Copying axum's approach should be enough.

Goals

  • #2
  • #3
  • #4
  • Add custom macro parser that generates a router function
  • Allow routers to compose together
  • #37
  • Handle middleware and guards in routers
  • #68

RUSTSEC-2021-0145: Potential unaligned read

Potential unaligned read

Details
Status unsound
Package atty
Version 0.2.14
URL softprops/atty#50
Date 2021-07-04

On windows, atty dereferences a potentially unaligned pointer.

In practice however, the pointer won't be unaligned unless a custom global allocator is used.

In particular, the System allocator on windows uses HeapAlloc, which guarantees a large enough alignment.

atty is Unmaintained

A Pull Request with a fix has been provided over a year ago but the maintainer seems to be unreachable.

Last release of atty was almost 3 years ago.

Possible Alternative(s)

The below list has not been vetted in any way and may or may not contain alternatives;

  • is-terminal
  • std::io::IsTerminal nightly-only experimental

See advisory page for additional details.

Improve performance of routing

  • Optional - we could create a BorrowedParams type which is used for the routers since they don't have any use in owning the type. And the Params type is owned which can be used for the extractor
  • Remove unnecessary cloning in the routing code

`_` route for custom 404 pages

The router macro should support _ as a last route, to serve custom 404 pages. Similar to Rust's _ arm in match statements.

router! {
    GET "/foo" => foo_handler
    GET "/bar" => bar_handler
    _ => handle_404
}

The _ route should not be prefixed with a http method.

Unit tests

Now that most important features are in place, unit tests should be written for the library.

Create lunatic cli

Create a quick start cli commands to get a barebones project up and running

RUSTSEC-2023-0065: Tungstenite allows remote attackers to cause a denial of service

Tungstenite allows remote attackers to cause a denial of service

Details
Package tungstenite
Version 0.19.0
URL snapview/tungstenite-rs#376
Date 2023-09-25
Patched versions >=0.20.1

The Tungstenite crate through 0.20.0 for Rust allows remote attackers to cause
a denial of service (minutes of CPU consumption) via an excessive length of an
HTTP header in a client handshake. The length affects both how many times a parse
is attempted (e.g., thousands of times) and the average amount of data for each
parse attempt (e.g., millions of bytes).

See advisory page for additional details.

RUSTSEC-2020-0071: Potential segfault in the time crate

Potential segfault in the time crate

Details
Package time
Version 0.1.45
URL time-rs/time#293
Date 2020-11-18
Patched versions >=0.2.23
Unaffected versions =0.2.0,=0.2.1,=0.2.2,=0.2.3,=0.2.4,=0.2.5,=0.2.6

Impact

Unix-like operating systems may segfault due to dereferencing a dangling pointer in specific circumstances. This requires an environment variable to be set in a different thread than the affected functions. This may occur without the user's knowledge, notably in a third-party library.

The affected functions from time 0.2.7 through 0.2.22 are:

  • time::UtcOffset::local_offset_at
  • time::UtcOffset::try_local_offset_at
  • time::UtcOffset::current_local_offset
  • time::UtcOffset::try_current_local_offset
  • time::OffsetDateTime::now_local
  • time::OffsetDateTime::try_now_local

The affected functions in time 0.1 (all versions) are:

  • at
  • at_utc
  • now

Non-Unix targets (including Windows and wasm) are unaffected.

Patches

Pending a proper fix, the internal method that determines the local offset has been modified to always return None on the affected operating systems. This has the effect of returning an Err on the try_* methods and UTC on the non-try_* methods.

Users and library authors with time in their dependency tree should perform cargo update, which will pull in the updated, unaffected code.

Users of time 0.1 do not have a patch and should upgrade to an unaffected version: time 0.2.23 or greater or the 0.3 series.

Workarounds

No workarounds are known.

See advisory page for additional details.

Examples don't work

use std::io::Result;
use submillisecond::extract::Path;
use submillisecond::{router, Application, NamedParam};

fn main() -> Result<()> {
    Application::new(router! {
        GET "/" => index
        GET "/:name" => hi
        GET "/:name/:age" => hi_2
        GET "/users/:first/:last/:age" => greet
    })
    .serve("0.0.0.0:3000")
}

fn index() -> &'static str {
    "Hello :)"
}

#[derive(NamedParam)]
#[param(name = "name")]
struct HiParams(String);

fn hi(HiParams(name): HiParams) -> String {
    format!("Hi {}!", name)
}

fn hi_2(Path((name, age)): Path<(String, String)>) -> String {
    format!("Hi {}({})!", name, age)
}

#[derive(NamedParam)]
struct GreetInfo {
    first: String,
    last: String,
    age: u32,
}

fn greet(GreetInfo { first, last, age }: GreetInfo) -> String {
    format!("Welcome {first} {last}. You are {age} years old.")
}

http://localhost:3000/users/1/2/3 => Welcome 1 2. You are 1 years old.

http://localhost:3000/1/2/

thread '<unnamed>' panicked at 'called `Option::unwrap()` on a `None` value', /Users/ben/.cargo/registry/src/index.crates.io-6f17d22bba15001f/submillisecond-0.4.1/src/extract/path.rs:156:14
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
[2023-09-19T19:52:41Z WARN  lunatic_process] Process 14 failed, notifying: 1 links
    			    (Set ENV variable `RUST_LOG=lunatic=debug` to show stacktrace)
INFO submillisecond::supervisor: GET /1/2/    -
ERROR submillisecond::supervisor: Worker process panicked

It's also not possible to create NamedParam with only one param.

The configuration in the Cargo.toml also don't work. When running cargo build --target=wasm32-wasi everything works fine.

[package]
name = "playground"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[lib]
name = "playground"
path = "src/lib.rs"

[build]
target = "wasm32-wasi"

[target.wasm32-wasi]
runner = "lunatic"

[dependencies]
submillisecond = "0.4.1"
submillisecond-live-view = "0.4.1"
serde = "1.0.188"
serde_json = "1.0.107"

But cargo build fails.

ld: symbol(s) not found for architecture arm64
clang: error: linker command failed with exit code 1 (use -v to see invocation)

Named params extractor

Add trait for extracting params via their name.

pub trait NamedParam {
    type Rejection: IntoResponse;

    fn name() -> &'static str;
    fn extract(req: &Request) -> Result<Self, Self::Rejection>;
}

And can be implemented manually:

struct AgeParam(i32);

impl NamedParam for AgeParam {
    type Rejection = ...;
    
    fn name() -> &'static str { "age" }
    fn extract() -> ...
}

But more conveniently, can be implemented with a derive macro we provide:

#[derive(NamedParam)]
#[name("age")]
struct AgeParam(i32);

fn foo(AgeParam(age): AgeParam) { ... }

The router should take a string and a function handling the request

The string part is straight forward, but the handler part will need to be a bit different in lunatic than in other Rust web frameworks. Axum for example uses a concept of Extractors.

We can't copy this approach, because lunatic can't capture arbitrary closures in rust, only fn functions. We still can pass in non-capturing closures and they will be correctly coerced into functions. However, we can't use the type system to implement traits for fn() functions. To implement the extractor approach we need to be able to do it for functions with different numbers of incoming arguments. E.g.:

fn main() {
    // This doesn't work in rust!!!
    // error[E0277]: the trait bound `fn(()) {handler}: Function` is not satisfied
    router_add("/", handler);
}

fn router_add<H>(route: &str, handler: H)
where
    H: Function,
{
    // Implementation ...
}

fn handler(empty_extractor: ()) {
// Implementation
}

trait Function {}

impl Function for fn() {}
impl Function for fn(_x: ()) {}

We can make this compile but need to change the router_add call to:

router_add("/", handler as fn(()));

What is not great. I'm not sure why the function is not casted by rust into the correct type, but I don't see any effort going on in fixing this. The general recommendation is just to use the Fn*() traits instead, that we can't because we need non-capturing guarantees. And for a bunch of other reasons, we can't determine if the closure is zero-sized (non-capturing), so there is no workaround for this.

Solution

I would just for now stick to having one Request argument that every handler gets, instead of this fancy extractor tricks. E.g.

fn handler(request: Request) {
// Implementation
}

Then the Request can be used to access all the data that would be "extracted" through the extractor.

Where to put Guards?

This is a summary of today's discussion on discord. Feel free to add stuff if I left something out.

As @SquattingSocrates discovered, the main issue is that you usually want to add a custom response to a Guard (e.g. Permission denied) and this is currently not possible. This also sparked a bigger discussion, what is actually a Guard and do we need an explicit if syntax for it?

There are 3 places a guard can live:

1. Extractors as Guards

We are using Rocket and Axum inspired extractors in handlers, this means that we can turn each extractor into a Guard by returning a "rejection" value that implements IntoResponse. So we have already a way of expressing a "Guard" on the handler level.

In my opinion, this is not always that great, especially if you want to guard a whole sub-route:

router! {
    "/admin" => {
         // I want to guard all handlers here
        "a" => a,
        "b" => b,
        // ... rest
    }
})

If someone else on your team adds a handler "c", they need to remember to add an explicit guard to it or this might become a security issue.

2. Middleware as Guards

Another place where we can add guards is middleware. Middleware wrap handlers and they could be used to short-circuit a response:

router! {
    "/admin" => {
         use isAdmin;

        "a" => a,
        "b" => b,
        // ... rest
    }
})

that way we give middleware more power, but simplify the mental model around it. You don't have anymore two different concepts Guards and Middleware. I think this simplifies the internals, because we don't need a dedicated if syntax. And developers are already used to using middleware that way in other web frameworks.

3. Explicit Guard syntax

The last option is an explicit guard, but with the option to return custom responses:

router! {
    "/admin" if isAdmin => {
        "a" => a,
        "b" => b,
        // ... rest
    }
})

The biggest benefit here is that the guarding part is explicit. Opposite of middleware, where some middleware could act as a guard but other could just be doing some logging.

Early returns

The question mostly boils down to, do we want only one way to end a request early (guard the handler)? Or do we want to be able to do it from multiple places. One extreme would be to allow each part to return an early response (middleware, extractor and guard). On the other side we could for example only allow middleware to early return, remove if guards and remove custom responses from extractors. Of course, any in-between combination of these 3 would be an option too.

Support for dependency injection?

How DI is usually done in submillisecond? For example, I may want to pass a configuration file, a database connection, and some other stuff of my own to some of my handlers. How can I do that?

Session data

If we are a "batteries included" framework, I think we should provide built in handling for session data. There are generally two approaches to sessions:

  1. Cookie based - All session data is stored client-side in an encrypted cookie.
  2. Database based - The session is stored server-side and the cookie just contains information to identify the user.

Approach 1 is simpler, but could result in a lot of data being sent back and forth if the session grows too much.

I prefer that we take approach 1 here and document that the session is not intended to contain too much data.

middleware compatibility

do you have any plans or roadmap to be compatible with existing middleware interfaces such as tower or hyper?

there are quite a lot of ready to use and battle tested middleware out there, it seems like a large effort to rewrite them all for submillisecond

Router params don't work in params example

The params example has the following router:

router! {
    "/:a" if FakeGuard => {
        "/:b" => {
            GET "/:c" if BarGuard use LoggingMiddleware => bar_handler
        }
    }
    GET "/hello/:x/:y/:z" if BarGuard => foo_handler
}

Making a request to /foo/bar/baz works and is handled by bar_handler, but I would expect /hello/foo/bar/baz to also work and get handled by foo_handler, but it instead returns 404.

Middleware

I would say that this is also somewhat of a solved problem and wouldn't innovate here. We just need to decide if each middleware should be a separate process or just have it in the same process. If middleware fails, the whole request should fail, so there is not much value in separating it out into another process and we save a bit of overhead of spawning bunch of stuff every time we add middleware. That's why I'm in favour to keep it simple (inside one process).

We can take the layering approach like axum does: https://docs.rs/axum/latest/axum/middleware/index.html

Copy Axum's IntoResponse trait directly

When creating the IntoResponse trait, I tried to avoid copying Axum's trait as much as possible, though I feel like their approach makes complete sense since it's based on http-body.

The IntoResponse trait I made uses Vec<u8> for the response body, though I think it makes sense to use the same that axum uses: UnsyncBoxBody<Bytes, Error>.

Their license seems to very permissive with copying & modifying their code.

I suggest we use their trait more closely.

Static router doesn't work with `"/"` path

The following router shows 404 with /index.html, but instead only works with //index.html (with double / prefix).

router! {
    "/" => static_router!("./static")
}

This should be fixed so /index.html matches directly.

Extractors

Extractor types which need to be implemented:

  • Path<u32> - gives you the path parameters and deserializes them
  • Query<HashMap<String, String>> - gives you the query parameters and deserializes them
  • HeaderMap - gives you all the headers
  • TypedHeader<UserAgent> - can be used to extract a single header
  • String - consumes the request body and ensures it is valid utf-8
  • Vec<u8> - gives you the raw request body
  • Json<Value> - consumes the request body and deserializes it as JSON into some target type
  • Extension<T> - extensions?

See Axum extractors for inspiration.

Add Database support

For the first release (MVP), I would just focus on getting only support for SQLite.

For the distributed part, we can go the fly.io way: https://fly.io/blog/all-in-on-sqlite-litestream/
See also https://tailscale.com/blog/database-for-2022/ & https://blog.cloudflare.com/introducing-d1/

Nice to have features

  • Keep SQL in .sql files (similar to Clojure's Yesql and many others with the no-dsl philosophy). This would keep the DX similar to our templating system (compile-time embedding & domain-specific languages stay domain specific).
  • Migration tools
  • WYSIWYG built in editor for migration creation and looking into the DB content

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.