(no tests) updated jsvm bindings
This commit is contained in:
parent
5e37c90dde
commit
13d96e793b
|
@ -2,26 +2,212 @@ package jsvm
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/dop251/goja"
|
||||
validation "github.com/go-ozzo/ozzo-validation/v4"
|
||||
"github.com/labstack/echo/v5"
|
||||
"github.com/pocketbase/dbx"
|
||||
"github.com/pocketbase/pocketbase/apis"
|
||||
"github.com/pocketbase/pocketbase/core"
|
||||
"github.com/pocketbase/pocketbase/daos"
|
||||
"github.com/pocketbase/pocketbase/forms"
|
||||
"github.com/pocketbase/pocketbase/models"
|
||||
"github.com/pocketbase/pocketbase/models/schema"
|
||||
"github.com/pocketbase/pocketbase/tokens"
|
||||
"github.com/pocketbase/pocketbase/tools/cron"
|
||||
"github.com/pocketbase/pocketbase/tools/filesystem"
|
||||
"github.com/pocketbase/pocketbase/tools/hook"
|
||||
"github.com/pocketbase/pocketbase/tools/inflector"
|
||||
"github.com/pocketbase/pocketbase/tools/list"
|
||||
"github.com/pocketbase/pocketbase/tools/mailer"
|
||||
"github.com/pocketbase/pocketbase/tools/security"
|
||||
"github.com/pocketbase/pocketbase/tools/types"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
// hooksBinds adds wrapped "on*" hook methods by reflecting on core.App.
|
||||
func hooksBinds(app core.App, loader *goja.Runtime, executors *vmsPool) {
|
||||
fm := FieldMapper{}
|
||||
|
||||
appType := reflect.TypeOf(app)
|
||||
appValue := reflect.ValueOf(app)
|
||||
totalMethods := appType.NumMethod()
|
||||
excludeHooks := []string{"OnBeforeServe"}
|
||||
|
||||
for i := 0; i < totalMethods; i++ {
|
||||
method := appType.Method(i)
|
||||
if !strings.HasPrefix(method.Name, "On") || list.ExistInSlice(method.Name, excludeHooks) {
|
||||
continue // not a hook or excluded
|
||||
}
|
||||
|
||||
jsName := fm.MethodName(appType, method)
|
||||
|
||||
// register the hook to the loader
|
||||
loader.Set(jsName, func(callback string, tags ...string) {
|
||||
pr := goja.MustCompile("", "{("+callback+").apply(undefined, __args)}", true)
|
||||
|
||||
tagsAsValues := make([]reflect.Value, len(tags))
|
||||
for i, tag := range tags {
|
||||
tagsAsValues[i] = reflect.ValueOf(tag)
|
||||
}
|
||||
|
||||
hookInstance := appValue.MethodByName(method.Name).Call(tagsAsValues)[0]
|
||||
addFunc := hookInstance.MethodByName("Add")
|
||||
|
||||
handlerType := addFunc.Type().In(0)
|
||||
|
||||
handler := reflect.MakeFunc(handlerType, func(args []reflect.Value) (results []reflect.Value) {
|
||||
handlerArgs := make([]any, len(args))
|
||||
for i, arg := range args {
|
||||
handlerArgs[i] = arg.Interface()
|
||||
}
|
||||
|
||||
err := executors.run(func(executor *goja.Runtime) error {
|
||||
executor.Set("__args", handlerArgs)
|
||||
res, err := executor.RunProgram(pr)
|
||||
executor.Set("__args", goja.Undefined())
|
||||
|
||||
// check for returned hook.StopPropagation
|
||||
if res != nil {
|
||||
if v, ok := res.Export().(error); ok {
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
// check for throwed hook.StopPropagation
|
||||
if err != nil {
|
||||
exception, ok := err.(*goja.Exception)
|
||||
if ok && errors.Is(exception.Value().Export().(error), hook.StopPropagation) {
|
||||
return hook.StopPropagation
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
})
|
||||
|
||||
return []reflect.Value{reflect.ValueOf(&err).Elem()}
|
||||
})
|
||||
|
||||
// register the wrapped hook handler
|
||||
addFunc.Call([]reflect.Value{handler})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func cronBinds(app core.App, loader *goja.Runtime, executors *vmsPool) {
|
||||
jobs := cron.New()
|
||||
|
||||
loader.Set("cronAdd", func(jobId, cronExpr, handler string) {
|
||||
pr := goja.MustCompile("", "{("+handler+").apply(undefined)}", true)
|
||||
|
||||
err := jobs.Add(jobId, cronExpr, func() {
|
||||
executors.run(func(executor *goja.Runtime) error {
|
||||
_, err := executor.RunProgram(pr)
|
||||
return err
|
||||
})
|
||||
})
|
||||
if err != nil {
|
||||
panic("[cronAdd] failed to register cron job " + jobId + ": " + err.Error())
|
||||
}
|
||||
|
||||
// start the ticker (if not already)
|
||||
if jobs.Total() > 0 && !jobs.HasStarted() {
|
||||
jobs.Start()
|
||||
}
|
||||
})
|
||||
|
||||
loader.Set("cronRemove", func(jobId string) {
|
||||
jobs.Remove(jobId)
|
||||
|
||||
// stop the ticker if there are no other jobs
|
||||
if jobs.Total() == 0 {
|
||||
jobs.Stop()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func routerBinds(app core.App, loader *goja.Runtime, executors *vmsPool) {
|
||||
loader.Set("routerAdd", func(method string, path string, handler string, middlewares ...goja.Value) {
|
||||
wrappedMiddlewares, err := wrapMiddlewares(executors, middlewares...)
|
||||
if err != nil {
|
||||
panic("[routerAdd] failed to wrap middlewares: " + err.Error())
|
||||
}
|
||||
|
||||
pr := goja.MustCompile("", "{("+handler+").apply(undefined, __args)}", true)
|
||||
|
||||
app.OnBeforeServe().Add(func(e *core.ServeEvent) error {
|
||||
e.Router.Add(strings.ToUpper(method), path, func(c echo.Context) error {
|
||||
return executors.run(func(executor *goja.Runtime) error {
|
||||
executor.Set("__args", []any{c})
|
||||
_, err := executor.RunProgram(pr)
|
||||
executor.Set("__args", goja.Undefined())
|
||||
return err
|
||||
})
|
||||
}, wrappedMiddlewares...)
|
||||
|
||||
return nil
|
||||
})
|
||||
})
|
||||
|
||||
loader.Set("routerUse", func(middlewares ...goja.Value) {
|
||||
wrappedMiddlewares, err := wrapMiddlewares(executors, middlewares...)
|
||||
if err != nil {
|
||||
panic("[routerUse] failed to wrap middlewares: " + err.Error())
|
||||
}
|
||||
|
||||
app.OnBeforeServe().Add(func(e *core.ServeEvent) error {
|
||||
e.Router.Use(wrappedMiddlewares...)
|
||||
return nil
|
||||
})
|
||||
})
|
||||
|
||||
loader.Set("routerPre", func(middlewares ...goja.Value) {
|
||||
wrappedMiddlewares, err := wrapMiddlewares(executors, middlewares...)
|
||||
if err != nil {
|
||||
panic("[routerPre] failed to wrap middlewares: " + err.Error())
|
||||
}
|
||||
|
||||
app.OnBeforeServe().Add(func(e *core.ServeEvent) error {
|
||||
e.Router.Pre(wrappedMiddlewares...)
|
||||
return nil
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func wrapMiddlewares(executors *vmsPool, rawMiddlewares ...goja.Value) ([]echo.MiddlewareFunc, error) {
|
||||
wrappedMiddlewares := make([]echo.MiddlewareFunc, len(rawMiddlewares))
|
||||
|
||||
for i, m := range rawMiddlewares {
|
||||
switch v := m.Export().(type) {
|
||||
case echo.MiddlewareFunc:
|
||||
// "native" middleware - no need to wrap
|
||||
wrappedMiddlewares[i] = v
|
||||
case func(goja.FunctionCall) goja.Value, string:
|
||||
pr := goja.MustCompile("", "{(("+m.String()+").apply(undefined, __args)).apply(undefined, __args2)}", true)
|
||||
|
||||
wrappedMiddlewares[i] = func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
return executors.run(func(executor *goja.Runtime) error {
|
||||
executor.Set("__args", []any{next})
|
||||
executor.Set("__args2", []any{c})
|
||||
_, err := executor.RunProgram(pr)
|
||||
executor.Set("__args", goja.Undefined())
|
||||
executor.Set("__args2", goja.Undefined())
|
||||
return err
|
||||
})
|
||||
}
|
||||
}
|
||||
default:
|
||||
return nil, errors.New("unsupported goja middleware type")
|
||||
}
|
||||
}
|
||||
|
||||
return wrappedMiddlewares, nil
|
||||
}
|
||||
|
||||
func baseBinds(vm *goja.Runtime) {
|
||||
vm.SetFieldNameMapper(FieldMapper{})
|
||||
|
||||
|
@ -48,7 +234,7 @@ func baseBinds(vm *goja.Runtime) {
|
|||
}
|
||||
`)
|
||||
|
||||
vm.Set("$arrayOf", func(model any) any {
|
||||
vm.Set("arrayOf", func(model any) any {
|
||||
mt := reflect.TypeOf(model)
|
||||
st := reflect.SliceOf(mt)
|
||||
elem := reflect.New(st).Elem()
|
||||
|
@ -147,6 +333,8 @@ func baseBinds(vm *goja.Runtime) {
|
|||
|
||||
return instanceValue
|
||||
})
|
||||
|
||||
vm.Set("$stopPropagation", hook.StopPropagation)
|
||||
}
|
||||
|
||||
func dbxBinds(vm *goja.Runtime) {
|
||||
|
@ -253,11 +441,6 @@ func apisBinds(vm *goja.Runtime) {
|
|||
obj := vm.NewObject()
|
||||
vm.Set("$apis", obj)
|
||||
|
||||
vm.Set("Route", func(call goja.ConstructorCall) *goja.Object {
|
||||
instance := &echo.Route{}
|
||||
return structConstructor(vm, call, instance)
|
||||
})
|
||||
|
||||
// middlewares
|
||||
obj.Set("requireRecordAuth", apis.RequireRecordAuth)
|
||||
obj.Set("requireAdminAuth", apis.RequireAdminAuth)
|
||||
|
|
|
@ -38,7 +38,7 @@ func TestBaseBindsCount(t *testing.T) {
|
|||
vm := goja.New()
|
||||
baseBinds(vm)
|
||||
|
||||
testBindsCount(vm, "this", 14, t)
|
||||
testBindsCount(vm, "this", 15, t)
|
||||
}
|
||||
|
||||
func TestBaseBindsRecord(t *testing.T) {
|
||||
|
@ -682,48 +682,10 @@ func TestApisBindsCount(t *testing.T) {
|
|||
vm := goja.New()
|
||||
apisBinds(vm)
|
||||
|
||||
testBindsCount(vm, "this", 7, t)
|
||||
testBindsCount(vm, "this", 6, t)
|
||||
testBindsCount(vm, "$apis", 10, t)
|
||||
}
|
||||
|
||||
func TestApisBindsRoute(t *testing.T) {
|
||||
app, _ := tests.NewTestApp()
|
||||
defer app.Cleanup()
|
||||
|
||||
vm := goja.New()
|
||||
baseBinds(vm)
|
||||
apisBinds(vm)
|
||||
|
||||
_, err := vm.RunString(`
|
||||
let handlerCalls = 0;
|
||||
|
||||
const route = new Route({
|
||||
method: "test_method",
|
||||
path: "test_path",
|
||||
handler: () => {
|
||||
handlerCalls++;
|
||||
}
|
||||
});
|
||||
|
||||
route.handler();
|
||||
|
||||
if (handlerCalls != 1) {
|
||||
throw new Error('Expected handlerCalls 1, got ' + handlerCalls);
|
||||
}
|
||||
|
||||
if (route.method != "test_method") {
|
||||
throw new Error('Expected method "test_method", got ' + route.method);
|
||||
}
|
||||
|
||||
if (route.path != "test_path") {
|
||||
throw new Error('Expected method "test_path", got ' + route.path);
|
||||
}
|
||||
`)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestApisBindsApiError(t *testing.T) {
|
||||
app, _ := tests.NewTestApp()
|
||||
defer app.Cleanup()
|
||||
|
@ -843,7 +805,7 @@ func TestLoadingArrayOf(t *testing.T) {
|
|||
vm.Set("$app", app)
|
||||
|
||||
_, err := vm.RunString(`
|
||||
let result = $arrayOf(new DynamicModel({
|
||||
let result = arrayOf(new DynamicModel({
|
||||
id: "",
|
||||
text: "",
|
||||
}))
|
||||
|
|
|
@ -4,16 +4,96 @@ import (
|
|||
"log"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/pocketbase/pocketbase/core"
|
||||
"github.com/pocketbase/pocketbase/plugins/jsvm"
|
||||
"github.com/pocketbase/pocketbase/tools/list"
|
||||
"github.com/pocketbase/tygoja"
|
||||
)
|
||||
|
||||
const heading = `
|
||||
// -------------------------------------------------------------------
|
||||
// routerBinds
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* RouterAdd registers a new route definition.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ` + "```" + `js
|
||||
* routerAdd("GET", "/hello", (c) => {
|
||||
* return c.json(200, {"message": "Hello!"})
|
||||
* }, $apis.requireAdminOrRecordAuth())
|
||||
* ` + "```" + `
|
||||
*
|
||||
* _Note that this method is available only in pb_hooks context._
|
||||
*
|
||||
* @group PocketBase
|
||||
*/
|
||||
declare function routerAdd(
|
||||
method: string,
|
||||
path: string,
|
||||
handler: echo.HandlerFunc,
|
||||
...middlewares: Array<string|echo.MiddlewareFunc>,
|
||||
): void;
|
||||
|
||||
/**
|
||||
* RouterUse registers one or more global middlewares that are executed
|
||||
* along the handler middlewares after a matching route is found.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ` + "```" + `js
|
||||
* routerUse((next) => {
|
||||
* return (c) => {
|
||||
* console.log(c.Path())
|
||||
* return next(c)
|
||||
* }
|
||||
* })
|
||||
* ` + "```" + `
|
||||
*
|
||||
* _Note that this method is available only in pb_hooks context._
|
||||
*
|
||||
* @group PocketBase
|
||||
*/
|
||||
declare function routerUse(...middlewares: Array<string|echo.MiddlewareFunc>): void;
|
||||
|
||||
/**
|
||||
* RouterPre registers one or more global middlewares that are executed
|
||||
* BEFORE the router processes the request. It is usually used for making
|
||||
* changes to the request properties, for example, adding or removing
|
||||
* a trailing slash or adding segments to a path so it matches a route.
|
||||
*
|
||||
* NB! Since the router will not have processed the request yet,
|
||||
* middlewares registered at this level won't have access to any path
|
||||
* related APIs from echo.Context.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ` + "```" + `js
|
||||
* routerPre((next) => {
|
||||
* return (c) => {
|
||||
* console.log(c.request().url)
|
||||
* return next(c)
|
||||
* }
|
||||
* })
|
||||
* ` + "```" + `
|
||||
*
|
||||
* _Note that this method is available only in pb_hooks context._
|
||||
*
|
||||
* @group PocketBase
|
||||
*/
|
||||
declare function routerPre(...middlewares: Array<string|echo.MiddlewareFunc>): void;
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// baseBinds
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
// skip on* hook methods as they are registered via the global on* method
|
||||
type appWithoutHooks = Omit<pocketbase.PocketBase, ` + "`on${string}`" + `>
|
||||
|
||||
/**
|
||||
* $app is the current running PocketBase instance that is globally
|
||||
* available in each .pb.js file.
|
||||
|
@ -21,23 +101,23 @@ const heading = `
|
|||
* @namespace
|
||||
* @group PocketBase
|
||||
*/
|
||||
declare var $app: pocketbase.PocketBase
|
||||
declare var $app: appWithoutHooks
|
||||
|
||||
/**
|
||||
* $arrayOf creates a placeholder array of the specified models.
|
||||
* arrayOf creates a placeholder array of the specified models.
|
||||
* Usually used to populate DB result into an array of models.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ` + "```" + `js
|
||||
* const records = $arrayOf(new Record)
|
||||
* const records = arrayOf(new Record)
|
||||
*
|
||||
* $app.dao().recordQuery(collection).limit(10).all(records)
|
||||
* ` + "```" + `
|
||||
*
|
||||
* @group PocketBase
|
||||
*/
|
||||
declare function $arrayOf<T>(model: T): Array<T>;
|
||||
declare function arrayOf<T>(model: T): Array<T>;
|
||||
|
||||
/**
|
||||
* DynamicModel creates a new dynamic model with fields from the provided data shape.
|
||||
|
@ -498,27 +578,6 @@ declare class TestS3FilesystemForm implements forms.TestS3Filesystem {
|
|||
// apisBinds
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
interface Route extends echo.Route{} // merge
|
||||
/**
|
||||
* Route specifies a new route definition.
|
||||
* This is usually used when registering routes with router.addRoute().
|
||||
*
|
||||
* ` + "```" + `js
|
||||
* const route = new Route({
|
||||
* path: "/hello",
|
||||
* handler: (c) => {
|
||||
* c.string(200, "hello world!")
|
||||
* },
|
||||
* middlewares: [$apis.activityLogger($app)]
|
||||
* })
|
||||
* ` + "```" + `
|
||||
*
|
||||
* @group PocketBase
|
||||
*/
|
||||
declare class Route implements echo.Route {
|
||||
constructor(data?: Partial<echo.Route>)
|
||||
}
|
||||
|
||||
interface ApiError extends apis.ApiError{} // merge
|
||||
/**
|
||||
* @inheritDoc
|
||||
|
@ -594,7 +653,7 @@ declare namespace $apis {
|
|||
/**
|
||||
* Migrate defines a single migration upgrade/downgrade action.
|
||||
*
|
||||
* Note that this method is available only in pb_migrations context.
|
||||
* _Note that this method is available only in pb_migrations context._
|
||||
*
|
||||
* @group PocketBase
|
||||
*/
|
||||
|
@ -604,8 +663,10 @@ declare function migrate(
|
|||
): void;
|
||||
`
|
||||
|
||||
var mapper = &jsvm.FieldMapper{}
|
||||
|
||||
func main() {
|
||||
mapper := &jsvm.FieldMapper{}
|
||||
declarations := heading + hooksDeclarations()
|
||||
|
||||
gen := tygoja.New(tygoja.Config{
|
||||
Packages: map[string][]string{
|
||||
|
@ -626,7 +687,7 @@ func main() {
|
|||
},
|
||||
Indent: " ", // use only a single space to reduce slight the size
|
||||
WithPackageFunctions: true,
|
||||
Heading: heading,
|
||||
Heading: declarations,
|
||||
})
|
||||
|
||||
result, err := gen.Generate()
|
||||
|
@ -638,3 +699,45 @@ func main() {
|
|||
log.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func hooksDeclarations() string {
|
||||
var result strings.Builder
|
||||
|
||||
excluded := []string{"OnBeforeServe"}
|
||||
appType := reflect.TypeOf(struct{ core.App }{})
|
||||
totalMethods := appType.NumMethod()
|
||||
|
||||
for i := 0; i < totalMethods; i++ {
|
||||
method := appType.Method(i)
|
||||
if !strings.HasPrefix(method.Name, "On") || list.ExistInSlice(method.Name, excluded) {
|
||||
continue // not a hook or excluded
|
||||
}
|
||||
|
||||
hookType := method.Type.Out(0)
|
||||
|
||||
withTags := strings.HasPrefix(hookType.String(), "*hook.TaggedHook")
|
||||
|
||||
addMethod, ok := hookType.MethodByName("Add")
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
addHanlder := addMethod.Type.In(1)
|
||||
eventTypeName := strings.TrimPrefix(addHanlder.In(0).String(), "*")
|
||||
|
||||
jsName := mapper.MethodName(appType, method)
|
||||
result.WriteString("/** @group PocketBase */")
|
||||
result.WriteString("declare function ")
|
||||
result.WriteString(jsName)
|
||||
result.WriteString("(handler: (e: ")
|
||||
result.WriteString(eventTypeName)
|
||||
result.WriteString(") => void")
|
||||
if withTags {
|
||||
result.WriteString(", ...tags: string[]")
|
||||
}
|
||||
result.WriteString("): void")
|
||||
result.WriteString("\n")
|
||||
}
|
||||
|
||||
return result.String()
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -18,7 +18,6 @@ import (
|
|||
|
||||
"github.com/dop251/goja"
|
||||
"github.com/dop251/goja_nodejs/console"
|
||||
"github.com/dop251/goja_nodejs/eventloop"
|
||||
"github.com/dop251/goja_nodejs/process"
|
||||
"github.com/dop251/goja_nodejs/require"
|
||||
"github.com/fatih/color"
|
||||
|
@ -49,6 +48,13 @@ type Config struct {
|
|||
// If not set it fallbacks to a relative "pb_data/../pb_hooks" directory.
|
||||
HooksDir string
|
||||
|
||||
// HooksPoolSize specifies how many goja.Runtime instances to preinit
|
||||
// and keep for the JS app hooks gorotines execution.
|
||||
//
|
||||
// Zero or negative value means that it will create a new goja.Runtime
|
||||
// on every fired goroutine.
|
||||
HooksPoolSize int
|
||||
|
||||
// MigrationsDir specifies the JS migrations directory.
|
||||
//
|
||||
// If not set it fallbacks to a relative "pb_data/../pb_migrations" directory.
|
||||
|
@ -172,11 +178,10 @@ func (p *plugin) registerHooks() error {
|
|||
}
|
||||
}
|
||||
|
||||
registry := new(require.Registry) // this can be shared by multiple runtimes
|
||||
// this is safe to be shared across multiple vms
|
||||
registry := new(require.Registry)
|
||||
|
||||
loop := eventloop.NewEventLoop()
|
||||
|
||||
loop.Run(func(vm *goja.Runtime) {
|
||||
sharedBinds := func(vm *goja.Runtime) {
|
||||
registry.Enable(vm)
|
||||
console.Enable(vm)
|
||||
process.Enable(vm)
|
||||
|
@ -186,11 +191,25 @@ func (p *plugin) registerHooks() error {
|
|||
securityBinds(vm)
|
||||
formsBinds(vm)
|
||||
apisBinds(vm)
|
||||
|
||||
vm.Set("$app", p.app)
|
||||
}
|
||||
|
||||
// initiliaze the executor vms
|
||||
executors := newPool(p.config.HooksPoolSize, func() *goja.Runtime {
|
||||
executor := goja.New()
|
||||
sharedBinds(executor)
|
||||
return executor
|
||||
})
|
||||
|
||||
// initialize the loader vm
|
||||
loader := goja.New()
|
||||
sharedBinds(loader)
|
||||
hooksBinds(p.app, loader, executors)
|
||||
cronBinds(p.app, loader, executors)
|
||||
routerBinds(p.app, loader, executors)
|
||||
|
||||
for file, content := range files {
|
||||
_, err := vm.RunString(string(content))
|
||||
_, err := loader.RunString(string(content))
|
||||
if err != nil {
|
||||
if p.config.HooksWatch {
|
||||
color.Red("Failed to execute %s: %v", file, err)
|
||||
|
@ -199,13 +218,8 @@ func (p *plugin) registerHooks() error {
|
|||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
loop.Start()
|
||||
|
||||
p.app.OnTerminate().Add(func(e *core.TerminateEvent) error {
|
||||
loop.StopNoWait()
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
|
@ -243,11 +257,12 @@ func (p *plugin) watchHooks() error {
|
|||
|
||||
// start listening for events.
|
||||
go func() {
|
||||
defer stopDebounceTimer()
|
||||
|
||||
for {
|
||||
select {
|
||||
case event, ok := <-watcher.Events:
|
||||
if !ok {
|
||||
stopDebounceTimer()
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -266,7 +281,6 @@ func (p *plugin) watchHooks() error {
|
|||
})
|
||||
case err, ok := <-watcher.Errors:
|
||||
if !ok {
|
||||
stopDebounceTimer()
|
||||
return
|
||||
}
|
||||
color.Red("Watch error:", err)
|
||||
|
|
|
@ -0,0 +1,73 @@
|
|||
package jsvm
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/dop251/goja"
|
||||
)
|
||||
|
||||
type poolItem struct {
|
||||
mux sync.Mutex
|
||||
busy bool
|
||||
vm *goja.Runtime
|
||||
}
|
||||
|
||||
type vmsPool struct {
|
||||
mux sync.RWMutex
|
||||
factory func() *goja.Runtime
|
||||
items []*poolItem
|
||||
}
|
||||
|
||||
// newPool creates a new pool with pre-warmed vms generated from the specified factory.
|
||||
func newPool(size int, factory func() *goja.Runtime) *vmsPool {
|
||||
pool := &vmsPool{
|
||||
factory: factory,
|
||||
items: make([]*poolItem, size),
|
||||
}
|
||||
|
||||
for i := 0; i < size; i++ {
|
||||
vm := pool.factory()
|
||||
pool.items[i] = &poolItem{vm: vm}
|
||||
}
|
||||
|
||||
return pool
|
||||
}
|
||||
|
||||
// run executes "call" with a vm created from the pool
|
||||
// (either from the buffer or a new one if all buffered vms are busy)
|
||||
func (p *vmsPool) run(call func(vm *goja.Runtime) error) error {
|
||||
p.mux.RLock()
|
||||
|
||||
// try to find a free item
|
||||
var freeItem *poolItem
|
||||
for _, item := range p.items {
|
||||
item.mux.Lock()
|
||||
if item.busy {
|
||||
item.mux.Unlock()
|
||||
continue
|
||||
}
|
||||
item.busy = true
|
||||
item.mux.Unlock()
|
||||
freeItem = item
|
||||
break
|
||||
}
|
||||
|
||||
p.mux.RUnlock()
|
||||
|
||||
// create a new one-off item if of all of the pool items are currently busy
|
||||
//
|
||||
// note: if turned out not efficient we may change this in the future
|
||||
// by adding the created item in the pool with some timer for removal
|
||||
if freeItem == nil {
|
||||
return call(p.factory())
|
||||
}
|
||||
|
||||
execErr := call(freeItem.vm)
|
||||
|
||||
// "free" the vm
|
||||
freeItem.mux.Lock()
|
||||
freeItem.busy = false
|
||||
freeItem.mux.Unlock()
|
||||
|
||||
return execErr
|
||||
}
|
Loading…
Reference in New Issue