GithubHelp home page GithubHelp logo

mhewedy / spwrap Goto Github PK

View Code? Open in Web Editor NEW
24.0 6.0 3.0 4.15 MB

Simple Stored Procedure call wrapper with no framework dependencies.

Home Page: https://github.com/mhewedy/spwrap-spring-boot-starter

License: Apache License 2.0

Java 66.62% SQLPL 1.40% Groovy 29.03% Shell 0.71% PLSQL 1.08% PLpgSQL 1.16%
stored-procedures jdbc sql dao-interface spwrap database

spwrap's Introduction

spwrap

Stored Procedure caller; simply execute stored procedure from java code.
Compatible with jdk >= 1.5, with only single dependency (slf4j-api)

Build Status Coverage Status

Step 0: Create Stored Procedures:

Suppose you have 3 Stored Procedures to save customer to database, get customer by id and list all customer.

For example here's SP code using HSQL:

CREATE PROCEDURE create_customer(firstname VARCHAR(50), lastname VARCHAR(50), OUT custId INT, 
        OUT code SMALLINT, OUT msg VARCHAR(50))
    MODIFIES SQL DATA DYNAMIC RESULT SETS 1
    BEGIN ATOMIC
        INSERT INTO CUSTOMERS VALUES (DEFAULT, firstname, lastname);
        SET custId = IDENTITY();
        SET code = 0 -- success;
    END

CREATE PROCEDURE get_customer(IN custId INT, OUT firstname VARCHAR(50), OUT lastname VARCHAR(50), 
        OUT code SMALLINT, OUT msg VARCHAR(50)) 
    READS SQL DATA
    BEGIN ATOMIC
        SELECT first_name, last_name INTO firstname, lastname FROM customers WHERE id = custId;
        SET code = 0 -- success;
    END

CREATE PROCEDURE list_customers(OUT code SMALLINT, OUT msg VARCHAR(50))
    READS SQL DATA DYNAMIC RESULT SETS 1
    BEGIN ATOMIC
        DECLARE result CURSOR FOR SELECT id, first_name firstname, last_name lastname FROM CUSTOMERS;
        OPEN result;
        SET code = 0 -- success;
    END

NOTE: Every Stored Procedure by default need to have 2 additional Output Parameters at the end of its parameter list. One of type SMALLINT and the other of type VARCHAR for result code and message respectively, where result code 0 means success. You can override the 0 value or remove this default behviour at all, see the configuration wiki page.

Step 1: Create The DAO interface:

public interface CustomerDAO {

    @StoredProc("create_customer")
    void createCustomer(@Param(VARCHAR) String firstName, @Param(VARCHAR) String lastName);

    @StoredProc("get_customer")
    Customer getCustomer(@Param(INTEGER) Integer id);	
	
    @StoredProc("list_customers")
    List<Customer> listCustomers();
}

Step 2: Map Output parameters and Result set (if any):

Before start using the CustomerDAO interface, one last step is required, to map the result of the get_customer and list_customers stored procedures.

  • get_customer stored procs returns the result as Output Parameters, so you need to have a class to implement TypedOutputParamMapper interface.
  • list_customers stored proc returns the result as Result Set, so you need to have a class to implement ResultSetMapper interface.

Let's create Customer class to implement both interfaces (for getCustomer and listCustomers):

public class Customer implements TypedOutputParamMapper<Customer>, ResultSetMapper<Customer> {

	private Integer id;
	private String firstName, lastName;

	public Customer() {
	}

	public Customer(Integer id, String firstName, String lastName) {
		super();
		this.id = id;
		this.firstName = firstName;
		this.lastName = lastName;
	}

	public Integer id() {
		return id;
	}

	public String firstName() {
		return firstName;
	}

	public String lastName() {
		return lastName;
	}
	
	 //You can acess result set columns/output parameters by name as well
	@Override
	public Customer map(Result<?> result) {
		if (result.isResultSet()) {
			return new Customer(result.getInt(1), result.getString(2), result.getString(3));
		} else {
			return new Customer(null, result.getString(1), result.getString(2));
		}
	}

	// for TypedOutputParamMapper
	@Override
	public List<Integer> getTypes() {
		return Arrays.asList(VARCHAR, VARCHAR);
	}
}

See more examples on spwrap-examples github project and read more about Mappers in the wiki.

NOTE: If your stored procedure returns a single output parameter with no result set, then you can use the @Scalar annotation and you will not need to provide a Mapper class yourself, the mapping will done for you. see wiki page about scalars for more

NOTE: You can use @AutoMappers to do the mapping for you instead of Mapping the Result object into your domain object yourself.

Step 3: Using the DAO interface:

Now you can start using the interface to call the stored procedures:

DataSource dataSource = ...
DAO dao = new DAO.Builder(dataSource).build();
CustomerDAO customerDao = dao.create(CustomerDAO.class);

customerDao.createCustomer("Abdullah", "Muhammad");
Customer customer = customerDao.getCustomer1(1);
Assert.assertEquals("Abdullah", customer.firstName());

Installation

Gradle:

compile group: 'com.github.mhewedy', name: 'spwrap', version: '0.0.20'

Maven:

<dependency>
    <groupId>com.github.mhewedy</groupId>
    <artifactId>spwrap</artifactId>
    <version>0.0.20</version>
</dependency>

Additional staff:

  • If you don't supply the stored procedure name to @StoredProc, it will use the method name by default.

  • @Param annotation should used for ALL method parameters and accepts the SQL Type per java.sql.Types.

  • If you don't want to tie your Domain Object with spwrap as of step 3 above, you can have another class to implement the Mapper interfaces (TypedOutputParamMapper and ResultSetMapper) and pass it to the annotaion @Mapper like:

@Mapper(CustomResultSetMapper.class)
@StoredProc("list_customers")
List<Customer> listCustomers();
  • @Mapper annotation overrides the mapping specified by the return type object, i.e. spwrap extract Mapping infromation from the return type class, and then override it with the classes set by @Mapper annotation if found.

  • Your Stored procedure can return output parameter as well as One Result set in one call, to achieve this use Tuple return type:

@Mapper({MyResultSetMapper.class, MyOutputParameterMapper.class})
@StoredProc("list_customers_with_date")
Tuple<Customer, Date> listCustomersWithDate();

Limitations:

  • spwrap doesn't support INOUT parameters.

  • spwrap doesn't support returning multi-result sets from the stored procedure.

  • When the Stored procedure have input and output parameters, input parameters should come first and then the output parameters.

Database Support:

Because spwrap is based on JDBC API, theoretically it should support any Database Management System with a JDBC Driver, However it is tested on HSQL, MySQL, SQL Server and Oracle with jdk 1.6, 1.7 and 1.8 (1.7 and 1.8 are remove to reduce build time). (Plan to test againest: Postgresql, Sybase, DB2)

See wiki page for more info and test cases/spwrap-examples for more usage scenarios.

spwrap's People

Contributors

mhewedy 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

spwrap's Issues

Copy ResuletSet/Callable Statement to a new object and return it in case no mapper was provided

After brainstorming with Ahmad Altihami, ResultSet cannot be copied (unless the result set types are known ahead which violations the main principles of spwrap), however it might be wrapped inside Iterator (like commons-dbutils ResultSetIterator).

Also ResultSetIterator solution to be type-safe may require a head knowledge of result set types.

Will close now and reopen a new issue if there's still a need.

Enhance code of MetaData object

  • rename from Metadata to MetaData (and all references of it)
  • use private fields and Setters and Getters // may be to do so, but I think public fields might be ok as long as the instance of this class is controlled and used in thread safe environment (need to read in JCIP)

Consider adding a license file

The project is Apache 2 licensed but one has to the scroll to the bottom to find out. Github has support for LICENSE files which cause the licensing information to be prominently displayed.

Support ResultSets in Oracle stored procedures

When use define the stored procedure, then the output parameter of type curser that will hold the result set need to be the last output parameter in the parameter list (before status fields).

Write about which mapping method to use

change the method

    @StoredProc("new_customer")
    void createCustomer(@Param(VARCHAR) String firstName, @Param(VARCHAR) String lastName);

make it return the newly crated Customer Id with custom mappings, and add section about custom mapping vs non-custom mapping

