commit
74cdcef637
20 changed files with 2173 additions and 339 deletions
47
dsl/api/api.go
Normal file
47
dsl/api/api.go
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/yaoapp/yao/dsl/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
// YaoAPI is the MCP client DSL manager
|
||||||
|
type YaoAPI struct {
|
||||||
|
root string // The relative path of the MCP client DSL
|
||||||
|
}
|
||||||
|
|
||||||
|
// New returns a new connector DSL manager
|
||||||
|
func New(root string) types.Manager {
|
||||||
|
return &YaoAPI{root: root}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Loaded return all loaded DSLs
|
||||||
|
func (api *YaoAPI) Loaded(ctx context.Context) (map[string]*types.Info, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load will unload the DSL first, then load the DSL from DB or file system
|
||||||
|
func (api *YaoAPI) Load(ctx context.Context, id string, options interface{}) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reload will unload the DSL first, then reload the DSL from DB or file system
|
||||||
|
func (api *YaoAPI) Reload(ctx context.Context, id string, options interface{}) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unload will unload the DSL from memory
|
||||||
|
func (api *YaoAPI) Unload(ctx context.Context, id string, options interface{}) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate will validate the DSL from source
|
||||||
|
func (api *YaoAPI) Validate(ctx context.Context, source string) (bool, []types.LintMessage) {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute will execute the DSL
|
||||||
|
func (api *YaoAPI) Execute(ctx context.Context, method string, args ...any) (any, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
47
dsl/connector/connector.go
Normal file
47
dsl/connector/connector.go
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
package connector
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/yaoapp/yao/dsl/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
// YaoConnector is the connector DSL manager
|
||||||
|
type YaoConnector struct {
|
||||||
|
root string // The relative path of the connector DSL
|
||||||
|
}
|
||||||
|
|
||||||
|
// New returns a new connector DSL manager
|
||||||
|
func New(root string) types.Manager {
|
||||||
|
return &YaoConnector{root: root}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Loaded return all loaded DSLs
|
||||||
|
func (c *YaoConnector) Loaded(ctx context.Context) (map[string]*types.Info, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load will unload the DSL first, then load the DSL from DB or file system
|
||||||
|
func (c *YaoConnector) Load(ctx context.Context, id string, options interface{}) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unload will unload the DSL from memory
|
||||||
|
func (c *YaoConnector) Unload(ctx context.Context, id string, options interface{}) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reload will unload the DSL first, then reload the DSL from DB or file system
|
||||||
|
func (c *YaoConnector) Reload(ctx context.Context, id string, options interface{}) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate will validate the DSL from source
|
||||||
|
func (c *YaoConnector) Validate(ctx context.Context, source string) (bool, []types.LintMessage) {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute will execute the DSL
|
||||||
|
func (c *YaoConnector) Execute(ctx context.Context, method string, args ...any) (any, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
277
dsl/db.go
Normal file
277
dsl/db.go
Normal file
|
|
@ -0,0 +1,277 @@
|
||||||
|
package dsl
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
jsoniter "github.com/json-iterator/go"
|
||||||
|
"github.com/yaoapp/gou/model"
|
||||||
|
"github.com/yaoapp/yao/dsl/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
// getInfoFromDB get the info from the db
|
||||||
|
func (dsl *DSL) dbInspect(id string) (*types.Info, bool, error) {
|
||||||
|
|
||||||
|
// Get from database
|
||||||
|
m := model.Select("__yao.dsl")
|
||||||
|
|
||||||
|
// Get the info
|
||||||
|
var info types.Info
|
||||||
|
rows, err := m.Get(model.QueryParam{
|
||||||
|
Wheres: []model.QueryWhere{{Column: "dsl_id", Value: id}},
|
||||||
|
Select: []interface{}{
|
||||||
|
"dsl_id",
|
||||||
|
"type",
|
||||||
|
"label",
|
||||||
|
"path",
|
||||||
|
"sort",
|
||||||
|
"tags",
|
||||||
|
"description",
|
||||||
|
"status",
|
||||||
|
"store",
|
||||||
|
"mtime",
|
||||||
|
"ctime",
|
||||||
|
},
|
||||||
|
Limit: 1,
|
||||||
|
Orders: []model.QueryOrder{{Column: "sort", Option: "asc"}, {Column: "mtime", Option: "desc"}},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(rows) == 0 {
|
||||||
|
return nil, false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
raw, err := jsoniter.Marshal(rows[0])
|
||||||
|
if err != nil {
|
||||||
|
return nil, false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = jsoniter.Unmarshal(raw, &info)
|
||||||
|
if err != nil {
|
||||||
|
return nil, false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &info, true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getSourceFromDB get the source from the db
|
||||||
|
func (dsl *DSL) dbSource(id string) (string, bool, error) {
|
||||||
|
|
||||||
|
// Get from database
|
||||||
|
m := model.Select("__yao.dsl")
|
||||||
|
|
||||||
|
// Get the source
|
||||||
|
rows, err := m.Get(model.QueryParam{
|
||||||
|
Wheres: []model.QueryWhere{{Column: "dsl_id", Value: id}},
|
||||||
|
Select: []interface{}{"source"},
|
||||||
|
Limit: 1,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return "", false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(rows) == 0 {
|
||||||
|
return "", false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
source, ok := rows[0]["source"].(string)
|
||||||
|
if !ok {
|
||||||
|
return "", true, fmt.Errorf("%s %s source is not a string", dsl.Type, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
return source, true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getListFromDB get the list from the db
|
||||||
|
func (dsl *DSL) dbList(options *types.ListOptions) ([]*types.Info, error) {
|
||||||
|
|
||||||
|
// Get from database
|
||||||
|
m := model.Select("__yao.dsl")
|
||||||
|
|
||||||
|
var orders []model.QueryOrder = []model.QueryOrder{{Column: "mtime", Option: "desc"}}
|
||||||
|
if options.Sort == "sort" {
|
||||||
|
orders = []model.QueryOrder{{Column: "sort", Option: "asc"}}
|
||||||
|
}
|
||||||
|
|
||||||
|
var wheres []model.QueryWhere = []model.QueryWhere{{Column: "type", Value: dsl.Type}}
|
||||||
|
|
||||||
|
// Filter by tags
|
||||||
|
if len(options.Tags) > 0 {
|
||||||
|
var orwheres []model.QueryWhere = []model.QueryWhere{}
|
||||||
|
for _, tag := range options.Tags {
|
||||||
|
match := "%" + strings.TrimSpace(tag) + "%"
|
||||||
|
orwheres = append(orwheres, model.QueryWhere{Column: "tags", Value: match, OP: "like", Method: "orwhere"})
|
||||||
|
}
|
||||||
|
wheres = append(wheres, model.QueryWhere{Wheres: orwheres})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the list
|
||||||
|
rows, err := m.Get(model.QueryParam{
|
||||||
|
Wheres: []model.QueryWhere{{Column: "type", Value: dsl.Type}},
|
||||||
|
Select: []interface{}{"dsl_id", "label", "path", "sort", "tags", "description", "status", "store", "mtime", "ctime"},
|
||||||
|
Orders: orders,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(rows) == 0 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var infos []*types.Info
|
||||||
|
raw, err := jsoniter.Marshal(rows)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = jsoniter.Unmarshal(raw, &infos)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return infos, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dsl *DSL) dbCreate(options *types.CreateOptions) error {
|
||||||
|
|
||||||
|
if options.Source == "" {
|
||||||
|
return fmt.Errorf("%s %s source is required", dsl.Type, options.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get info from source
|
||||||
|
var info types.Info
|
||||||
|
err := jsoniter.Unmarshal([]byte(options.Source), &info)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the info
|
||||||
|
m := model.Select("__yao.dsl")
|
||||||
|
data := map[string]interface{}{
|
||||||
|
"source": options.Source,
|
||||||
|
"dsl_id": options.ID,
|
||||||
|
"type": dsl.Type,
|
||||||
|
"label": info.Label,
|
||||||
|
"path": info.Path,
|
||||||
|
"sort": info.Sort,
|
||||||
|
"tags": info.Tags,
|
||||||
|
"description": info.Description,
|
||||||
|
"status": info.Status,
|
||||||
|
"store": info.Store,
|
||||||
|
"mtime": time.Now().Unix(),
|
||||||
|
"ctime": time.Now().Unix(),
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = m.Create(data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dsl *DSL) dbUpdate(options *types.UpdateOptions) error {
|
||||||
|
if options.Source == "" && options.Info == nil {
|
||||||
|
return fmt.Errorf("%s %s one of source or info is required", dsl.Type, options.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
m := model.Select("__yao.dsl")
|
||||||
|
|
||||||
|
// Check if the dsl exists
|
||||||
|
rows, err := m.Get(model.QueryParam{
|
||||||
|
Wheres: []model.QueryWhere{{Column: "dsl_id", Value: options.ID}},
|
||||||
|
Select: []interface{}{"id", "dsl_id"},
|
||||||
|
Limit: 1,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(rows) == 0 {
|
||||||
|
return fmt.Errorf("%s %s not found", dsl.Type, options.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
row := rows[0]
|
||||||
|
|
||||||
|
// update source
|
||||||
|
var data map[string]interface{} = map[string]interface{}{}
|
||||||
|
if options.Source != "" {
|
||||||
|
data["source"] = options.Source
|
||||||
|
} else {
|
||||||
|
// Update info
|
||||||
|
if options.Info.Label != "" {
|
||||||
|
data["label"] = options.Info.Label
|
||||||
|
}
|
||||||
|
|
||||||
|
if options.Info.Path != "" {
|
||||||
|
data["path"] = options.Info.Path
|
||||||
|
}
|
||||||
|
|
||||||
|
if options.Info.Sort != 0 {
|
||||||
|
data["sort"] = options.Info.Sort
|
||||||
|
}
|
||||||
|
|
||||||
|
if options.Info.Tags != nil {
|
||||||
|
data["tags"] = options.Info.Tags
|
||||||
|
}
|
||||||
|
|
||||||
|
if options.Info.Description != "" {
|
||||||
|
data["description"] = options.Info.Description
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the data
|
||||||
|
err = m.Update(row["id"], data)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dsl *DSL) dbDelete(id string) error {
|
||||||
|
|
||||||
|
// Get from database
|
||||||
|
m := model.Select("__yao.dsl")
|
||||||
|
|
||||||
|
// Check if the dsl exists
|
||||||
|
rows, err := m.Get(model.QueryParam{
|
||||||
|
Wheres: []model.QueryWhere{{Column: "dsl_id", Value: id}},
|
||||||
|
Select: []interface{}{"id", "dsl_id"},
|
||||||
|
Limit: 1,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(rows) == 0 {
|
||||||
|
return fmt.Errorf("%s %s not found", dsl.Type, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete the dsl
|
||||||
|
row := rows[0]
|
||||||
|
return m.Delete(row["id"])
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dsl *DSL) dbExists(id string) (bool, error) {
|
||||||
|
|
||||||
|
// Get from database
|
||||||
|
m := model.Select("__yao.dsl")
|
||||||
|
|
||||||
|
// Check if the dsl exists
|
||||||
|
rows, err := m.Get(model.QueryParam{
|
||||||
|
Wheres: []model.QueryWhere{{Column: "dsl_id", Value: id}},
|
||||||
|
Select: []interface{}{"id", "dsl_id"},
|
||||||
|
Limit: 1,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return len(rows) > 0, nil
|
||||||
|
}
|
||||||
319
dsl/dsl.go
Normal file
319
dsl/dsl.go
Normal file
|
|
@ -0,0 +1,319 @@
|
||||||
|
package dsl
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/yaoapp/yao/dsl/api"
|
||||||
|
"github.com/yaoapp/yao/dsl/connector"
|
||||||
|
"github.com/yaoapp/yao/dsl/mcp"
|
||||||
|
"github.com/yaoapp/yao/dsl/model"
|
||||||
|
"github.com/yaoapp/yao/dsl/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
// DSL is the base DSL struct
|
||||||
|
type DSL struct {
|
||||||
|
Type types.Type
|
||||||
|
exts []string
|
||||||
|
root string
|
||||||
|
manager types.Manager
|
||||||
|
}
|
||||||
|
|
||||||
|
// New returns a new DSL manager
|
||||||
|
func New(typ types.Type) (types.DSL, error) {
|
||||||
|
var manager types.Manager
|
||||||
|
|
||||||
|
// Get the root path and the extensions of the type
|
||||||
|
root, exts := types.TypeRootAndExts(typ)
|
||||||
|
|
||||||
|
// Create the manager
|
||||||
|
switch typ {
|
||||||
|
case types.TypeConnector:
|
||||||
|
exts = []string{".conn.yao", ".conn.jsonc", ".conn.json"}
|
||||||
|
manager = connector.New(root)
|
||||||
|
|
||||||
|
case types.TypeModel:
|
||||||
|
exts = []string{".mod.yao", ".mod.jsonc", ".mod.json"}
|
||||||
|
manager = model.New(root)
|
||||||
|
|
||||||
|
case types.TypeMCPClient:
|
||||||
|
exts = []string{".mcp.yao", ".mcp.jsonc", ".mcp.json"}
|
||||||
|
manager = mcp.NewClient(root)
|
||||||
|
|
||||||
|
// case types.TypeMCPServer:
|
||||||
|
// exts = []string{".mcp.yao", ".mcp.jsonc", ".mcp.json"}
|
||||||
|
// manager = mcp.NewServer(root)
|
||||||
|
|
||||||
|
case types.TypeAPI:
|
||||||
|
exts = []string{".http.yao", ".http.jsonc", ".http.json"}
|
||||||
|
manager = api.New(root)
|
||||||
|
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("dsl manager is not initialized, %s not supported", typ)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &DSL{Type: typ, manager: manager, root: root, exts: exts}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inspect DSL
|
||||||
|
func (dsl *DSL) Inspect(ctx context.Context, id string) (*types.Info, error) {
|
||||||
|
|
||||||
|
// Get the info from the db
|
||||||
|
info, exists, err := dsl.dbInspect(id)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !exists {
|
||||||
|
// Get the info from the file
|
||||||
|
info, exists, err = dsl.fsInspect(types.ToPath(dsl.Type, id))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !exists {
|
||||||
|
return nil, fmt.Errorf("dsl not found, %s", id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Merge the status from the manager
|
||||||
|
loaded, err := dsl.manager.Loaded(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return info, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the DSL is loaded
|
||||||
|
if _, ok := loaded[id]; ok {
|
||||||
|
info.Status = types.StatusLoaded
|
||||||
|
}
|
||||||
|
|
||||||
|
return info, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Path Get Path by id, ( If the DSL is saved as file, return the file path )
|
||||||
|
func (dsl *DSL) Path(ctx context.Context, id string) (string, error) {
|
||||||
|
return types.ToPath(dsl.Type, id), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Source Get Source by id
|
||||||
|
func (dsl *DSL) Source(ctx context.Context, id string) (string, error) {
|
||||||
|
|
||||||
|
// Get the source from the db
|
||||||
|
source, exists, err := dsl.dbSource(id)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !exists {
|
||||||
|
// Get the source from the file
|
||||||
|
source, exists, err = dsl.fsSource(types.ToPath(dsl.Type, id))
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !exists {
|
||||||
|
return "", fmt.Errorf("%s DSL not found, %s", dsl.Type, id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return source, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// List DSLs
|
||||||
|
func (dsl *DSL) List(ctx context.Context, opts *types.ListOptions) ([]*types.Info, error) {
|
||||||
|
// Get the list from the db
|
||||||
|
dbList, err := dsl.dbList(opts)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the list from the file
|
||||||
|
fileList, err := dsl.fsList(opts)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Merge the list and unique
|
||||||
|
list := []*types.Info{}
|
||||||
|
unique := make(map[string]bool)
|
||||||
|
for _, info := range dbList {
|
||||||
|
if _, ok := unique[info.ID]; !ok {
|
||||||
|
list = append(list, info)
|
||||||
|
unique[info.ID] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for _, info := range fileList {
|
||||||
|
if _, ok := unique[info.ID]; !ok {
|
||||||
|
list = append(list, info)
|
||||||
|
unique[info.ID] = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Merge the status from the manager
|
||||||
|
loaded, err := dsl.manager.Loaded(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Merge the status from the manager
|
||||||
|
for _, info := range list {
|
||||||
|
if _, ok := loaded[info.ID]; ok {
|
||||||
|
info.Status = types.StatusLoaded
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return list, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create DSL
|
||||||
|
func (dsl *DSL) Create(ctx context.Context, options *types.CreateOptions) error {
|
||||||
|
if options.Store == types.StoreTypeDB {
|
||||||
|
err := dsl.dbCreate(options)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if options.Store == types.StoreTypeFile {
|
||||||
|
err := dsl.fsCreate(options)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load the DSL
|
||||||
|
err := dsl.Load(ctx, options.ID, options.LoadOptions)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exists Check if the DSL exists
|
||||||
|
func (dsl *DSL) Exists(ctx context.Context, id string) (bool, error) {
|
||||||
|
// Check if the DSL exists in the db
|
||||||
|
exists, err := dsl.dbExists(id)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if exists {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if the DSL exists in the file
|
||||||
|
return dsl.fsExists(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update DSL
|
||||||
|
func (dsl *DSL) Update(ctx context.Context, options *types.UpdateOptions) error {
|
||||||
|
|
||||||
|
// Exists
|
||||||
|
info, exists, err := dsl.dbInspect(options.ID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !exists {
|
||||||
|
info, exists, err = dsl.fsInspect(types.ToPath(dsl.Type, options.ID))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !exists {
|
||||||
|
return fmt.Errorf("%s DSL not found, %s", dsl.Type, options.ID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the DSL in the db
|
||||||
|
if info.Store == types.StoreTypeDB {
|
||||||
|
err := dsl.dbUpdate(options)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reload the DSL
|
||||||
|
return dsl.manager.Reload(ctx, options.ID, options.ReloadOptions)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the DSL in the file
|
||||||
|
err = dsl.fsUpdate(options)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reload the DSL
|
||||||
|
return dsl.manager.Reload(ctx, options.ID, options.ReloadOptions)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete DSL
|
||||||
|
func (dsl *DSL) Delete(ctx context.Context, id string, options ...interface{}) error {
|
||||||
|
// Exists
|
||||||
|
info, exists, err := dsl.dbInspect(id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !exists {
|
||||||
|
info, exists, err = dsl.fsInspect(types.ToPath(dsl.Type, id))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !exists {
|
||||||
|
return fmt.Errorf("%s DSL not found, %s", dsl.Type, id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var unloadOptions interface{}
|
||||||
|
if len(options) > 0 {
|
||||||
|
unloadOptions = options[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
if info.Store == types.StoreTypeDB {
|
||||||
|
err = dsl.dbDelete(id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unload the DSL
|
||||||
|
return dsl.manager.Unload(ctx, id, unloadOptions)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = dsl.fsDelete(id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unload the DSL
|
||||||
|
return dsl.manager.Unload(ctx, id, unloadOptions)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load DSL
|
||||||
|
func (dsl *DSL) Load(ctx context.Context, id string, options interface{}) error {
|
||||||
|
return dsl.manager.Load(ctx, id, options)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unload DSL
|
||||||
|
func (dsl *DSL) Unload(ctx context.Context, id string, options ...interface{}) error {
|
||||||
|
var unloadOptions interface{}
|
||||||
|
if len(options) > 0 {
|
||||||
|
unloadOptions = options[0]
|
||||||
|
}
|
||||||
|
return dsl.manager.Unload(ctx, id, unloadOptions)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reload DSL
|
||||||
|
func (dsl *DSL) Reload(ctx context.Context, id string, options interface{}) error {
|
||||||
|
return dsl.manager.Reload(ctx, id, options)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute DSL (Some DSLs can be executed)
|
||||||
|
func (dsl *DSL) Execute(ctx context.Context, method string, args ...any) (any, error) {
|
||||||
|
return dsl.manager.Execute(ctx, method, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate DSL
|
||||||
|
func (dsl *DSL) Validate(ctx context.Context, source string) (bool, []types.LintMessage) {
|
||||||
|
return dsl.manager.Validate(ctx, source)
|
||||||
|
}
|
||||||
203
dsl/fs.go
Normal file
203
dsl/fs.go
Normal file
|
|
@ -0,0 +1,203 @@
|
||||||
|
package dsl
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
jsoniter "github.com/json-iterator/go"
|
||||||
|
"github.com/yaoapp/gou/application"
|
||||||
|
"github.com/yaoapp/yao/dsl/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
// getInfoFromFile get the info from the file
|
||||||
|
func (dsl *DSL) fsInspect(id string, path ...string) (*types.Info, bool, error) {
|
||||||
|
file := ""
|
||||||
|
if len(path) > 0 {
|
||||||
|
file = path[0]
|
||||||
|
} else {
|
||||||
|
file = types.ToPath(dsl.Type, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
var info types.Info = types.Info{ID: id, Path: file}
|
||||||
|
exists, err := application.App.Exists(file)
|
||||||
|
if err != nil {
|
||||||
|
return nil, false, err
|
||||||
|
}
|
||||||
|
if !exists {
|
||||||
|
return nil, false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read the file
|
||||||
|
data, err := application.App.Read(file)
|
||||||
|
if err != nil {
|
||||||
|
return nil, false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unmarshal the data to the info
|
||||||
|
err = application.Parse(file, data, &info)
|
||||||
|
if err != nil {
|
||||||
|
return nil, true, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Merge the mtime and ctime
|
||||||
|
fileInfo, err := application.App.Info(file)
|
||||||
|
if err != nil {
|
||||||
|
return nil, true, err
|
||||||
|
}
|
||||||
|
|
||||||
|
info.Mtime = fileInfo.ModTime()
|
||||||
|
info.Ctime = fileInfo.ModTime()
|
||||||
|
return &info, true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getSourceFromFile get the source from the file
|
||||||
|
func (dsl *DSL) fsSource(id string) (string, bool, error) {
|
||||||
|
path := types.ToPath(dsl.Type, id)
|
||||||
|
exists, err := application.App.Exists(path)
|
||||||
|
if err != nil {
|
||||||
|
return "", false, err
|
||||||
|
}
|
||||||
|
if !exists {
|
||||||
|
return "", false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read the file
|
||||||
|
data, err := application.App.Read(path)
|
||||||
|
if err != nil {
|
||||||
|
return "", false, err
|
||||||
|
}
|
||||||
|
return string(data), true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getListFromPath get the list from the path
|
||||||
|
func (dsl *DSL) fsList(options *types.ListOptions) ([]*types.Info, error) {
|
||||||
|
root, exts := types.TypeRootAndExts(dsl.Type)
|
||||||
|
var infos []*types.Info = []*types.Info{}
|
||||||
|
patterns := []string{}
|
||||||
|
for _, ext := range exts {
|
||||||
|
patterns = append(patterns, "*"+ext)
|
||||||
|
}
|
||||||
|
var errs []error
|
||||||
|
err := application.App.Walk(root, func(root, file string, isdir bool) error {
|
||||||
|
if isdir {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
id := types.WithTypeToID(dsl.Type, file)
|
||||||
|
info, _, err := dsl.fsInspect(id, file)
|
||||||
|
if err != nil {
|
||||||
|
errs = append(errs, err)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Filter by options
|
||||||
|
if len(options.Tags) > 0 {
|
||||||
|
if len(info.Tags) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tag := range options.Tags {
|
||||||
|
for _, t := range info.Tags {
|
||||||
|
if t == tag {
|
||||||
|
infos = append(infos, info)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add to the list
|
||||||
|
infos = append(infos, info)
|
||||||
|
return err
|
||||||
|
}, patterns...)
|
||||||
|
|
||||||
|
return infos, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dsl *DSL) fsCreate(options *types.CreateOptions) error {
|
||||||
|
|
||||||
|
path := types.ToPath(dsl.Type, options.ID)
|
||||||
|
|
||||||
|
// Check if the file is a directory
|
||||||
|
exists, err := application.App.Exists(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if exists {
|
||||||
|
return fmt.Errorf("%v %s already exists", dsl.Type, options.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create the file
|
||||||
|
return application.App.Write(path, []byte(options.Source))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dsl *DSL) fsUpdate(options *types.UpdateOptions) error {
|
||||||
|
|
||||||
|
// Validate the options
|
||||||
|
if options.Source == "" && options.Info == nil {
|
||||||
|
return fmt.Errorf("%v %s one of source or info is required", dsl.Type, options.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
path := types.ToPath(dsl.Type, options.ID)
|
||||||
|
|
||||||
|
// Check if the file exists
|
||||||
|
exists, err := application.App.Exists(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !exists {
|
||||||
|
return fmt.Errorf("%v %s not found", dsl.Type, options.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update source
|
||||||
|
if options.Source != "" {
|
||||||
|
return application.App.Write(path, []byte(options.Source))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update info
|
||||||
|
var source map[string]interface{}
|
||||||
|
data, err := application.App.Read(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
err = application.Parse(path, data, &source)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the info
|
||||||
|
source["id"] = options.ID
|
||||||
|
source["label"] = options.Info.Label
|
||||||
|
source["tags"] = options.Info.Tags
|
||||||
|
source["description"] = options.Info.Description
|
||||||
|
new, err := jsoniter.MarshalIndent(source, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return application.App.Write(path, []byte(new))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dsl *DSL) fsDelete(id string) error {
|
||||||
|
|
||||||
|
path := types.ToPath(dsl.Type, id)
|
||||||
|
|
||||||
|
// Check if the file is a directory
|
||||||
|
exists, err := application.App.Exists(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !exists {
|
||||||
|
return fmt.Errorf("%v %s not found", dsl.Type, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete the file
|
||||||
|
return application.App.Remove(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (dsl *DSL) fsExists(id string) (bool, error) {
|
||||||
|
path := types.ToPath(dsl.Type, id)
|
||||||
|
return application.App.Exists(path)
|
||||||
|
}
|
||||||
52
dsl/mcp/client.go
Normal file
52
dsl/mcp/client.go
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
package mcp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/yaoapp/yao/dsl/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
// YaoMCPClient is the MCP client DSL manager
|
||||||
|
type YaoMCPClient struct {
|
||||||
|
root string // The relative path of the MCP client DSL
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewClient returns a new MCP client DSL manager
|
||||||
|
func NewClient(root string) types.Manager {
|
||||||
|
return New(root)
|
||||||
|
}
|
||||||
|
|
||||||
|
// New returns a new connector DSL manager
|
||||||
|
func New(root string) types.Manager {
|
||||||
|
return &YaoMCPClient{root: root}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Loaded return all loaded DSLs
|
||||||
|
func (client *YaoMCPClient) Loaded(ctx context.Context) (map[string]*types.Info, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load will unload the DSL first, then load the DSL from DB or file system
|
||||||
|
func (client *YaoMCPClient) Load(ctx context.Context, id string, options interface{}) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unload will unload the DSL from memory
|
||||||
|
func (client *YaoMCPClient) Unload(ctx context.Context, id string, options interface{}) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reload will unload the DSL first, then reload the DSL from DB or file system
|
||||||
|
func (client *YaoMCPClient) Reload(ctx context.Context, id string, options interface{}) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate will validate the DSL from source
|
||||||
|
func (client *YaoMCPClient) Validate(ctx context.Context, source string) (bool, []types.LintMessage) {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute will execute the DSL
|
||||||
|
func (client *YaoMCPClient) Execute(ctx context.Context, method string, args ...any) (any, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
47
dsl/mcp/server.go
Normal file
47
dsl/mcp/server.go
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
package mcp
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/yaoapp/yao/dsl/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
// YaoMCPServer is the MCP client DSL manager
|
||||||
|
type YaoMCPServer struct {
|
||||||
|
root string // The relative path of the MCP client DSL
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewServer returns a new MCP server DSL manager
|
||||||
|
func NewServer(root string) types.Manager {
|
||||||
|
return &YaoMCPServer{root: root}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Loaded return all loaded DSLs
|
||||||
|
func (server *YaoMCPServer) Loaded(ctx context.Context) (map[string]*types.Info, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load will unload the DSL first, then load the DSL from DB or file system
|
||||||
|
func (server *YaoMCPServer) Load(ctx context.Context, id string, options interface{}) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unload will unload the DSL from memory
|
||||||
|
func (server *YaoMCPServer) Unload(ctx context.Context, id string, options interface{}) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reload will unload the DSL first, then reload the DSL from DB or file system
|
||||||
|
func (server *YaoMCPServer) Reload(ctx context.Context, id string, options interface{}) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate will validate the DSL from source
|
||||||
|
func (server *YaoMCPServer) Validate(ctx context.Context, source string) (bool, []types.LintMessage) {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute will execute the DSL
|
||||||
|
func (server *YaoMCPServer) Execute(ctx context.Context, method string, args ...any) (any, error) {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
139
dsl/model/model.go
Normal file
139
dsl/model/model.go
Normal file
|
|
@ -0,0 +1,139 @@
|
||||||
|
package model
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/yaoapp/gou/model"
|
||||||
|
"github.com/yaoapp/yao/dsl/types"
|
||||||
|
)
|
||||||
|
|
||||||
|
// YaoModel is the MCP client DSL manager
|
||||||
|
type YaoModel struct {
|
||||||
|
root string // The relative path of the MCP client DSL
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewClient returns a new MCP client DSL manager
|
||||||
|
func NewClient(root string) types.Manager {
|
||||||
|
return New(root)
|
||||||
|
}
|
||||||
|
|
||||||
|
// New returns a new connector DSL manager
|
||||||
|
func New(root string) types.Manager {
|
||||||
|
return &YaoModel{root: root}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Loaded return all loaded DSLs
|
||||||
|
func (m *YaoModel) Loaded(ctx context.Context) (map[string]*types.Info, error) {
|
||||||
|
|
||||||
|
infos := map[string]*types.Info{}
|
||||||
|
for id, mod := range model.Models {
|
||||||
|
infos[id] = &types.Info{
|
||||||
|
ID: id,
|
||||||
|
Type: types.TypeModel,
|
||||||
|
Label: mod.MetaData.Name,
|
||||||
|
Path: mod.File,
|
||||||
|
Sort: 999,
|
||||||
|
Tags: []string{},
|
||||||
|
Description: "Description",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return infos, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load will unload the DSL first, then load the DSL from DB or file system
|
||||||
|
func (m *YaoModel) Load(ctx context.Context, id string, options interface{}) error {
|
||||||
|
var opts map[string]interface{}
|
||||||
|
if v, ok := options.(map[string]interface{}); ok {
|
||||||
|
opts = v
|
||||||
|
}
|
||||||
|
|
||||||
|
var migration bool = false
|
||||||
|
if v, ok := opts["migration"]; ok {
|
||||||
|
migration = v.(bool)
|
||||||
|
}
|
||||||
|
|
||||||
|
var reset bool = false
|
||||||
|
if v, ok := opts["reset"]; ok {
|
||||||
|
reset = v.(bool)
|
||||||
|
}
|
||||||
|
|
||||||
|
path := types.ToPath(types.TypeModel, id)
|
||||||
|
mod, err := model.LoadSync(path, id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if migration || reset {
|
||||||
|
return mod.Migrate(reset, model.WithDonotInsertValues(true))
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unload will unload the DSL from memory
|
||||||
|
func (m *YaoModel) Unload(ctx context.Context, id string, options interface{}) error {
|
||||||
|
|
||||||
|
var opts map[string]interface{}
|
||||||
|
if v, ok := options.(map[string]interface{}); ok {
|
||||||
|
opts = v
|
||||||
|
}
|
||||||
|
|
||||||
|
var dropTable bool = false
|
||||||
|
if v, ok := opts["dropTable"]; ok {
|
||||||
|
dropTable = v.(bool)
|
||||||
|
}
|
||||||
|
|
||||||
|
mod := model.Select(id)
|
||||||
|
if mod == nil {
|
||||||
|
return fmt.Errorf("model %s not found", id)
|
||||||
|
}
|
||||||
|
|
||||||
|
if dropTable {
|
||||||
|
return mod.DropTable()
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reload will unload the DSL first, then reload the DSL from DB or file system
|
||||||
|
func (m *YaoModel) Reload(ctx context.Context, id string, options interface{}) error {
|
||||||
|
|
||||||
|
var opts map[string]interface{}
|
||||||
|
if v, ok := options.(map[string]interface{}); ok {
|
||||||
|
opts = v
|
||||||
|
}
|
||||||
|
|
||||||
|
var migration bool = false
|
||||||
|
if v, ok := opts["migration"]; ok {
|
||||||
|
migration = v.(bool)
|
||||||
|
}
|
||||||
|
|
||||||
|
var reset bool = false
|
||||||
|
if v, ok := opts["reset"]; ok {
|
||||||
|
reset = v.(bool)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reload the model
|
||||||
|
path := types.ToPath(types.TypeModel, id)
|
||||||
|
mod, err := model.LoadSync(path, id)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if migration || reset {
|
||||||
|
return mod.Migrate(reset, model.WithDonotInsertValues(true))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate will validate the DSL from source
|
||||||
|
func (m *YaoModel) Validate(ctx context.Context, source string) (bool, []types.LintMessage) {
|
||||||
|
return true, []types.LintMessage{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute will execute the DSL
|
||||||
|
func (m *YaoModel) Execute(ctx context.Context, method string, args ...any) (any, error) {
|
||||||
|
return nil, fmt.Errorf("Not implemented")
|
||||||
|
}
|
||||||
49
dsl/types/interfaces.go
Normal file
49
dsl/types/interfaces.go
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
package types
|
||||||
|
|
||||||
|
import "context"
|
||||||
|
|
||||||
|
// DSL interface
|
||||||
|
type DSL interface {
|
||||||
|
Inspect(ctx context.Context, id string) (*Info, error) // Inspect DSL
|
||||||
|
Path(ctx context.Context, id string) (string, error) // Get Path by id, ( If the DSL is saved as file, return the file path )
|
||||||
|
Source(ctx context.Context, id string) (string, error) // Get Source by id
|
||||||
|
List(ctx context.Context, opts *ListOptions) ([]*Info, error) // List All DSLs including unloaded/error DSLs
|
||||||
|
Exists(ctx context.Context, id string) (bool, error) // Check if the DSL exists
|
||||||
|
|
||||||
|
// DSL Operations
|
||||||
|
Create(ctx context.Context, options *CreateOptions) error // Create DSL, Create will unload the DSL first, then create the DSL to DB
|
||||||
|
Update(ctx context.Context, options *UpdateOptions) error // Update DSL, Update will unload the DSL first, then update the DSL, if update info only, will not unload the DSL
|
||||||
|
Delete(ctx context.Context, id string, unloadOptions ...interface{}) error // Delete DSL, Delete will unload the DSL first, then delete the DSL file
|
||||||
|
|
||||||
|
// Load manager
|
||||||
|
Load(ctx context.Context, id string, options interface{}) error // Load DSL, Load will unload the DSL first, then load the DSL from DB or file system
|
||||||
|
Reload(ctx context.Context, id string, options interface{}) error // Reload DSL, Reload will unload the DSL first, then reload the DSL from DB or file system
|
||||||
|
Unload(ctx context.Context, id string, options ...interface{}) error // Unload DSL, Unload will unload the DSL from memory
|
||||||
|
|
||||||
|
// Execute
|
||||||
|
Execute(ctx context.Context, method string, args ...any) (any, error) // Execute DSL (Some DSLs can be executed)
|
||||||
|
|
||||||
|
// Validate
|
||||||
|
Validate(ctx context.Context, source string) (bool, []LintMessage) // Validate DSL, Validate will validate the DSL from source
|
||||||
|
}
|
||||||
|
|
||||||
|
// Manager interface
|
||||||
|
type Manager interface {
|
||||||
|
// Get all loaded DSLs
|
||||||
|
Loaded(ctx context.Context) (map[string]*Info, error) // Get all loaded DSLs
|
||||||
|
|
||||||
|
// Load DSL, Load will unload the DSL first, then load the DSL from DB or file system
|
||||||
|
Load(ctx context.Context, id string, options interface{}) error
|
||||||
|
|
||||||
|
// Unload DSL, Unload will unload the DSL from memory
|
||||||
|
Unload(ctx context.Context, id string, options interface{}) error
|
||||||
|
|
||||||
|
// Reload DSL, Reload will unload the DSL first, then reload the DSL from DB or file system
|
||||||
|
Reload(ctx context.Context, id string, options interface{}) error
|
||||||
|
|
||||||
|
// Validate DSL, Validate will validate the DSL from source
|
||||||
|
Validate(ctx context.Context, source string) (bool, []LintMessage)
|
||||||
|
|
||||||
|
// Execute DSL (Some DSLs can be executed)
|
||||||
|
Execute(ctx context.Context, method string, args ...any) (any, error)
|
||||||
|
}
|
||||||
126
dsl/types/types.go
Normal file
126
dsl/types/types.go
Normal file
|
|
@ -0,0 +1,126 @@
|
||||||
|
package types
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Type for DSL
|
||||||
|
type Type string
|
||||||
|
|
||||||
|
// Status for DSL
|
||||||
|
type Status string
|
||||||
|
|
||||||
|
// StoreType for DSL store
|
||||||
|
type StoreType string
|
||||||
|
|
||||||
|
// LintSeverity for DSL linter
|
||||||
|
type LintSeverity string
|
||||||
|
|
||||||
|
// StoreType for DSL store
|
||||||
|
const (
|
||||||
|
StoreTypeDB StoreType = "db"
|
||||||
|
StoreTypeFile StoreType = "file"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Status for DSL
|
||||||
|
const (
|
||||||
|
StatusLoading Status = "loading"
|
||||||
|
StatusLoaded Status = "loaded"
|
||||||
|
StatusError Status = "error"
|
||||||
|
)
|
||||||
|
|
||||||
|
// LintSeverity for DSL linter
|
||||||
|
const (
|
||||||
|
LintSeverityError LintSeverity = "error"
|
||||||
|
LintSeverityWarning LintSeverity = "warning"
|
||||||
|
LintSeverityInfo LintSeverity = "info"
|
||||||
|
LintSeverityHint LintSeverity = "hint"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Type for DSL
|
||||||
|
const (
|
||||||
|
// TypeModel for model
|
||||||
|
TypeModel Type = "model"
|
||||||
|
// TypeAPI for api
|
||||||
|
TypeAPI Type = "api"
|
||||||
|
// TypeConnector for connector
|
||||||
|
TypeConnector Type = "connector"
|
||||||
|
// TypeMCPServer for MCP server
|
||||||
|
TypeMCPServer Type = "mcp-server"
|
||||||
|
// TypeMCPClient for MCP client
|
||||||
|
TypeMCPClient Type = "mcp-client"
|
||||||
|
// TypeStore for store
|
||||||
|
TypeStore Type = "store"
|
||||||
|
// TypeSchedule for schedule
|
||||||
|
TypeSchedule Type = "schedule"
|
||||||
|
|
||||||
|
// TypeTable for table
|
||||||
|
TypeTable Type = "table"
|
||||||
|
// TypeForm for form
|
||||||
|
TypeForm Type = "form"
|
||||||
|
// TypeList for list
|
||||||
|
TypeList Type = "list"
|
||||||
|
// TypeChart for chart
|
||||||
|
TypeChart Type = "chart"
|
||||||
|
// TypeDashboard for dashboard
|
||||||
|
TypeDashboard Type = "dashboard"
|
||||||
|
|
||||||
|
// TypeFlow for flow
|
||||||
|
TypeFlow Type = "flow"
|
||||||
|
// TypePipe for pipe
|
||||||
|
TypePipe Type = "pipe"
|
||||||
|
// TypeAIGC for aigc
|
||||||
|
TypeAIGC Type = "aigc"
|
||||||
|
|
||||||
|
// TypeUnknown for unknown
|
||||||
|
TypeUnknown Type = "unknown"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Info for DSL
|
||||||
|
type Info struct {
|
||||||
|
ID string
|
||||||
|
Type Type
|
||||||
|
Sort int
|
||||||
|
Path string
|
||||||
|
Label string
|
||||||
|
Description string
|
||||||
|
Tags []string
|
||||||
|
Status Status
|
||||||
|
Store StoreType
|
||||||
|
Mtime time.Time
|
||||||
|
Ctime time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListOptions for DSL list
|
||||||
|
type ListOptions struct {
|
||||||
|
Sort string
|
||||||
|
Order string
|
||||||
|
Tags []string
|
||||||
|
}
|
||||||
|
|
||||||
|
// CreateOptions for DSL upsert
|
||||||
|
type CreateOptions struct {
|
||||||
|
ID string // ID is the id of the DSL, if not provided, a new id will be generated, required
|
||||||
|
Source string // Source is the source of the DSL, if not provided, the DSL will be loaded from the file system
|
||||||
|
Store StoreType // Store is the store type of the DSL, if not provided, the DSL will be loaded from the file system
|
||||||
|
LoadOptions interface{} // LoadOptions is the options for the DSL, if not provided, the DSL will be loaded from the file system
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateOptions for DSL upsert
|
||||||
|
type UpdateOptions struct {
|
||||||
|
ID string // ID is the id of the DSL, if not provided, a new id will be generated, required
|
||||||
|
Info *Info // Info is the info of the DSL, if not provided, the DSL will be loaded from the file system, one of info or source must be provided
|
||||||
|
Source string // Source is the source of the DSL, if not provided, the DSL will be loaded from the file system, one of info or source must be provided
|
||||||
|
ReloadOptions interface{} // ReloadOptions is the options for the DSL, if not provided, the DSL will be loaded from the file system
|
||||||
|
}
|
||||||
|
|
||||||
|
// LintMessage for DSL linter
|
||||||
|
type LintMessage struct {
|
||||||
|
File string
|
||||||
|
Line int
|
||||||
|
Column int
|
||||||
|
Message string
|
||||||
|
Severity LintSeverity
|
||||||
|
}
|
||||||
|
|
||||||
|
var lintMessages []LintMessage
|
||||||
194
dsl/types/utils.go
Normal file
194
dsl/types/utils.go
Normal file
|
|
@ -0,0 +1,194 @@
|
||||||
|
package types
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ToPath convert id to path
|
||||||
|
func ToPath(typ Type, id string) string {
|
||||||
|
|
||||||
|
// Get the root path and the extensions of the type
|
||||||
|
root, exts := TypeRootAndExts(typ)
|
||||||
|
ext := ".yao"
|
||||||
|
if len(exts) > 0 {
|
||||||
|
ext = exts[0]
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. Replace all . to /
|
||||||
|
path := strings.ReplaceAll(id, ".", string(os.PathSeparator))
|
||||||
|
// 2. Replace all __ to .
|
||||||
|
path = strings.ReplaceAll(path, "__", ".")
|
||||||
|
// 3. Join the root path
|
||||||
|
return filepath.Join(root, path) + ext
|
||||||
|
}
|
||||||
|
|
||||||
|
// ToID convert file path to id
|
||||||
|
func ToID(path string) string {
|
||||||
|
typ := DetectType(path)
|
||||||
|
return WithTypeToID(typ, path)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WithTypeToID convert file path to id
|
||||||
|
func WithTypeToID(typ Type, path string) string {
|
||||||
|
|
||||||
|
// Get the root path and the extensions of the type
|
||||||
|
root, exts := TypeRootAndExts(typ)
|
||||||
|
|
||||||
|
// 0. if the first character is /, remove it
|
||||||
|
if strings.HasPrefix(path, string(os.PathSeparator)) {
|
||||||
|
path = strings.TrimPrefix(path, string(os.PathSeparator))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. Split the path by /
|
||||||
|
parts := strings.Split(path, string(os.PathSeparator))
|
||||||
|
if len(parts) > 0 && parts[0] == root {
|
||||||
|
// Skip the root path
|
||||||
|
parts = parts[1:]
|
||||||
|
|
||||||
|
// Remove the extension only if parts is not empty
|
||||||
|
if len(parts) > 0 {
|
||||||
|
last := parts[len(parts)-1]
|
||||||
|
for _, ext := range exts {
|
||||||
|
if strings.HasSuffix(last, ext) {
|
||||||
|
parts[len(parts)-1] = strings.TrimSuffix(last, ext)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Join the parts
|
||||||
|
path = strings.Join(parts, string(os.PathSeparator))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Replace All . to __
|
||||||
|
path = strings.ReplaceAll(path, ".", "__")
|
||||||
|
|
||||||
|
// 3. Replace all / to .
|
||||||
|
path = strings.ReplaceAll(path, string(os.PathSeparator), ".")
|
||||||
|
|
||||||
|
return path
|
||||||
|
}
|
||||||
|
|
||||||
|
// DetectType detect the type by the file path
|
||||||
|
func DetectType(path string) Type {
|
||||||
|
parts := strings.Split(path, string(os.PathSeparator))
|
||||||
|
if len(parts) < 2 {
|
||||||
|
return TypeUnknown
|
||||||
|
}
|
||||||
|
|
||||||
|
root := parts[0]
|
||||||
|
last := parts[len(parts)-1]
|
||||||
|
extParts := strings.Split(last, ".")
|
||||||
|
if len(extParts) < 2 {
|
||||||
|
return TypeUnknown
|
||||||
|
}
|
||||||
|
ext := extParts[len(extParts)-2]
|
||||||
|
|
||||||
|
// Detect the type by the extension
|
||||||
|
switch ext {
|
||||||
|
case "http":
|
||||||
|
return TypeAPI
|
||||||
|
case "sch":
|
||||||
|
return TypeSchedule
|
||||||
|
case "table":
|
||||||
|
return TypeTable
|
||||||
|
case "form":
|
||||||
|
return TypeForm
|
||||||
|
case "list":
|
||||||
|
return TypeList
|
||||||
|
case "chart":
|
||||||
|
return TypeChart
|
||||||
|
case "dash":
|
||||||
|
return TypeDashboard
|
||||||
|
case "flow":
|
||||||
|
return TypeFlow
|
||||||
|
case "pipe":
|
||||||
|
return TypePipe
|
||||||
|
case "ai":
|
||||||
|
return TypeAIGC
|
||||||
|
case "mod":
|
||||||
|
return TypeModel
|
||||||
|
case "conn":
|
||||||
|
return TypeConnector
|
||||||
|
case "lru", "redis", "mongo", "badger":
|
||||||
|
return TypeStore
|
||||||
|
}
|
||||||
|
|
||||||
|
// Detect the type by the root path
|
||||||
|
switch root {
|
||||||
|
case "models":
|
||||||
|
return TypeModel
|
||||||
|
case "connectors":
|
||||||
|
return TypeConnector
|
||||||
|
case "mcps":
|
||||||
|
return TypeMCPClient
|
||||||
|
case "apis":
|
||||||
|
if ext == "http" {
|
||||||
|
return TypeAPI
|
||||||
|
}
|
||||||
|
if ext == "mcp" {
|
||||||
|
return TypeMCPServer
|
||||||
|
}
|
||||||
|
return TypeUnknown
|
||||||
|
case "schedules":
|
||||||
|
return TypeSchedule
|
||||||
|
case "tables":
|
||||||
|
return TypeTable
|
||||||
|
case "forms":
|
||||||
|
return TypeForm
|
||||||
|
case "lists":
|
||||||
|
return TypeList
|
||||||
|
case "charts":
|
||||||
|
return TypeChart
|
||||||
|
case "dashboards":
|
||||||
|
return TypeDashboard
|
||||||
|
case "flows":
|
||||||
|
return TypeFlow
|
||||||
|
case "pipes":
|
||||||
|
return TypePipe
|
||||||
|
case "aigcs":
|
||||||
|
return TypeAIGC
|
||||||
|
case "stores":
|
||||||
|
return TypeStore
|
||||||
|
default:
|
||||||
|
return TypeUnknown
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// TypeRootAndExts return the root path and the extensions of the type
|
||||||
|
func TypeRootAndExts(typ Type) (string, []string) {
|
||||||
|
switch typ {
|
||||||
|
case TypeModel:
|
||||||
|
return "models", []string{".mod.yao", ".mod.jsonc", ".mod.json"}
|
||||||
|
case TypeConnector:
|
||||||
|
return "connectors", []string{".conn.yao", ".conn.jsonc", ".conn.json"}
|
||||||
|
case TypeMCPClient, TypeMCPServer:
|
||||||
|
return "mcps", []string{".mcp.yao", ".mcp.jsonc", ".mcp.json"}
|
||||||
|
case TypeAPI:
|
||||||
|
return "apis", []string{".http.yao", ".http.jsonc", ".http.json"}
|
||||||
|
case TypeSchedule:
|
||||||
|
return "schedules", []string{".sch.yao", ".sch.jsonc", ".sch.json"}
|
||||||
|
case TypeTable:
|
||||||
|
return "tables", []string{".table.yao", ".table.jsonc", ".table.json"}
|
||||||
|
case TypeForm:
|
||||||
|
return "forms", []string{".form.yao", ".form.jsonc", ".form.json"}
|
||||||
|
case TypeList:
|
||||||
|
return "lists", []string{".list.yao", ".list.jsonc", ".list.json"}
|
||||||
|
case TypeChart:
|
||||||
|
return "charts", []string{".chart.yao", ".chart.jsonc", ".chart.json"}
|
||||||
|
case TypeDashboard:
|
||||||
|
return "dashboards", []string{".dash.yao", ".dash.jsonc", ".dash.json"}
|
||||||
|
case TypeFlow:
|
||||||
|
return "flows", []string{".flow.yao", ".flow.jsonc", ".flow.json"}
|
||||||
|
case TypePipe:
|
||||||
|
return "pipes", []string{".pipe.yao", ".pipe.jsonc", ".pipe.json"}
|
||||||
|
case TypeAIGC:
|
||||||
|
return "aigcs", []string{".ai.yao", ".ai.jsonc", ".ai.json"}
|
||||||
|
case TypeStore:
|
||||||
|
return "stores", []string{".lru.yao", ".redis.yao", ".mongo.yao", ".badger.yao", ".store.yao", ".store.jsonc", ".store.json"}
|
||||||
|
default:
|
||||||
|
return "", []string{}
|
||||||
|
}
|
||||||
|
}
|
||||||
601
dsl/types/utils_test.go
Normal file
601
dsl/types/utils_test.go
Normal file
|
|
@ -0,0 +1,601 @@
|
||||||
|
package types
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestToPath(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
typ Type
|
||||||
|
id string
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Model with dots and underscores",
|
||||||
|
typ: TypeModel,
|
||||||
|
id: "user__profile.admin",
|
||||||
|
want: filepath.Join("models", "user.profile", "admin.mod.yao"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "API with simple id",
|
||||||
|
typ: TypeAPI,
|
||||||
|
id: "user.login",
|
||||||
|
want: filepath.Join("apis", "user", "login.http.yao"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Unknown type (defaults to .yao)",
|
||||||
|
typ: TypeUnknown,
|
||||||
|
id: "test",
|
||||||
|
want: filepath.Join("", "test.yao"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Connector with nested path",
|
||||||
|
typ: TypeConnector,
|
||||||
|
id: "database.mysql__config",
|
||||||
|
want: filepath.Join("connectors", "database", "mysql.config.conn.yao"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Type with no extensions",
|
||||||
|
typ: Type("unknown"),
|
||||||
|
id: "test",
|
||||||
|
want: filepath.Join("", "test.yao"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Type with empty extensions",
|
||||||
|
typ: Type(""),
|
||||||
|
id: "test",
|
||||||
|
want: filepath.Join("", "test.yao"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if got := ToPath(tt.typ, tt.id); got != tt.want {
|
||||||
|
t.Errorf("ToPath() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestToID(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
path string
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Model file path",
|
||||||
|
path: filepath.Join("models", "user.mod.yao"),
|
||||||
|
want: "user",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "API file path",
|
||||||
|
path: filepath.Join("apis", "user", "login.http.yao"),
|
||||||
|
want: "user.login",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Form file path with dots",
|
||||||
|
path: filepath.Join("forms", "user.profile", "edit.form.yao"),
|
||||||
|
want: "user__profile.edit",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if got := ToID(tt.path); got != tt.want {
|
||||||
|
t.Errorf("ToID() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWithTypeToID(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
typ Type
|
||||||
|
path string
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Path with leading separator",
|
||||||
|
typ: TypeModel,
|
||||||
|
path: string(os.PathSeparator) + filepath.Join("models", "user.mod.yao"),
|
||||||
|
want: "user",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Path without leading separator",
|
||||||
|
typ: TypeModel,
|
||||||
|
path: filepath.Join("models", "user.mod.yao"),
|
||||||
|
want: "user",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Path with root not matching",
|
||||||
|
typ: TypeModel,
|
||||||
|
path: filepath.Join("other", "user.mod.yao"),
|
||||||
|
want: "other.user__mod__yao",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Nested path with dots",
|
||||||
|
typ: TypeForm,
|
||||||
|
path: filepath.Join("forms", "user.profile", "edit.form.yao"),
|
||||||
|
want: "user__profile.edit",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Multiple extensions matching",
|
||||||
|
typ: TypeModel,
|
||||||
|
path: filepath.Join("models", "user.mod.jsonc"),
|
||||||
|
want: "user",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "No extension matching",
|
||||||
|
typ: TypeModel,
|
||||||
|
path: filepath.Join("models", "user.txt"),
|
||||||
|
want: "user__txt",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Path with single part",
|
||||||
|
typ: TypeModel,
|
||||||
|
path: "user.mod.yao",
|
||||||
|
want: "user__mod__yao",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Store type with multiple extensions",
|
||||||
|
typ: TypeStore,
|
||||||
|
path: filepath.Join("stores", "cache.redis.yao"),
|
||||||
|
want: "cache",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Empty path",
|
||||||
|
typ: TypeModel,
|
||||||
|
path: "",
|
||||||
|
want: "",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Path with root matching but no parts",
|
||||||
|
typ: TypeModel,
|
||||||
|
path: "models",
|
||||||
|
want: "",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if got := WithTypeToID(tt.typ, tt.path); got != tt.want {
|
||||||
|
t.Errorf("WithTypeToID() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDetectType(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
path string
|
||||||
|
want Type
|
||||||
|
}{
|
||||||
|
// Test by extension
|
||||||
|
{
|
||||||
|
name: "HTTP API",
|
||||||
|
path: filepath.Join("apis", "user.http.yao"),
|
||||||
|
want: TypeAPI,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Schedule",
|
||||||
|
path: filepath.Join("schedules", "backup.sch.yao"),
|
||||||
|
want: TypeSchedule,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Table",
|
||||||
|
path: filepath.Join("tables", "user.table.yao"),
|
||||||
|
want: TypeTable,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Form",
|
||||||
|
path: filepath.Join("forms", "user.form.yao"),
|
||||||
|
want: TypeForm,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "List",
|
||||||
|
path: filepath.Join("lists", "user.list.yao"),
|
||||||
|
want: TypeList,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Chart",
|
||||||
|
path: filepath.Join("charts", "sales.chart.yao"),
|
||||||
|
want: TypeChart,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Dashboard",
|
||||||
|
path: filepath.Join("dashboards", "main.dash.yao"),
|
||||||
|
want: TypeDashboard,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Flow",
|
||||||
|
path: filepath.Join("flows", "process.flow.yao"),
|
||||||
|
want: TypeFlow,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Pipe",
|
||||||
|
path: filepath.Join("pipes", "transform.pipe.yao"),
|
||||||
|
want: TypePipe,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "AIGC",
|
||||||
|
path: filepath.Join("aigcs", "chat.ai.yao"),
|
||||||
|
want: TypeAIGC,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Model by extension",
|
||||||
|
path: filepath.Join("models", "user.mod.yao"),
|
||||||
|
want: TypeModel,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Connector by extension",
|
||||||
|
path: filepath.Join("connectors", "db.conn.yao"),
|
||||||
|
want: TypeConnector,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Store LRU",
|
||||||
|
path: filepath.Join("stores", "cache.lru.yao"),
|
||||||
|
want: TypeStore,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "LRU extension in non-stores directory",
|
||||||
|
path: filepath.Join("other", "cache.lru.yao"),
|
||||||
|
want: TypeStore,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Store Redis",
|
||||||
|
path: filepath.Join("stores", "cache.redis.yao"),
|
||||||
|
want: TypeStore,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Store Mongo",
|
||||||
|
path: filepath.Join("stores", "cache.mongo.yao"),
|
||||||
|
want: TypeStore,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Store Badger",
|
||||||
|
path: filepath.Join("stores", "cache.badger.yao"),
|
||||||
|
want: TypeStore,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Store by extension",
|
||||||
|
path: filepath.Join("stores", "cache.store.yao"),
|
||||||
|
want: TypeStore,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "MCP extension in non-apis directory",
|
||||||
|
path: filepath.Join("other", "service.mcp.yao"),
|
||||||
|
want: TypeUnknown,
|
||||||
|
},
|
||||||
|
// Test by root path
|
||||||
|
{
|
||||||
|
name: "Model by root",
|
||||||
|
path: filepath.Join("models", "user.yao"),
|
||||||
|
want: TypeModel,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Connector by root",
|
||||||
|
path: filepath.Join("connectors", "db.yao"),
|
||||||
|
want: TypeConnector,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "MCP Client",
|
||||||
|
path: filepath.Join("mcps", "client.yao"),
|
||||||
|
want: TypeMCPClient,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "API by root with http ext",
|
||||||
|
path: filepath.Join("apis", "user.http.yao"),
|
||||||
|
want: TypeAPI,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "MCP Server",
|
||||||
|
path: filepath.Join("apis", "server.mcp.yao"),
|
||||||
|
want: TypeMCPServer,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "MCP by extension",
|
||||||
|
path: filepath.Join("mcps", "client.mcp.yao"),
|
||||||
|
want: TypeMCPClient,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "API by root unknown ext",
|
||||||
|
path: filepath.Join("apis", "user.unknown.yao"),
|
||||||
|
want: TypeUnknown,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Schedule by root",
|
||||||
|
path: filepath.Join("schedules", "backup.yao"),
|
||||||
|
want: TypeSchedule,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Table by root",
|
||||||
|
path: filepath.Join("tables", "user.yao"),
|
||||||
|
want: TypeTable,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Form by root",
|
||||||
|
path: filepath.Join("forms", "user.yao"),
|
||||||
|
want: TypeForm,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "List by root",
|
||||||
|
path: filepath.Join("lists", "user.yao"),
|
||||||
|
want: TypeList,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Chart by root",
|
||||||
|
path: filepath.Join("charts", "sales.yao"),
|
||||||
|
want: TypeChart,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Dashboard by root",
|
||||||
|
path: filepath.Join("dashboards", "main.yao"),
|
||||||
|
want: TypeDashboard,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Flow by root",
|
||||||
|
path: filepath.Join("flows", "process.yao"),
|
||||||
|
want: TypeFlow,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Pipe by root",
|
||||||
|
path: filepath.Join("pipes", "transform.yao"),
|
||||||
|
want: TypePipe,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "AIGC by root",
|
||||||
|
path: filepath.Join("aigcs", "chat.yao"),
|
||||||
|
want: TypeAIGC,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Store by root",
|
||||||
|
path: filepath.Join("stores", "cache.yao"),
|
||||||
|
want: TypeStore,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Unknown root",
|
||||||
|
path: filepath.Join("unknown", "file.yao"),
|
||||||
|
want: TypeUnknown,
|
||||||
|
},
|
||||||
|
// Edge cases
|
||||||
|
{
|
||||||
|
name: "Path with less than 2 parts",
|
||||||
|
path: "file.yao",
|
||||||
|
want: TypeUnknown,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "File without extension",
|
||||||
|
path: filepath.Join("models", "user"),
|
||||||
|
want: TypeUnknown,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "File with single dot",
|
||||||
|
path: filepath.Join("models", "user.yao"),
|
||||||
|
want: TypeModel,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Empty path",
|
||||||
|
path: "",
|
||||||
|
want: TypeUnknown,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Path with single component",
|
||||||
|
path: "file",
|
||||||
|
want: TypeUnknown,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "File with extension parts length < 2",
|
||||||
|
path: filepath.Join("models", "user"),
|
||||||
|
want: TypeUnknown,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "File with extension matching filename",
|
||||||
|
path: filepath.Join("models", "http.yao"),
|
||||||
|
want: TypeAPI,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "File with extension matching filename - sch",
|
||||||
|
path: filepath.Join("schedules", "sch.yao"),
|
||||||
|
want: TypeSchedule,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if got := DetectType(tt.path); got != tt.want {
|
||||||
|
t.Errorf("DetectType() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTypeRootAndExts(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
typ Type
|
||||||
|
wantRoot string
|
||||||
|
wantExts []string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Model",
|
||||||
|
typ: TypeModel,
|
||||||
|
wantRoot: "models",
|
||||||
|
wantExts: []string{".mod.yao", ".mod.jsonc", ".mod.json"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Connector",
|
||||||
|
typ: TypeConnector,
|
||||||
|
wantRoot: "connectors",
|
||||||
|
wantExts: []string{".conn.yao", ".conn.jsonc", ".conn.json"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "MCP Client",
|
||||||
|
typ: TypeMCPClient,
|
||||||
|
wantRoot: "mcps",
|
||||||
|
wantExts: []string{".mcp.yao", ".mcp.jsonc", ".mcp.json"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "MCP Server",
|
||||||
|
typ: TypeMCPServer,
|
||||||
|
wantRoot: "mcps",
|
||||||
|
wantExts: []string{".mcp.yao", ".mcp.jsonc", ".mcp.json"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "API",
|
||||||
|
typ: TypeAPI,
|
||||||
|
wantRoot: "apis",
|
||||||
|
wantExts: []string{".http.yao", ".http.jsonc", ".http.json"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Schedule",
|
||||||
|
typ: TypeSchedule,
|
||||||
|
wantRoot: "schedules",
|
||||||
|
wantExts: []string{".sch.yao", ".sch.jsonc", ".sch.json"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Table",
|
||||||
|
typ: TypeTable,
|
||||||
|
wantRoot: "tables",
|
||||||
|
wantExts: []string{".table.yao", ".table.jsonc", ".table.json"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Form",
|
||||||
|
typ: TypeForm,
|
||||||
|
wantRoot: "forms",
|
||||||
|
wantExts: []string{".form.yao", ".form.jsonc", ".form.json"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "List",
|
||||||
|
typ: TypeList,
|
||||||
|
wantRoot: "lists",
|
||||||
|
wantExts: []string{".list.yao", ".list.jsonc", ".list.json"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Chart",
|
||||||
|
typ: TypeChart,
|
||||||
|
wantRoot: "charts",
|
||||||
|
wantExts: []string{".chart.yao", ".chart.jsonc", ".chart.json"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Dashboard",
|
||||||
|
typ: TypeDashboard,
|
||||||
|
wantRoot: "dashboards",
|
||||||
|
wantExts: []string{".dash.yao", ".dash.jsonc", ".dash.json"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Flow",
|
||||||
|
typ: TypeFlow,
|
||||||
|
wantRoot: "flows",
|
||||||
|
wantExts: []string{".flow.yao", ".flow.jsonc", ".flow.json"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Pipe",
|
||||||
|
typ: TypePipe,
|
||||||
|
wantRoot: "pipes",
|
||||||
|
wantExts: []string{".pipe.yao", ".pipe.jsonc", ".pipe.json"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "AIGC",
|
||||||
|
typ: TypeAIGC,
|
||||||
|
wantRoot: "aigcs",
|
||||||
|
wantExts: []string{".ai.yao", ".ai.jsonc", ".ai.json"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Store",
|
||||||
|
typ: TypeStore,
|
||||||
|
wantRoot: "stores",
|
||||||
|
wantExts: []string{".lru.yao", ".redis.yao", ".mongo.yao", ".badger.yao", ".store.yao", ".store.jsonc", ".store.json"},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Unknown",
|
||||||
|
typ: TypeUnknown,
|
||||||
|
wantRoot: "",
|
||||||
|
wantExts: []string{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Empty type",
|
||||||
|
typ: Type(""),
|
||||||
|
wantRoot: "",
|
||||||
|
wantExts: []string{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
gotRoot, gotExts := TypeRootAndExts(tt.typ)
|
||||||
|
if gotRoot != tt.wantRoot {
|
||||||
|
t.Errorf("TypeRootAndExts() root = %v, want %v", gotRoot, tt.wantRoot)
|
||||||
|
}
|
||||||
|
if len(gotExts) != len(tt.wantExts) {
|
||||||
|
t.Errorf("TypeRootAndExts() exts length = %v, want %v", len(gotExts), len(tt.wantExts))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
for i, ext := range gotExts {
|
||||||
|
if ext != tt.wantExts[i] {
|
||||||
|
t.Errorf("TypeRootAndExts() exts[%d] = %v, want %v", i, ext, tt.wantExts[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test integration scenarios
|
||||||
|
func TestIntegration(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
typ Type
|
||||||
|
id string
|
||||||
|
wantPath string
|
||||||
|
wantID string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Model round trip",
|
||||||
|
typ: TypeModel,
|
||||||
|
id: "user__profile.admin",
|
||||||
|
wantPath: filepath.Join("models", "user.profile", "admin.mod.yao"),
|
||||||
|
wantID: "user__profile.admin",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "API round trip",
|
||||||
|
typ: TypeAPI,
|
||||||
|
id: "user.login",
|
||||||
|
wantPath: filepath.Join("apis", "user", "login.http.yao"),
|
||||||
|
wantID: "user.login",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Complex nested path",
|
||||||
|
typ: TypeForm,
|
||||||
|
id: "admin__panel.user__management.edit",
|
||||||
|
wantPath: filepath.Join("forms", "admin.panel", "user.management", "edit.form.yao"),
|
||||||
|
wantID: "admin__panel.user__management.edit",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
// Test ID to Path
|
||||||
|
path := ToPath(tt.typ, tt.id)
|
||||||
|
if path != tt.wantPath {
|
||||||
|
t.Errorf("ToPath() = %v, want %v", path, tt.wantPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test Path to ID
|
||||||
|
id := WithTypeToID(tt.typ, path)
|
||||||
|
if id != tt.wantID {
|
||||||
|
t.Errorf("WithTypeToID() = %v, want %v", id, tt.wantID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test DetectType
|
||||||
|
detectedType := DetectType(path)
|
||||||
|
if detectedType != tt.typ {
|
||||||
|
t.Errorf("DetectType() = %v, want %v", detectedType, tt.typ)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -178,47 +178,6 @@
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"relations": {},
|
"relations": {},
|
||||||
"indexes": [
|
"indexes": [],
|
||||||
{
|
|
||||||
"name": "idx_assistant_id",
|
|
||||||
"columns": ["assistant_id"],
|
|
||||||
"type": "unique"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "idx_type",
|
|
||||||
"columns": ["type"],
|
|
||||||
"type": "index"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "idx_description",
|
|
||||||
"columns": ["description"],
|
|
||||||
"type": "index"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "idx_sort",
|
|
||||||
"columns": ["sort"],
|
|
||||||
"type": "index"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "idx_built_in",
|
|
||||||
"columns": ["built_in"],
|
|
||||||
"type": "index"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "idx_readonly",
|
|
||||||
"columns": ["readonly"],
|
|
||||||
"type": "index"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "idx_automated",
|
|
||||||
"columns": ["automated"],
|
|
||||||
"type": "index"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "idx_mentionable",
|
|
||||||
"columns": ["mentionable"],
|
|
||||||
"type": "index"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"option": { "timestamps": true, "soft_deletes": false }
|
"option": { "timestamps": true, "soft_deletes": false }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -141,62 +141,6 @@
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"relations": {},
|
"relations": {},
|
||||||
"indexes": [
|
"indexes": [],
|
||||||
{
|
|
||||||
"name": "idx_file_id",
|
|
||||||
"columns": ["file_id"],
|
|
||||||
"type": "unique"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "idx_uid",
|
|
||||||
"columns": ["uid"],
|
|
||||||
"type": "index"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "idx_guest",
|
|
||||||
"columns": ["guest"],
|
|
||||||
"type": "index"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "idx_manager",
|
|
||||||
"columns": ["manager"],
|
|
||||||
"type": "index"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "idx_content_type",
|
|
||||||
"columns": ["content_type"],
|
|
||||||
"type": "index"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "idx_name",
|
|
||||||
"columns": ["name"],
|
|
||||||
"type": "index"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "idx_public",
|
|
||||||
"columns": ["public"],
|
|
||||||
"type": "index"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "idx_gzip",
|
|
||||||
"columns": ["gzip"],
|
|
||||||
"type": "index"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "idx_bytes",
|
|
||||||
"columns": ["bytes"],
|
|
||||||
"type": "index"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "idx_collection_id",
|
|
||||||
"columns": ["collection_id"],
|
|
||||||
"type": "index"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "idx_status",
|
|
||||||
"columns": ["status"],
|
|
||||||
"type": "index"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"option": { "timestamps": true, "soft_deletes": false }
|
"option": { "timestamps": true, "soft_deletes": false }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -214,76 +214,6 @@
|
||||||
],
|
],
|
||||||
"relations": {},
|
"relations": {},
|
||||||
"indexes": [
|
"indexes": [
|
||||||
{
|
|
||||||
"name": "idx_event_id",
|
|
||||||
"columns": ["event_id"],
|
|
||||||
"type": "index"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "idx_operation",
|
|
||||||
"columns": ["operation"],
|
|
||||||
"type": "index"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "idx_category",
|
|
||||||
"columns": ["category"],
|
|
||||||
"type": "index"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "idx_severity",
|
|
||||||
"columns": ["severity"],
|
|
||||||
"type": "index"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "idx_user_id",
|
|
||||||
"columns": ["user_id"],
|
|
||||||
"type": "index"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "idx_session_id",
|
|
||||||
"columns": ["session_id"],
|
|
||||||
"type": "index"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "idx_client_ip",
|
|
||||||
"columns": ["client_ip"],
|
|
||||||
"type": "index"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "idx_target_resource",
|
|
||||||
"columns": ["target_resource"],
|
|
||||||
"type": "index"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "idx_resource_type",
|
|
||||||
"columns": ["resource_type"],
|
|
||||||
"type": "index"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "idx_source",
|
|
||||||
"columns": ["source"],
|
|
||||||
"type": "index"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "idx_application",
|
|
||||||
"columns": ["application"],
|
|
||||||
"type": "index"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "idx_success",
|
|
||||||
"columns": ["success"],
|
|
||||||
"type": "index"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "idx_request_id",
|
|
||||||
"columns": ["request_id"],
|
|
||||||
"type": "index"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "idx_trace_id",
|
|
||||||
"columns": ["trace_id"],
|
|
||||||
"type": "index"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "idx_user_operation",
|
"name": "idx_user_operation",
|
||||||
"columns": ["user_id", "operation"],
|
"columns": ["user_id", "operation"],
|
||||||
|
|
|
||||||
|
|
@ -59,27 +59,6 @@
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"relations": {},
|
"relations": {},
|
||||||
"indexes": [
|
"indexes": [],
|
||||||
{
|
|
||||||
"name": "idx_chat_id",
|
|
||||||
"columns": ["chat_id"],
|
|
||||||
"type": "unique"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "idx_assistant_id",
|
|
||||||
"columns": ["assistant_id"],
|
|
||||||
"type": "index"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "idx_sid",
|
|
||||||
"columns": ["sid"],
|
|
||||||
"type": "index"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "idx_silent",
|
|
||||||
"columns": ["silent"],
|
|
||||||
"type": "index"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"option": { "timestamps": true, "soft_deletes": false }
|
"option": { "timestamps": true, "soft_deletes": false }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -66,27 +66,6 @@
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"relations": {},
|
"relations": {},
|
||||||
"indexes": [
|
"indexes": [],
|
||||||
{
|
|
||||||
"name": "idx_key",
|
|
||||||
"columns": ["key"],
|
|
||||||
"type": "unique"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "idx_category",
|
|
||||||
"columns": ["category"],
|
|
||||||
"type": "index"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "idx_type",
|
|
||||||
"columns": ["type"],
|
|
||||||
"type": "index"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "idx_readonly",
|
|
||||||
"columns": ["readonly"],
|
|
||||||
"type": "index"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"option": { "timestamps": true, "soft_deletes": false }
|
"option": { "timestamps": true, "soft_deletes": false }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
{
|
{
|
||||||
"name": "mcp",
|
"name": "dsl",
|
||||||
"label": "MCP",
|
"label": "DSL",
|
||||||
"description": "MCP table for storing MCP client configuration",
|
"description": "DSL table for storing Yao DSL configurations",
|
||||||
"table": {
|
"table": {
|
||||||
"name": "mcp",
|
"name": "dsl",
|
||||||
"comment": "MCP table"
|
"comment": "DSL table"
|
||||||
},
|
},
|
||||||
"columns": [
|
"columns": [
|
||||||
{
|
{
|
||||||
|
|
@ -14,43 +14,93 @@
|
||||||
"comment": "Unique identifier"
|
"comment": "Unique identifier"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "mcp_id",
|
"name": "dsl_id",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"label": "MCP ID",
|
"label": "DSL ID",
|
||||||
"comment": "MCP client identifier",
|
"comment": "DSL identifier",
|
||||||
"length": 200,
|
"length": 200,
|
||||||
"nullable": false,
|
"nullable": false,
|
||||||
"unique": true,
|
"unique": true,
|
||||||
"index": true
|
"index": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "name",
|
"name": "type",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"label": "Name",
|
"label": "Type",
|
||||||
"comment": "MCP client name",
|
"comment": "DSL type (model, api, table, form, list, chart, dashboard, connector, store, schedule, flow, pipe, aigc, sui, etc.)",
|
||||||
|
"length": 50,
|
||||||
|
"nullable": false,
|
||||||
|
"index": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "label",
|
||||||
|
"type": "string",
|
||||||
|
"label": "Label",
|
||||||
|
"comment": "DSL display label",
|
||||||
"length": 200,
|
"length": 200,
|
||||||
"nullable": false,
|
"nullable": false,
|
||||||
"index": true
|
"index": true
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "path",
|
||||||
|
"type": "string",
|
||||||
|
"label": "Path",
|
||||||
|
"comment": "DSL file path",
|
||||||
|
"length": 500,
|
||||||
|
"nullable": true,
|
||||||
|
"index": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "sort",
|
||||||
|
"type": "integer",
|
||||||
|
"label": "Sort",
|
||||||
|
"comment": "Sort order for data sorting",
|
||||||
|
"nullable": true,
|
||||||
|
"default": 0,
|
||||||
|
"index": true
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "description",
|
"name": "description",
|
||||||
"type": "text",
|
"type": "text",
|
||||||
"label": "Description",
|
"label": "Description",
|
||||||
"comment": "MCP client description",
|
"comment": "DSL description",
|
||||||
|
"nullable": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "tags",
|
||||||
|
"type": "json",
|
||||||
|
"label": "Tags",
|
||||||
|
"comment": "DSL tags for categorization and filtering",
|
||||||
"nullable": true
|
"nullable": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "dsl",
|
"name": "dsl",
|
||||||
"type": "json",
|
"type": "json",
|
||||||
"label": "DSL",
|
"label": "DSL",
|
||||||
"comment": "MCP client DSL configuration in JSON format",
|
"comment": "DSL configuration in JSON format",
|
||||||
"nullable": true
|
"nullable": true
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "mtime",
|
||||||
|
"type": "timestamp",
|
||||||
|
"label": "Modification Time",
|
||||||
|
"comment": "File modification time (for file-based DSL)",
|
||||||
|
"nullable": true,
|
||||||
|
"index": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "ctime",
|
||||||
|
"type": "timestamp",
|
||||||
|
"label": "Creation Time",
|
||||||
|
"comment": "File creation time (for file-based DSL)",
|
||||||
|
"nullable": true,
|
||||||
|
"index": true
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "readonly",
|
"name": "readonly",
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"label": "Readonly",
|
"label": "Readonly",
|
||||||
"comment": "MCP client readonly status",
|
"comment": "DSL readonly status",
|
||||||
"default": false,
|
"default": false,
|
||||||
"index": true
|
"index": true
|
||||||
},
|
},
|
||||||
|
|
@ -58,7 +108,7 @@
|
||||||
"name": "built_in",
|
"name": "built_in",
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"label": "Built In",
|
"label": "Built In",
|
||||||
"comment": "Whether this is a built-in MCP client",
|
"comment": "Whether this is a built-in DSL",
|
||||||
"default": false,
|
"default": false,
|
||||||
"index": true
|
"index": true
|
||||||
},
|
},
|
||||||
|
|
@ -98,37 +148,6 @@
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"relations": {},
|
"relations": {},
|
||||||
"indexes": [
|
"indexes": [],
|
||||||
{
|
|
||||||
"name": "idx_mcp_id",
|
|
||||||
"columns": ["mcp_id"],
|
|
||||||
"type": "unique"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "idx_name",
|
|
||||||
"columns": ["name"],
|
|
||||||
"type": "index"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "idx_readonly",
|
|
||||||
"columns": ["readonly"],
|
|
||||||
"type": "index"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "idx_built_in",
|
|
||||||
"columns": ["built_in"],
|
|
||||||
"type": "index"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "idx_creator_id",
|
|
||||||
"columns": ["creator_id"],
|
|
||||||
"type": "index"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "idx_updater_id",
|
|
||||||
"columns": ["updater_id"],
|
|
||||||
"type": "index"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"option": { "timestamps": true, "soft_deletes": false }
|
"option": { "timestamps": true, "soft_deletes": false }
|
||||||
}
|
}
|
||||||
|
|
@ -121,42 +121,6 @@
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"relations": {},
|
"relations": {},
|
||||||
"indexes": [
|
"indexes": [],
|
||||||
{
|
|
||||||
"name": "idx_sid",
|
|
||||||
"columns": ["sid"],
|
|
||||||
"type": "index"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "idx_cid",
|
|
||||||
"columns": ["cid"],
|
|
||||||
"type": "index"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "idx_uid",
|
|
||||||
"columns": ["uid"],
|
|
||||||
"type": "index"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "idx_role",
|
|
||||||
"columns": ["role"],
|
|
||||||
"type": "index"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "idx_assistant_id",
|
|
||||||
"columns": ["assistant_id"],
|
|
||||||
"type": "index"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "idx_silent",
|
|
||||||
"columns": ["silent"],
|
|
||||||
"type": "index"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "idx_expired_at",
|
|
||||||
"columns": ["expired_at"],
|
|
||||||
"type": "index"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"option": { "timestamps": true, "soft_deletes": false }
|
"option": { "timestamps": true, "soft_deletes": false }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -106,47 +106,6 @@
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"relations": {},
|
"relations": {},
|
||||||
"indexes": [
|
"indexes": [],
|
||||||
{
|
|
||||||
"name": "idx_collection_id",
|
|
||||||
"columns": ["collection_id"],
|
|
||||||
"type": "unique"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "idx_name",
|
|
||||||
"columns": ["name"],
|
|
||||||
"type": "index"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "idx_description",
|
|
||||||
"columns": ["description"],
|
|
||||||
"type": "index"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "idx_uid",
|
|
||||||
"columns": ["uid"],
|
|
||||||
"type": "index"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "idx_public",
|
|
||||||
"columns": ["public"],
|
|
||||||
"type": "index"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "idx_readonly",
|
|
||||||
"columns": ["readonly"],
|
|
||||||
"type": "index"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "idx_system",
|
|
||||||
"columns": ["system"],
|
|
||||||
"type": "index"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "idx_sort",
|
|
||||||
"columns": ["sort"],
|
|
||||||
"type": "index"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"option": { "timestamps": true, "soft_deletes": false }
|
"option": { "timestamps": true, "soft_deletes": false }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue