Skip to content

Commit

Permalink
chore: refactor proto query (#1563)
Browse files Browse the repository at this point in the history
  • Loading branch information
RutZap authored Aug 23, 2024
1 parent 738f24e commit 10df8b2
Show file tree
Hide file tree
Showing 29 changed files with 399 additions and 205 deletions.
2 changes: 1 addition & 1 deletion events/events.go
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ func SendEvents(ctx context.Context, schema *proto.Schema) error {
return fmt.Errorf("event '%s' does not exist", eventName)
}

subscribers := proto.FindEventSubscriptions(schema, protoEvent)
subscribers := schema.FindEventSubscribers(protoEvent)
if len(subscribers) == 0 {
return fmt.Errorf("event '%s' must have at least one subscriber", eventName)
}
Expand Down
4 changes: 2 additions & 2 deletions migrations/migrations.go
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,7 @@ func New(ctx context.Context, schema *proto.Schema, database db.Database) (*Migr
pushAuditModel(schema)
defer popAuditModel(schema)

modelNames := proto.ModelNames(schema)
modelNames := schema.ModelNames()

// Add any new models
for _, modelName := range modelNames {
Expand Down Expand Up @@ -467,7 +467,7 @@ func GetCurrentSchema(ctx context.Context, database db.Database) (*proto.Schema,
// fkConstraintsForModel generates foreign key constraint statements for each of fields marked as
// being foreign keys in the given model.
func fkConstraintsForModel(model *proto.Model) (fkStatements []string) {
fkFields := proto.ForeignKeyFields(model)
fkFields := model.ForeignKeyFields()
for _, field := range fkFields {
stmt := fkConstraint(field, model)
fkStatements = append(fkStatements, stmt)
Expand Down
4 changes: 2 additions & 2 deletions migrations/sql.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,11 +120,11 @@ func addUniqueConstraintStmt(schema *proto.Schema, modelName string, fieldNames
for _, name := range fieldNames {
field := proto.FindField(schema.Models, modelName, name)

if proto.IsBelongsTo(field) {
if field.IsBelongsTo() {
name = fmt.Sprintf("%sId", name)
}

if proto.IsHasMany(field) || proto.IsHasOne(field) {
if field.IsHasMany() || field.IsHasOne() {
return "", fmt.Errorf("cannot create unique constraint on has-many or has-one model field '%s'", name)
}

Expand Down
6 changes: 3 additions & 3 deletions node/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ func writeClientApiClass(w *codegen.Writer, schema *proto.Schema, api *proto.Api

func writeClientActions(w *codegen.Writer, schema *proto.Schema, api *proto.Api) {
for _, a := range proto.GetActionNamesForApi(schema, api) {
action := proto.FindAction(schema, a)
action := schema.FindAction(a)
msg := proto.FindMessage(schema.Messages, action.InputMessageName)

w.Writef("%s: (i", action.Name)
Expand Down Expand Up @@ -195,7 +195,7 @@ func writeClientApiDefinition(w *codegen.Writer, schema *proto.Schema, api *prot
mutations := []string{}

for _, a := range proto.GetActionNamesForApi(schema, api) {
action := proto.FindAction(schema, a)
action := schema.FindAction(a)
if action.Type == proto.ActionType_ACTION_TYPE_GET || action.Type == proto.ActionType_ACTION_TYPE_LIST || action.Type == proto.ActionType_ACTION_TYPE_READ {
queries = append(queries, action.Name)
} else {
Expand Down Expand Up @@ -241,7 +241,7 @@ func writeClientTypes(w *codegen.Writer, schema *proto.Schema, api *proto.Api) {

// writing embedded response types
for _, a := range proto.GetActionNamesForApi(schema, api) {
action := proto.FindAction(schema, a)
action := schema.FindAction(a)
embeds := action.GetResponseEmbeds()
if len(embeds) == 0 {
continue
Expand Down
24 changes: 12 additions & 12 deletions node/codegen.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ func generateSdkPackage(schema *proto.Schema, cfg *config.ProjectConfig) codegen

// if the action type is read or write, then the signature of the exported method just takes the function
// defined by the user
if proto.ActionIsArbitraryFunction(action) {
if action.IsArbitraryFunction() {
sdk.Writef("module.exports.%s = (fn) => fn;", casing.ToCamel(action.Name))
} else {
// writes the default implementation of a function. the user can specify hooks which can
Expand Down Expand Up @@ -330,14 +330,14 @@ func writeCreateValuesType(w *codegen.Writer, schema *proto.Schema, model *proto
}

w.Write(field.Name)
if field.Optional || field.DefaultValue != nil || proto.IsHasMany(field) {
if field.Optional || field.DefaultValue != nil || field.IsHasMany() {
w.Write("?")
}

w.Write(": ")

if field.Type.Type == proto.Type_TYPE_MODEL {
if proto.IsHasMany(field) {
if field.IsHasMany() {
w.Write("Array<")
}

Expand All @@ -348,7 +348,7 @@ func writeCreateValuesType(w *codegen.Writer, schema *proto.Schema, model *proto
// field is "books" then we don't want the create values type for each book
// to expect you to provide "author" or "authorId" - as that field will be filled
// in when the author record is created
if proto.IsHasMany(field) {
if field.IsHasMany() {
inverseField := proto.FindField(schema.Models, relation.Name, field.InverseFieldName.Value)
w.Writef("Omit<%sCreateValues, '%s' | '%s'>", relation.Name, inverseField.Name, inverseField.ForeignKeyFieldName.Value)
} else {
Expand All @@ -357,9 +357,9 @@ func writeCreateValuesType(w *codegen.Writer, schema *proto.Schema, model *proto

// ...or just an id. This API might not be ideal because by allowing just
// "id" we make the types less strict.
w.Writef(" | {%s: string}", proto.PrimaryKeyFieldName(relation))
w.Writef(" | {%s: string}", relation.PrimaryKeyFieldName())

if proto.IsHasMany(field) {
if field.IsHasMany() {
w.Write(">")
}
} else {
Expand Down Expand Up @@ -394,7 +394,7 @@ func writeCreateValuesType(w *codegen.Writer, schema *proto.Schema, model *proto
fkName := field.ForeignKeyFieldName.Value

relation := proto.FindModel(schema.Models, field.Type.ModelName.Value)
relationPk := proto.PrimaryKeyFieldName(relation)
relationPk := relation.PrimaryKeyFieldName()

w.Writef("// Either %s or %s can be provided but not both\n", field.Name, fkName)
w.Writef("| {%s: %sCreateValues | {%s: string}, %s?: undefined}\n", field.Name, field.Type.ModelName.Value, relationPk, fkName)
Expand Down Expand Up @@ -585,7 +585,7 @@ func writeUniqueConditionsInterface(w *codegen.Writer, model *proto.Model) {
})
}
}
case proto.IsHasMany(f):
case f.IsHasMany():
// If a field is has-many then the other side is has-one, meaning
// you can use that fields unique conditions to look up _this_ model.
// Example: an author has many books, but a book has one author, which
Expand Down Expand Up @@ -1042,11 +1042,11 @@ func writeTableConfig(w *codegen.Writer, models []*proto.Model) {
}

switch {
case proto.IsHasOne(field):
case field.IsHasOne():
relationshipConfig["relationshipType"] = "hasOne"
case proto.IsHasMany(field):
case field.IsHasMany():
relationshipConfig["relationshipType"] = "hasMany"
case proto.IsBelongsTo(field):
case field.IsBelongsTo():
relationshipConfig["relationshipType"] = "belongsTo"
}

Expand Down Expand Up @@ -1166,7 +1166,7 @@ func writeFunctionWrapperType(w *codegen.Writer, schema *proto.Schema, model *pr
// decorating existing js code with types.
w.Writef("export declare function %s", casing.ToCamel(action.Name))

if proto.ActionIsArbitraryFunction(action) {
if action.IsArbitraryFunction() {
inputType := action.InputMessageName
if inputType == parser.MessageFieldTypeAny {
inputType = "any"
Expand Down
4 changes: 2 additions & 2 deletions node/scaffold.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ func Scaffold(dir string, schema *proto.Schema, cfg *config.ProjectConfig) (code

generatedFiles := codegen.GeneratedFiles{}

functions := proto.FilterActions(schema, func(op *proto.Action) bool {
functions := schema.FilterActions(func(op *proto.Action) bool {
return op.Implementation == proto.ActionImplementation_ACTION_IMPLEMENTATION_CUSTOM
})

Expand Down Expand Up @@ -135,7 +135,7 @@ func ensureDir(dirName string) error {
func writeFunctionWrapper(function *proto.Action) string {
functionName := casing.ToCamel(function.Name)

if proto.ActionIsArbitraryFunction(function) {
if function.IsArbitraryFunction() {
return fmt.Sprintf(`import { %s } from '@teamkeel/sdk';
// To learn more about what you can do with custom functions, visit https://docs.keel.so/functions
Expand Down
8 changes: 4 additions & 4 deletions permissions/permissions.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ type Value struct {
// what values should be provided to the query at runtime.
func ToSQL(s *proto.Schema, m *proto.Model, action *proto.Action) (sql string, values []*Value, err error) {
tableName := identifier(m.Name)
pkField := identifier(proto.PrimaryKeyFieldName(m))
pkField := identifier(m.PrimaryKeyFieldName())

stmt := &statement{}
permissions := proto.PermissionsForAction(s, action)
Expand Down Expand Up @@ -298,11 +298,11 @@ func handleModel(s *proto.Schema, model *proto.Model, ident *parser.Ident, stmt
}

leftFieldName := proto.GetForeignKeyFieldName(s.Models, field)
rightFieldName := proto.PrimaryKeyFieldName(joinModel)
rightFieldName := joinModel.PrimaryKeyFieldName()

// If not belongs to then swap foreign/primary key
if !proto.IsBelongsTo(field) {
leftFieldName = proto.PrimaryKeyFieldName(model)
if !field.IsBelongsTo() {
leftFieldName = model.PrimaryKeyFieldName()
rightFieldName = proto.GetForeignKeyFieldName(s.Models, field)
}

Expand Down
27 changes: 27 additions & 0 deletions proto/action.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package proto

func (a *Action) IsFunction() bool {
return a.Implementation == ActionImplementation_ACTION_IMPLEMENTATION_CUSTOM
}

func (a *Action) IsArbitraryFunction() bool {
return a.IsFunction() && (a.Type == ActionType_ACTION_TYPE_READ || a.Type == ActionType_ACTION_TYPE_WRITE)
}

func (a *Action) IsWriteAction() bool {
switch a.Type {
case ActionType_ACTION_TYPE_CREATE, ActionType_ACTION_TYPE_DELETE, ActionType_ACTION_TYPE_WRITE, ActionType_ACTION_TYPE_UPDATE:
return true
default:
return false
}
}

func (a *Action) IsReadAction() bool {
switch a.Type {
case ActionType_ACTION_TYPE_GET, ActionType_ACTION_TYPE_LIST, ActionType_ACTION_TYPE_READ:
return true
default:
return false
}
}
33 changes: 33 additions & 0 deletions proto/field.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package proto

// IsFile tells us if the field is a file
func (f *Field) IsFile() bool {
if f.Type == nil {
return false
}

return f.Type.Type == Type_TYPE_INLINE_FILE
}

// IsTypeModel returns true of the field's type is Model.
func (f *Field) IsTypeModel() bool {
return f.Type.Type == Type_TYPE_MODEL
}

// IsTypeRepeated returns true if the field is specified as
// being "repeated".
func (f *Field) IsRepeated() bool {
return f.Type.Repeated
}

func (f *Field) IsHasMany() bool {
return f.Type.Type == Type_TYPE_MODEL && f.ForeignKeyFieldName == nil && f.Type.Repeated
}

func (f *Field) IsHasOne() bool {
return f.Type.Type == Type_TYPE_MODEL && f.ForeignKeyFieldName == nil && !f.Type.Repeated
}

func (f *Field) IsBelongsTo() bool {
return f.Type.Type == Type_TYPE_MODEL && f.ForeignKeyFieldName != nil && !f.Type.Repeated
}
42 changes: 42 additions & 0 deletions proto/message.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package proto

import "github.com/samber/lo"

// HasFiles checks if the message has any Inline file fields
func (m *Message) HasFiles() bool {
return len(m.FileFields()) > 0
}

// FileFields will return a slice of fields for the model that are of type file
func (m *Message) FileFields() []*MessageField {
return lo.Filter(m.Fields, func(f *MessageField, _ int) bool {
return f.IsFile()
})
}

// IsModelField returns true if the input targets a model field
// and is handled automatically by the runtime.
// This will only be true for inputs that are built-in actions,
// as functions never have this behaviour.
func (f *MessageField) IsModelField() bool {
return len(f.Target) > 0
}

// IsFile tells us if the field is a file
func (f *MessageField) IsFile() bool {
if f.Type == nil {
return false
}

return f.Type.Type == Type_TYPE_INLINE_FILE
}

func (m *Message) FindField(fieldName string) *MessageField {
for _, field := range m.Fields {
if field.Name == fieldName {
return field
}
}

return nil
}
48 changes: 48 additions & 0 deletions proto/model.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package proto

import (
"sort"

"github.com/samber/lo"
)

// FileFields will return a slice of fields for the model that are of type file
func (m *Model) FileFields() []*Field {
return lo.Filter(m.Fields, func(f *Field, _ int) bool {
return f.IsFile()
})
}

// HasFiles checks if the model has any fields that are files
func (m *Model) HasFiles() bool {
return len(m.FileFields()) > 0
}

// FieldNames provides a (sorted) list of the fields in the model of the given name.
func (m *Model) FieldNames() []string {
names := lo.Map(m.Fields, func(x *Field, _ int) string {
return x.Name
})
sort.Strings(names)
return names
}

// ForeignKeyFields returns all the fields in the given model which have their ForeignKeyInfo
// populated.
func (m *Model) ForeignKeyFields() []*Field {
return lo.Filter(m.Fields, func(f *Field, _ int) bool {
return f.ForeignKeyInfo != nil
})
}

// PrimaryKeyFieldName returns the name of the field in the given model,
// that is marked as being the model's primary key. (Or empty string).
func (m *Model) PrimaryKeyFieldName() string {
field, _ := lo.Find(m.Fields, func(f *Field) bool {
return f.PrimaryKey
})
if field != nil {
return field.Name
}
return ""
}
12 changes: 12 additions & 0 deletions proto/model_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package proto

import (
"testing"

"github.com/stretchr/testify/require"
)

func TestFieldNames(t *testing.T) {
t.Parallel()
require.Equal(t, []string{"Field1", "Field2"}, referenceSchema.Models[0].FieldNames())
}
Loading

0 comments on commit 10df8b2

Please sign in to comment.