print string represents java.sql.Types in log

Currently, log prints the Integer value of sql type as in java.sql.Types, need to print the String value instead:

[main] DEBUG spwrap.proxy.CallerInvocationHandler - storedProcName name is: create_customer for method: createCustomer
[main] DEBUG spwrap.proxy.ParamBinder - inParams are: [[value=Abdullah, sqlType=12], [value=Mohammad, sqlType=12]] for method: createCustomer
[main] DEBUG spwrap.proxy.ScalarBinder - Scalar annotation exists, try auto-mapping
[main] DEBUG spwrap.Caller - setting input parameters
[main] DEBUG spwrap.Caller - reading output parameters
[main] INFO spwrap.Caller - >call sp: [{call create_customer(?, ?, ?, ?, ?)}]
InParams: [[value=Abdullah, sqlType=12], [value=Mohammad, sqlType=12]],
OutParams Types: [4]
Result: Tuple [list=, object=0];

Create Database classes

Create Database abstract class with subclass for concert databases (HSQL, SQLServer, MySQL, Oracle, etc).

The purpose is to provide database specific behaviours (see the HSQL check in Caller#call method)

Make polymorphism in result object instead of ifelse

class Result<T>{
    T object;

     Result(T object){this.object = object;}
    T object(){return this.object;}


    // abstract getters
}

class ResultSetWrapper extends Result<java.sql.ResultSet>{
      ResultSetWrapper( java.sql.ResultSet rs){
          super(rs);
      }
}

class CallableStatementWrapper extends Result<java.sql.CallableStatement>{
      ResultSetWrapper( java.sql.CallableStatement call, int outStartIndex){
          super(call);
          this.outStartIndex = index;
      }

     // use outStartIndex to + to the getXXX input
}

Allow to pass jdbc parameters to DAO methods (Mainly Fetch size, Isolation level)

Allow to pass connection and result set attributes to the connection used by DAO (caller) methods:

connection.setTransactionIsolation(int level)

level - one of the following Connection constants: Connection.TRANSACTION_READ_UNCOMMITTED, Connection.TRANSACTION_READ_COMMITTED, Connection.TRANSACTION_REPEATABLE_READ, or Connection.TRANSACTION_SERIALIZABLE. (Note that Connection.TRANSACTION_NONE cannot be used because it specifies that transactions are not supported.)

connection.prepareCall(String sql, int resultSetType, int resultSetConcurrency, int resultSetHoldability)

resultSetType - one of the following ResultSet constants: ResultSet.TYPE_FORWARD_ONLY, ResultSet.TYPE_SCROLL_INSENSITIVE, or ResultSet.TYPE_SCROLL_SENSITIVE
resultSetConcurrency - one of the following ResultSet constants: ResultSet.CONCUR_READ_ONLY or ResultSet.CONCUR_UPDATABLE
resultSetHoldability - one of the following ResultSet constants: ResultSet.HOLD_CURSORS_OVER_COMMIT or ResultSet.CLOSE_CURSORS_AT_COMMIT

stmt.setFetchSize(fetchSize);

stmt.setMaxRows

Exception when running spwrap with spring-data-jpa

This issue mainly because the spwrap proxy returns null for all methods that are not annotated with @storedproc, and this is not valid hence spring framework depends on the hashcode of the spwrap DAO interface as a @bean, so the fix should provide default implementation for equals and hashcode methods.

java.lang.NullPointerException: null
	at com.sun.proxy.$Proxy73.hashCode(Unknown Source) ~[na:na]
	at java.util.concurrent.ConcurrentHashMap.get(ConcurrentHashMap.java:936) ~[na:1.8.0_102]
	at java.util.concurrent.ConcurrentHashMap.containsKey(ConcurrentHashMap.java:964) ~[na:1.8.0_102]
	at org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor.requiresDestruction(PersistenceAnnotationBeanPostProcessor.java:380) ~[spring-orm-4.3.7.RELEASE.jar:4.3.7.RELEASE]
	at org.springframework.beans.factory.support.DisposableBeanAdapter.hasApplicableProcessors(DisposableBeanAdapter.java:431) ~[spring-beans-4.3.7.RELEASE.jar:4.3.7.RELEASE]
	at org.springframework.beans.factory.support.AbstractBeanFactory.requiresDestruction(AbstractBeanFactory.java:1662) ~[spring-beans-4.3.7.RELEASE.jar:4.3.7.RELEASE]
	at org.springframework.beans.factory.support.AbstractBeanFactory.registerDisposableBeanIfNecessary(AbstractBeanFactory.java:1679) ~[spring-beans-4.3.7.RELEASE.jar:4.3.7.RELEASE]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:597) ~[spring-beans-4.3.7.RELEASE.jar:4.3.7.RELEASE]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:483) ~[spring-beans-4.3.7.RELEASE.jar:4.3.7.RELEASE]
	at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:306) ~[spring-beans-4.3.7.RELEASE.jar:4.3.7.RELEASE]
	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230) ~[spring-beans-4.3.7.RELEASE.jar:4.3.7.RELEASE]
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:302) ~[spring-beans-4.3.7.RELEASE.jar:4.3.7.RELEASE]
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202) ~[spring-beans-4.3.7.RELEASE.jar:4.3.7.RELEASE]
	at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:208) ~[spring-beans-4.3.7.RELEASE.jar:4.3.7.RELEASE]
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1138) ~[spring-beans-4.3.7.RELEASE.jar:4.3.7.RELEASE]
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1066) ~[spring-beans-4.3.7.RELEASE.jar:4.3.7.RELEASE]
	at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:835) ~[spring-beans-4.3.7.RELEASE.jar:4.3.7.RELEASE]
	at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:741) ~[spring-beans-4.3.7.RELEASE.jar:4.3.7.RELEASE]
	at org.springframework.beans.factory.support.ConstructorResolver.autowireConstructor(ConstructorResolver.java:189) ~[spring-beans-4.3.7.RELEASE.jar:4.3.7.RELEASE]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireConstructor(AbstractAutowireCapableBeanFactory.java:1193) ~[spring-beans-4.3.7.RELEASE.jar:4.3.7.RELEASE]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1095) ~[spring-beans-4.3.7.RELEASE.jar:4.3.7.RELEASE]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:513) ~[spring-beans-4.3.7.RELEASE.jar:4.3.7.RELEASE]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:483) ~[spring-beans-4.3.7.RELEASE.jar:4.3.7.RELEASE]
	at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:306) ~[spring-beans-4.3.7.RELEASE.jar:4.3.7.RELEASE]
	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230) ~[spring-beans-4.3.7.RELEASE.jar:4.3.7.RELEASE]
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:302) ~[spring-beans-4.3.7.RELEASE.jar:4.3.7.RELEASE]
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202) ~[spring-beans-4.3.7.RELEASE.jar:4.3.7.RELEASE]
	at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:208) ~[spring-beans-4.3.7.RELEASE.jar:4.3.7.RELEASE]
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1138) ~[spring-beans-4.3.7.RELEASE.jar:4.3.7.RELEASE]
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1066) ~[spring-beans-4.3.7.RELEASE.jar:4.3.7.RELEASE]
	at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:835) ~[spring-beans-4.3.7.RELEASE.jar:4.3.7.RELEASE]
	at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:741) ~[spring-beans-4.3.7.RELEASE.jar:4.3.7.RELEASE]
	at org.springframework.beans.factory.support.ConstructorResolver.autowireConstructor(ConstructorResolver.java:189) ~[spring-beans-4.3.7.RELEASE.jar:4.3.7.RELEASE]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.autowireConstructor(AbstractAutowireCapableBeanFactory.java:1193) ~[spring-beans-4.3.7.RELEASE.jar:4.3.7.RELEASE]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1095) ~[spring-beans-4.3.7.RELEASE.jar:4.3.7.RELEASE]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:513) ~[spring-beans-4.3.7.RELEASE.jar:4.3.7.RELEASE]
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:483) ~[spring-beans-4.3.7.RELEASE.jar:4.3.7.RELEASE]
	at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:306) ~[spring-beans-4.3.7.RELEASE.jar:4.3.7.RELEASE]
	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230) ~[spring-beans-4.3.7.RELEASE.jar:4.3.7.RELEASE]
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:302) ~[spring-beans-4.3.7.RELEASE.jar:4.3.7.RELEASE]
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:197) ~[spring-beans-4.3.7.RELEASE.jar:4.3.7.RELEASE]
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:761) ~[spring-beans-4.3.7.RELEASE.jar:4.3.7.RELEASE]
	at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:866) ~[spring-context-4.3.7.RELEASE.jar:4.3.7.RELEASE]
	at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:542) ~[spring-context-4.3.7.RELEASE.jar:4.3.7.RELEASE]
	at org.springframework.boot.context.embedded.EmbeddedWebApplicationContext.refresh(EmbeddedWebApplicationContext.java:122) ~[spring-boot-1.5.2.RELEASE.jar:1.5.2.RELEASE]
	at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:737) [spring-boot-1.5.2.RELEASE.jar:1.5.2.RELEASE]
	at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:370) [spring-boot-1.5.2.RELEASE.jar:1.5.2.RELEASE]
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:314) [spring-boot-1.5.2.RELEASE.jar:1.5.2.RELEASE]
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:1162) [spring-boot-1.5.2.RELEASE.jar:1.5.2.RELEASE]
	at org.springframework.boot.SpringApplication.run(SpringApplication.java:1151) [spring-boot-1.5.2.RELEASE.jar:1.5.2.RELEASE]

