Giter VIP home page Giter VIP logo

gorm-plus's Introduction

Gorm-Plus是Gorm的增强工具,它在保持Gorm原有特性的基础上,为开发者提供了开箱即用的增强功能。通过简化开发流程、提高效率,它为开发者带来了无与伦比的开发体验。如果您渴望尝试一款让开发变得轻松而高效的工具,Gorm-Plus将是您不可错过的选择。

特性

  1. 无侵入、增强而非改变:Gorm-Plus以无侵入的方式对 Gorm 进行增强,保持原有特性的同时提供更多功能,让您无需担心兼容性问题。
  2. 强大的 CRUD 操作和通用查询:Gorm-Plus内置了强大的 CRUD 操作功能,并提供了简便的通用查询功能,无需繁琐的配置即可轻松构建复杂条件查询。
  3. 支持指针字段形式查询:通过支持指针字段形式查询,Gorm-Plus让编写各类查询条件变得更加便捷,再也不用担心因为字段写错而出现错误。
  4. 内置泛型查询和灵活的返回类型封装:Gorm-Plus内置了泛型查询功能,让您能够灵活封装返回类型,轻松应对各种查询需求。
  5. 内置分页插件:Gorm-Plus还提供了内置的分页插件,让分页操作变得简单而高效,让您能够轻松处理大量数据,并实现更好的用户体验。

快速上手

现有一张 Users 表,其表结构如下:

