diff --git a/plugins/jsvm/binds.go b/plugins/jsvm/binds.go index 5abf6163..e4352961 100644 --- a/plugins/jsvm/binds.go +++ b/plugins/jsvm/binds.go @@ -188,6 +188,10 @@ func routerBinds(app core.App, loader *goja.Runtime, executors *vmsPool) { } func wrapHandler(executors *vmsPool, handler goja.Value) (echo.HandlerFunc, error) { + if handler == nil { + return nil, errors.New("handler must be non-nil") + } + switch h := handler.Export().(type) { case echo.HandlerFunc: // "native" handler - no need to wrap @@ -222,6 +226,10 @@ func wrapMiddlewares(executors *vmsPool, rawMiddlewares ...goja.Value) ([]echo.M wrappedMiddlewares := make([]echo.MiddlewareFunc, len(rawMiddlewares)) for i, m := range rawMiddlewares { + if m == nil { + return nil, errors.New("middleware func must be non-nil") + } + switch v := m.Export().(type) { case echo.MiddlewareFunc: // "native" middleware - no need to wrap @@ -271,7 +279,7 @@ func baseBinds(vm *goja.Runtime) { vm.Set("DynamicModel", func(call goja.ConstructorCall) *goja.Object { shape, ok := call.Argument(0).Export().(map[string]any) if !ok || len(shape) == 0 { - panic("missing shape data") + panic("[DynamicModel] missing shape data") } instance := newDynamicModel(shape) @@ -364,7 +372,7 @@ func baseBinds(vm *goja.Runtime) { vm.Set("Dao", func(call goja.ConstructorCall) *goja.Object { concurrentDB, _ := call.Argument(0).Export().(dbx.Builder) if concurrentDB == nil { - panic("missing required Dao(concurrentDB, [nonconcurrentDB]) argument") + panic("[Dao] missing required Dao(concurrentDB, [nonconcurrentDB]) argument") } nonConcurrentDB, _ := call.Argument(1).Export().(dbx.Builder) diff --git a/plugins/jsvm/jsvm.go b/plugins/jsvm/jsvm.go index 2b4d7f1e..94ab495e 100644 --- a/plugins/jsvm/jsvm.go +++ b/plugins/jsvm/jsvm.go @@ -34,9 +34,6 @@ import ( ) const ( - hooksExtension = ".pb.js" - migrationsExtension = ".js" - typesFileName = "types.d.ts" ) @@ -53,6 +50,13 @@ type Config struct { // If not set it fallbacks to a relative "pb_data/../pb_hooks" directory. HooksDir string + // HooksFilesPattern specifies a regular expression pattern that + // identify which file to load by the hook vm(s). + // + // If not set it fallbacks to `^.*(\.pb\.js|\.pb\.ts)$`, aka. any + // HookdsDir file ending in ".pb.js" or ".pb.ts" (the last one is to enforce IDE linters). + HooksFilesPattern string + // HooksPoolSize specifies how many goja.Runtime instances to prewarm // and keep for the JS app hooks gorotines execution. // @@ -65,6 +69,10 @@ type Config struct { // If not set it fallbacks to a relative "pb_data/../pb_migrations" directory. MigrationsDir string + // If not set it fallbacks to `^.*(\.js|\.ts)$`, aka. any MigrationDir file + // ending in ".js" or ".ts" (the last one is to enforce IDE linters). + MigrationsFilesPattern string + // TypesDir specifies the directory where to store the embedded // TypeScript declarations file. // @@ -96,6 +104,14 @@ func Register(app core.App, config Config) error { p.config.MigrationsDir = filepath.Join(app.DataDir(), "../pb_migrations") } + if p.config.HooksFilesPattern == "" { + p.config.HooksFilesPattern = `^.*(\.pb\.js|\.pb\.ts)$` + } + + if p.config.MigrationsFilesPattern == "" { + p.config.MigrationsFilesPattern = `^.*(\.js|\.ts)$` + } + if p.config.TypesDir == "" { p.config.TypesDir = app.DataDir() } @@ -129,7 +145,7 @@ type plugin struct { // registerMigrations registers the JS migrations loader. func (p *plugin) registerMigrations() error { // fetch all js migrations sorted by their filename - files, err := filesContent(p.config.MigrationsDir, `^.*`+regexp.QuoteMeta(migrationsExtension)+`$`) + files, err := filesContent(p.config.MigrationsDir, p.config.MigrationsFilesPattern) if err != nil { return err } @@ -168,7 +184,7 @@ func (p *plugin) registerMigrations() error { // registerHooks registers the JS app hooks loader. func (p *plugin) registerHooks() error { // fetch all js hooks sorted by their filename - files, err := filesContent(p.config.HooksDir, `^.*`+regexp.QuoteMeta(hooksExtension)+`$`) + files, err := filesContent(p.config.HooksDir, p.config.HooksFilesPattern) if err != nil { return err } @@ -250,14 +266,24 @@ func (p *plugin) registerHooks() error { routerBinds(p.app, loader, executors) for file, content := range files { - _, err := loader.RunString(string(content)) - if err != nil { - if p.config.HooksWatch { - color.Red("Failed to execute %s: %v", file, err) - } else { + func() { + defer func() { + if err := recover(); err != nil { + fmtErr := fmt.Errorf("Failed to execute %s:\n - %v", file, err) + + if p.config.HooksWatch { + color.Red("%v", fmtErr) + } else { + panic(fmtErr) + } + } + }() + + _, err := loader.RunString(string(content)) + if err != nil { panic(err) } - } + }() } return nil