Support scale for IN/OUT parameters

In parameters:

statement.setObject(i + PARAM_OFFSET, inParams.get(i).value, inParams.get(i).sqlType);

Output parameters:

statement.registerOutParameter(i + PARAM_OFFSET + outParamIndex, outParamsTypes.get(i).sqlType);

The following snippet is from spring-jdbc

if (declaredParam.getScale() != null) {
	cs.registerOutParameter(sqlColIndx, declaredParam.getSqlType(), declaredParam.getScale());
}
else {
	cs.registerOutParameter(sqlColIndx, declaredParam.getSqlType());
}

Allow executing multipe DAO methods in one transaction?

I don't think so, as if the user wants to group logic in one transaction, then he will write it in a single Stored Procedure, but let keep it open for a while so If it came to my mind any other scenario then I can start work on it.

support cache compiled call statement

Each time the stored procedure is being called, there's a compilation step happening:

callString = database.getCallString(procName, config, inParams, outParamsTypes, rsMapper);
// ----- Applying JDBC Object Props
connectionPropsBkup = ConnectionProps.from(connection);
connChanged = connectionProps.apply(connection);
statement = resultSetProps.apply(connection, callString);
statementProps.apply(statement);
// ------- calc some indexes
int outParamIndex = length(inParams);
int statusParamsIndex = database.getStatusParamsIndex(config, inParams, outParamsTypes, rsMapper);
int resultSetParamIndex = database.getResultSetParamIndex(statusParamsIndex, rsMapper);

So instead of doing such activity eachtime, spwrap can cache such calculations. (spring stored procedure do)

Add Executor service support

Support of executing the stored procedure asynchronously and get the result back on the caller thread.

The support should be available in the new interface as well as the old interface.

Create config type

  • Create Config type (class/interface) for global configurations options, might look like
class Config{
        short SUCCESS_CODE = System.getProperty("spwarp.success_code") != null
			? Short.parseShort(System.getProperty("spwarp.success_code")) : 0;
         
        boolean USE_STATUS_CODE =  Boolean.getBoolean("spwarp.use_statuscode");
}
  • Make it instance field on Caller class

  • Create wiki page about the configuration and link to the config from the README.md page

Create a Mapper annotation for single results methods

When a method returns a single result (Integer, Date, etc), the user will need to provide a @Mapper mapping class, however it should done easy.

If there's an annotation like: @Scalar(Types.VARCHAR) that I could use to map the output param value into the output object, it would be better.

Need to take decision since it increase number of annotations, and hence might make the whole staff complex and not easy to use.

Also, I thinking of reusing the existing @Mapper annotation @Mapper(type = Types.VARCHAR) ๐Ÿ‘Ž I don't know yet!

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.