CREATE TABLE `users` (
  `id` bigint NOT NULL AUTO_INCREMENT,
  `username` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
  `password` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
  `address` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
  `age` bigint DEFAULT NULL,
  `phone` varchar(11) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
  `score` bigint DEFAULT NULL,
  `dept` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL,
  `created_at` datetime(3) DEFAULT NULL,
  `updated_at` datetime(3) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=407 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;

对应的数据如下:

INSERT INTO `users` (`username`, `password`, `address`, `age`, `phone`, `score`, `dept`, `created_at`, `updated_at`)
VALUES
  ('张三', 'password1', '地址1', 25, '12345678901', 80, '部门1', NOW(), NOW()),
  ('李四', 'password2', '地址2', 30, '12345678902', 90, '部门2', NOW(), NOW()),
  ('王五', 'password3', '地址3', 35, '12345678903', 70, '部门1', NOW(), NOW()),
  ('赵六', 'password4', '地址4', 28, '12345678904', 85, '部门2', NOW(), NOW()),
  ('钱七', 'password5', '地址5', 32, '12345678905', 75, '部门1', NOW(), NOW());

开始使用

下载Gorm-Plus

 go get github.com/acmestack/gorm-plus
package main

import (
  "github.com/acmestack/gorm-plus/gplus"
  "gorm.io/driver/mysql"
  "gorm.io/gorm"
  "gorm.io/gorm/logger"
  "log"
  "time"
)

type User struct {
  ID        int64
  Username  string
  Password  string
  Address   string
  Age       int
  Phone     string
  Score     int
  Dept      string
  CreatedAt time.Time
  UpdatedAt time.Time
}

var gormDb *gorm.DB

func init() {
  dsn := "root:123456@tcp(127.0.0.1:3306)/test?charset=utf8mb4&parseTime=True&loc=Local"
  var err error
  gormDb, err = gorm.Open(mysql.Open(dsn), &gorm.Config{
    Logger: logger.Default.LogMode(logger.Info),
  })
  if err != nil {
    log.Println(err)
  }

  // 初始化gplus
  gplus.Init(gormDb)
}

func main() {
  users, resultDb := gplus.SelectList[User](nil)
  log.Println("error:", resultDb.Error)
  log.Println("RowsAffected:", resultDb.RowsAffected)
  for _, user := range users {
    log.Println("user:", user)
  }
}

控制台输出:

2023/06/01 17:48:19 error: <nil>
2023/06/01 17:48:19 RowsAffected: 5
2023/06/01 17:48:19 user: &{1 张三 password1 地址1 25 12345678901 80 部门1 2023-06-01 17:48:11 +0800 CST 2023-06-01 17:48:11 +0800 CST}
2023/06/01 17:48:19 user: &{2 李四 password2 地址2 30 12345678902 90 部门2 2023-06-01 17:48:11 +0800 CST 2023-06-01 17:48:11 +0800 CST}
2023/06/01 17:48:19 user: &{3 王五 password3 地址3 35 12345678903 70 部门1 2023-06-01 17:48:11 +0800 CST 2023-06-01 17:48:11 +0800 CST}
2023/06/01 17:48:19 user: &{4 赵六 password4 地址4 28 12345678904 85 部门2 2023-06-01 17:48:11 +0800 CST 2023-06-01 17:48:11 +0800 CST}
2023/06/01 17:48:19 user: &{5 钱七 password5 地址5 32 12345678905 75 部门1 2023-06-01 17:48:11 +0800 CST 2023-06-01 17:48:11 +0800 CST}

搜索工具

只需要下面一行代码即可完成单表的所有查询功能

gplus.SelectList(gplus.BuildQuery[User](queryParams))

例子:

func main() {
  http.HandleFunc("/", handleRequest)
  http.ListenAndServe(":8080", nil)
}

func handleRequest(w http.ResponseWriter, r *http.Request) {
  queryParams := r.URL.Query()
  list, _ := gplus.SelectList(gplus.BuildQuery[User](queryParams))
  marshal, _ := json.Marshal(list)
  w.Write(marshal)
}

假设我们要查询username为zhangsan的用户

http://localhost:8080?q=username=zhangsan

假设我们要查询username姓zhang的用户

http://localhost:8080?q=username~>=zhang

假设我们要查询age大于20的用户

http://localhost:8080?q=age>20

假设我们要查询username等于zhagnsan,password等于123456的用户

http://localhost:8080?q=username=zhangsan&q=password=123456

假设我们要查询username等于zhagnsan,password等于123456的用户

http://localhost:8080?q=username=zhangsan&q=password=123456

假设我们要查询username等于zhagnsan,或者usename等于lisi的用户

可以增加一个分组和gcond的条件查询来实现

http://localhost:8080?q=A.username=zhangsan&q=B.username=lisi&gcond=A|B

所有的单表查询我们都只需要一行代码即可。

总结

从上述步骤中,我们可以看到集成Gorm-Plus非常简单。只需在初始化Gorm之后添加一行代码gplus.Init(gormDb)

即可使用。不仅如此,使用Gorm-Plus也同样轻松,只需一行代码即可完成列表查询。

然而,Gorm-Plus的强大功能远不止于此。

更多文档请查看: https://github.com/acmestack/gorm-plus/wiki

Star History

Star History Chart

技术交流群

加微信:afumubit,注意备注:gplus

gorm-plus's People

Contributors

afumu avatar aide-cloud avatar aixj1984 avatar bingtianyiyan avatar keepsilence233 avatar mzzsfy avatar phoenixl0911 avatar qicz avatar xgxiang 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

gorm-plus's Issues

嵌套结构体会在构造查询条件得适合错误

Enviornment (please complete the following information):

  • OS: [Linux: uname -a]
  • envcd version [e.g. v0.0.1]

Describe the bug
A clear and concise description of what the bug is.

To Reproduce
Steps to reproduce the behavior:

  1. Go to '...'
  2. Click on '....'
  3. Scroll down to '....'
  4. See error

定义如下结构体

type Base struct {
Id int64
CreatedAt time.Time json:"createdAt"
UpdatedAt time.Time json:"updatedAt"
}

type User struct {
Base
Username string gorm:"column:username"
Password string
Address string
Age int
Phone string
Score int
Dept string
}

在更新的时候,会直接使用base作为参数名

Expected behavior
A clear and concise description of what you expected to happen.

selectOne和selectId 的搜索时建议使用First

selectOne和selectId 的搜索时建议使用First

源码里面使用的limit+find。使用find后,如果查不到数据,数据接收对象被实例化了一个空的对象;并且没有gorm.ErrRecordNotFound的错误了。

support multiple gorm

We need to connect to multiple databases in our project, but gplus's gorm as a global variable.
can gplus support multi gorm??

go mod tidy error

go version 1.19.1

run go mod tidy:

go: finding module for package github.com/acmestack/gorm-plus
github.com/acmestack/gorm-plus: module github.com/acmestack/gorm-plus@latest found (v0.0.15), but does not contain package github.com/acmestack/gorm-plus

新增/修改记录时,无法选择忽略的字段

在model中,create_time和modify_time字段期望在新增/修改记录时,由数据库自动变更。
之前使用gorm的写法是:

data := &Students{
		Class: 2,
		Name:  "Mono",
		Age:   29,
}

db.Omit("create_time", "modify_time").Create(data)

切换到gorm-plus后,目前是这么实现的:

data := &Students{
		Class: 1,
		Name:  "Speci",
		Age:   12,
}

gplus.Insert[Students](data)

这样会报 Error 1292 (22007): Incorrect datetime value: '0000-00-00' for column 'create_time' at row 1
想问下目前gorm-plus有相关的解决方案么?

dao现在还不能设置 context,要如何安全的操作

如果获取 db设置 context再次设置到 dao,这会有问题
gplus.Init(dao.Db().WithContext(ctx))

接口操作
第一次不设置context save操作
第二次设置 context get操作

再次调用接口结果context canceled

当子条件的查询表达式为空时,构建sql拼接了多余的LeftBracket和RightBracket

bug案例复现:

func TestSelectListOr(t *testing.T) {
	var expectSql = "SELECT * FROM `Users` WHERE username = 'afumu' OR age = 20"
	sessionDb := checkSelectSql(t, expectSql)
	query, u := gplus.NewQuery[User]()
	query.Eq(&u.Username, "afumu").Or().Eq(&u.Age, 20).Or(func(q *gplus.QueryCond[User]) {

	})
	gplus.SelectList[User](query, gplus.Db(sessionDb))
}

期望得到的结果:

SELECT * FROM `Users` WHERE username = 'afumu' OR age = 20

实际得到的结果:

SELECT * FROM `Users` WHERE username = 'afumu' OR age OR () = 20

导致bug出现bug的函数:

func buildSqlAndArgs[T any](expressions []any, sqlBuilder *strings.Builder, queryArgs []any) []any {
	for _, v := range expressions {
		// 判断是否是columnValue类型
		switch segment := v.(type) {
		case *columnPointer:
			sqlBuilder.WriteString(segment.getSqlSegment() + " ")
		case *sqlKeyword:
			sqlBuilder.WriteString(segment.getSqlSegment() + " ")
		case *columnValue:
			if segment.value == constants.And {
				sqlBuilder.WriteString(segment.value.(string) + " ")
				continue
			}
			if segment.value != "" {
				sqlBuilder.WriteString("? ")
				queryArgs = append(queryArgs, segment.value)
			}
		case *QueryCond[T]:
			sqlBuilder.WriteString(constants.LeftBracket + " ")
			// 递归处理条件
			queryArgs = buildSqlAndArgs[T](segment.queryExpressions, sqlBuilder, queryArgs)
			sqlBuilder.WriteString(constants.RightBracket + " ")
		}
	}
	return queryArgs
}

bug fix方案:

func buildSqlAndArgs[T any](expressions []any, sqlBuilder *strings.Builder, queryArgs []any) []any {
	for _, v := range expressions {
		// 判断是否是columnValue类型
		switch segment := v.(type) {
		case *columnPointer:
			sqlBuilder.WriteString(segment.getSqlSegment() + " ")
		case *sqlKeyword:
			sqlBuilder.WriteString(segment.getSqlSegment() + " ")
		case *columnValue:
			if segment.value == constants.And {
				sqlBuilder.WriteString(segment.value.(string) + " ")
				continue
			}
			if segment.value != "" {
				sqlBuilder.WriteString("? ")
				queryArgs = append(queryArgs, segment.value)
			}
		case *QueryCond[T]:
			// 当子条件不存在查询表达式时,无需进行递归处理
			if len(segment.queryExpressions) == 0 {
				continue
			}
			sqlBuilder.WriteString(constants.LeftBracket + " ")
			// 递归处理条件
			queryArgs = buildSqlAndArgs[T](segment.queryExpressions, sqlBuilder, queryArgs)
			sqlBuilder.WriteString(constants.RightBracket + " ")
		}
	}
	return queryArgs
}

分页查询只有单个列执行查询 在查询SelectCount总数会报错,因为gorm里count处理在mysql会有问题

gorm对count的执行如果包含distinct 或者列别名会执行报错在mysql,我找了相应的issue :go-gorm/gorm#5821

我的用法:
1694510071205
报错的地方:
func SelectCount[T any](q *QueryCond[T], opts ...OptionFunc) (int64, *gorm.DB) {
var count int64
resultDb := buildCondition(q, opts...)
resultDb.Count(&count) //这里执行会生成sql不正确
return count, resultDb
}

sql生成比方说是生成这样的:SELECT COUNT(Id as id) FROM t_test 这种语法在mysql执行是报错的,
因为gorm里对count逻辑:
1694507712408

所以在不用到类似distinct什么情况下,所有sql一律生成 select count(*) from t_test ,不考虑复杂情况
所以我的解决方案我是想在这边执行Count的时候加个resultDb.Statement.Selects = nil
// SelectCount 根据条件查询记录数量
func SelectCount[T any](q *QueryCond[T], opts ...OptionFunc) (int64, *gorm.DB) {
var count int64
resultDb := buildCondition(q, opts...)
//fix 查询分页数量有列Select只有一个则生成sql不对问题
resultDb.Statement.Selects = nil
resultDb.Count(&count)
return count, resultDb
}

这种感觉也是组件gorm的问题~~~~对各个驱动并没有完全匹配
主要我用的使用列别名转了,如果单个字段,列别名不转的话没有问题,在gorm执行count的时候select count(fieldName) from 表 这样sql执行是正确的。不过总体上gorm的count兼容问题?

增加apply方法,用于拼接自定义sql字符串

需求案例场景:子查询、自定义sql片段
伪代码:

	var expectSql = "SELECT * FROM `Users` WHERE username = 'afumu' OR age = 20"
	sessionDb := checkSelectSql(t, expectSql)
	query, u := gplus.NewQuery[User]()
	query.Eq(&u.Username, "afumu").Or().Eq(&u.Age, 20).Apply("AND id IN (SELECT user_id FROM app_user)")
	gplus.SelectList[User](query, gplus.Db(sessionDb))

gorm tag 主键问题

tagSetting := schema.ParseTagSetting(field.Tag.Get("gorm"), ";")
isPrimaryKey := utils.CheckTruth(tagSetting["PRIMARYKEY"], tagSetting["PRIMARY_KEY"])

sqlite,我是用 type 标签去生成数据库表结构,工具查看数据库结构没有问题,上面的判断主键是否需要做个调整
Id int json:"id" gorm:"type:integer not null primary key autoincrement;"

提供一种基于流式分页的分页函数

在一些不需要跳页的场景下使用,相比于offset具有更好的性能。
伪代码如下:

type Comparable interface {
	~int | ~int8 | ~int16 | ~int32 | ~int64 | ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~float32 | ~float64 | time.Time
}

type StreamingPage[T any, V Comparable] struct {
	Column     string `json:"column"`     // 进行分页的列字段
	StartValue T      `json:"startValue"` // 分页起始值
	Limit      int    `json:"limit"`      // 页大小
	Forward    bool   `json:"forward"`    // 上下页翻页标识
	Total      int64  `json:"total"`      // 总记录数
	Records    []*T   `json:"records"`    // 查询记录
	RecordsMap []T    `json:"recordsMap"` // 查询记录Map
}

// StreamingPaginate 流式分页,根据自增ID、雪花ID、时间等数值类型或者时间类型分页
// Tips: 相比于 offset 分页性能更好,走的是 range,缺点是没办法跳页查询
func StreamingPaginate[T any, V Comparable](p *StreamingPage[T, V]) func(db *gorm.DB) *gorm.DB {
	return func(db *gorm.DB) *gorm.DB {
		// 下一页
		if p.Forward {
			return db.Where(fmt.Sprintf("%v > ?", p.Column), p.StartValue).Limit(p.Limit)
		}
		// 上一页
		return db.Where(fmt.Sprintf("%v < ?", p.Column), p.StartValue).Order(fmt.Sprintf("%v DESC", p.Column)).Limit(p.Limit)
	}
}

分页JSON序列化字段可以改成小驼峰吗?

Enviornment (please complete the following information):

  • OS: [Linux: uname -a]
  • envcd version [e.g. v0.0.1]

Describe the bug
A clear and concise description of what the bug is.

func page(c *gin.Context) {
	query := &QueryPage{}
	_ = c.ShouldBindQuery(query)
	page := gplus.NewPage[User](query.Current, query.PageSize)
	page, _ = gplus.SelectPage(page, gplus.BuildQuery[User](c.Request.URL.Query()))
	c.JSON(200, gin.H{
		"code": 0,
		"data": page,
	})
}
user.GET("/page", page)

返回

{
    "code": 0,
    "data": {
        "Current": 0,
        "Size": 0,
        "Total": 5,
        "Records": [
            {
                "id": 407,
                "username": "张三",
                "password": "password1",
                "address": "地址1",
                "age": 25,
                "phone": "12345678901",
                "score": 80,
                "dept": "部门1",
                "createdAt": "2023-08-12T07:03:05+08:00",
                "updatedAt": "2023-08-12T07:03:05+08:00"
            }
        ],
        "RecordsMap": null
    }
}

page的默认字段怎么改成小驼峰?

To Reproduce
Steps to reproduce the behavior:

  1. Go to '...'
  2. Click on '....'
  3. Scroll down to '....'
  4. See error

Expected behavior
A clear and concise description of what you expected to happen.

事务使用报错

下面这样应该也行
// 开启事务
tx := gplus.Begin()

// 使用defer,实现遇到错误时回滚
defer tx.Rollback()

// 新增,传入tx
student := Student{Name: "zhangsan", Age: 18, Email: "[email protected]", Birthday: time.Now()}
result := gplus.Insert(&student, tx)
...

// 更新,传入tx
query, model := gplus.NewQueryStudent
query.Eq(&model.Name, "zhangsan").Set(&model.Age, 30)
resultDb := gplus.Update[Student](query, tx)
...

// 查询,如果需要在事务中查询,则也需要传入tx
resultStudent, db := gplus.SelectById[Student](student.ID, tx)
...

// 提交事务,否则数据库数据不更改
tx.Commit()

按照这个方式传入tx,dao的方法需要一个OptionFunc
Cannot use 'tx' (type *gorm.DB) as the type OptionFunc

TestSelectListQueryModel Error

2023/06/19 10:40:49 D:/test/gorm-plus/gplus/dao.go:172
[0.000ms] [rows:0] SELECT * FROM Users ORDER BY username DESC,age ASC
--- FAIL: TestSelectListQueryModel (0.00s)
panic: interface conversion: interface {} is *tests.UserVo, not *tests.UserVo (types from different scopes) [recovered]
panic: interface conversion: interface {} is *tests.UserVo, not *tests.UserVo (types from different scopes)

goroutine 512 [running]:
testing.tRunner.func1.2({0x4f4280, 0xc00034f8f0})
D:/Program Files/golang/go1.20.3/src/testing/testing.go:1526 +0x24e
D:/Program Files/golang/go1.20.3/src/testing/testing.go:1576 +0x10b
created by testing.(*T).Run
D:/Program Files/golang/go1.20.3/src/testing/testing.go:1629 +0x3ea
exit status 2
FAIL github.com/acmestack/gorm-plus/tests 8.290s
PS D:\test\gorm-plus\tests>

建议结构体改为嵌套

新特性里面增加了字段忽略和条件查询的功能,里面用到的字段反射都是基于一层结构体的,建议改为下面的模式适配测试代码

type Base struct {
ID int64
CreatedAt time.Time
UpdatedAt time.Time
}

type User struct {
Base
Username string
Password string
Address string
Age int
Phone string
Score int
Dept string
}

func (User) TableName() string {
return "Users"
}

具体哪里改我还没看,有改法了我再提交申请

能不能通过接口来做的更通用一些?做成Repository层

Background & X Problem
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]

Proposal and Expectation
A clear and concise description of what you want to happen.

Solutions & Y Problems
A clear and concise description of any alternative solutions or features you've considered.

Additional context
Add any other context or screenshots about the feature request here.

关于连接池的问题

image

我这边用压测工具试了下。发现时间一长就出现
2024/02/03 12:14:52 C:/Users/lg/go/pkg/mod/github.com/acmestack/[email protected]/gplus/dao.go:172 dial tcp 127.0.0.1:3306: connectex: Only one usage of each socket address (protocol/network address/port) is normally permitted.
连接不够了。这个连接池的释放管理是不是有问题。下面查询语句一直都慢查询了。感觉似乎就是有空余连接没有释放卡住了。

还有另外一个是
2024/02/03 12:14:52 C:/Users/lg/go/pkg/mod/github.com/acmestack/[email protected]/gplus/dao.go:172 SLOW SQL >= 200ms
[7062.620ms] [rows:3] SELECT * FROM configs 这个日志计时的监控有没有办法关掉。

How to handle transaction in gorm-plus?

Background & X Problem
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]

Proposal and Expectation
A clear and concise description of what you want to happen.

Solutions & Y Problems
A clear and concise description of any alternative solutions or features you've considered.

Additional context
Add any other context or screenshots about the feature request here.

按条件判断拼接查询bug

伪代码如下:

        // 变量 user 为入参
	query, u := gplus.NewQuery[User]()
	query.Eq(&u.Password, "12345678").Or(func(q *gplus.QueryCond[User]) {
		q.EqCond(user.Id > 0,&u.Id, user.Id).
			Or().EqCond(len(user.Account) > 0, &u.Account, user.Account).
			Or().EqCond(len(user.Email) > 0, &u.Email, user.Email).
			Or().EqCond(len(user.Phone) > 0, &u.Phone, user.Phone)
	}).OrderByAsc(&u.Id)

当条件为false时,EqCond的条件不会加入到sql中,但是因为前面用了一个Or()作为条件关系,会出现这种情况:

SELECT * FROM `user` WHERE password = '12345678' OR ( id = 0 OR account = 'admin' OR )  ORDER BY id ASC LIMIT 1

应该所有的*Cond条件函数,在使用Or()或者And()之后都会出现这种情况,需要做特殊的适配,AndCond()和OrCond()没办法用在这种条件分组的情况

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.