(no tests) updated jsvm bindings
This commit is contained in:
		
							parent
							
								
									5e37c90dde
								
							
						
					
					
						commit
						13d96e793b
					
				| 
						 | 
					@ -2,26 +2,212 @@ package jsvm
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"encoding/json"
 | 
						"encoding/json"
 | 
				
			||||||
 | 
						"errors"
 | 
				
			||||||
	"reflect"
 | 
						"reflect"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/dop251/goja"
 | 
						"github.com/dop251/goja"
 | 
				
			||||||
	validation "github.com/go-ozzo/ozzo-validation/v4"
 | 
						validation "github.com/go-ozzo/ozzo-validation/v4"
 | 
				
			||||||
	"github.com/labstack/echo/v5"
 | 
						"github.com/labstack/echo/v5"
 | 
				
			||||||
	"github.com/pocketbase/dbx"
 | 
						"github.com/pocketbase/dbx"
 | 
				
			||||||
	"github.com/pocketbase/pocketbase/apis"
 | 
						"github.com/pocketbase/pocketbase/apis"
 | 
				
			||||||
 | 
						"github.com/pocketbase/pocketbase/core"
 | 
				
			||||||
	"github.com/pocketbase/pocketbase/daos"
 | 
						"github.com/pocketbase/pocketbase/daos"
 | 
				
			||||||
	"github.com/pocketbase/pocketbase/forms"
 | 
						"github.com/pocketbase/pocketbase/forms"
 | 
				
			||||||
	"github.com/pocketbase/pocketbase/models"
 | 
						"github.com/pocketbase/pocketbase/models"
 | 
				
			||||||
	"github.com/pocketbase/pocketbase/models/schema"
 | 
						"github.com/pocketbase/pocketbase/models/schema"
 | 
				
			||||||
	"github.com/pocketbase/pocketbase/tokens"
 | 
						"github.com/pocketbase/pocketbase/tokens"
 | 
				
			||||||
 | 
						"github.com/pocketbase/pocketbase/tools/cron"
 | 
				
			||||||
	"github.com/pocketbase/pocketbase/tools/filesystem"
 | 
						"github.com/pocketbase/pocketbase/tools/filesystem"
 | 
				
			||||||
 | 
						"github.com/pocketbase/pocketbase/tools/hook"
 | 
				
			||||||
	"github.com/pocketbase/pocketbase/tools/inflector"
 | 
						"github.com/pocketbase/pocketbase/tools/inflector"
 | 
				
			||||||
 | 
						"github.com/pocketbase/pocketbase/tools/list"
 | 
				
			||||||
	"github.com/pocketbase/pocketbase/tools/mailer"
 | 
						"github.com/pocketbase/pocketbase/tools/mailer"
 | 
				
			||||||
	"github.com/pocketbase/pocketbase/tools/security"
 | 
						"github.com/pocketbase/pocketbase/tools/security"
 | 
				
			||||||
	"github.com/pocketbase/pocketbase/tools/types"
 | 
						"github.com/pocketbase/pocketbase/tools/types"
 | 
				
			||||||
	"github.com/spf13/cobra"
 | 
						"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) {
 | 
					func baseBinds(vm *goja.Runtime) {
 | 
				
			||||||
	vm.SetFieldNameMapper(FieldMapper{})
 | 
						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)
 | 
							mt := reflect.TypeOf(model)
 | 
				
			||||||
		st := reflect.SliceOf(mt)
 | 
							st := reflect.SliceOf(mt)
 | 
				
			||||||
		elem := reflect.New(st).Elem()
 | 
							elem := reflect.New(st).Elem()
 | 
				
			||||||
| 
						 | 
					@ -147,6 +333,8 @@ func baseBinds(vm *goja.Runtime) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		return instanceValue
 | 
							return instanceValue
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						vm.Set("$stopPropagation", hook.StopPropagation)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func dbxBinds(vm *goja.Runtime) {
 | 
					func dbxBinds(vm *goja.Runtime) {
 | 
				
			||||||
| 
						 | 
					@ -253,11 +441,6 @@ func apisBinds(vm *goja.Runtime) {
 | 
				
			||||||
	obj := vm.NewObject()
 | 
						obj := vm.NewObject()
 | 
				
			||||||
	vm.Set("$apis", obj)
 | 
						vm.Set("$apis", obj)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	vm.Set("Route", func(call goja.ConstructorCall) *goja.Object {
 | 
					 | 
				
			||||||
		instance := &echo.Route{}
 | 
					 | 
				
			||||||
		return structConstructor(vm, call, instance)
 | 
					 | 
				
			||||||
	})
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// middlewares
 | 
						// middlewares
 | 
				
			||||||
	obj.Set("requireRecordAuth", apis.RequireRecordAuth)
 | 
						obj.Set("requireRecordAuth", apis.RequireRecordAuth)
 | 
				
			||||||
	obj.Set("requireAdminAuth", apis.RequireAdminAuth)
 | 
						obj.Set("requireAdminAuth", apis.RequireAdminAuth)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -38,7 +38,7 @@ func TestBaseBindsCount(t *testing.T) {
 | 
				
			||||||
	vm := goja.New()
 | 
						vm := goja.New()
 | 
				
			||||||
	baseBinds(vm)
 | 
						baseBinds(vm)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	testBindsCount(vm, "this", 14, t)
 | 
						testBindsCount(vm, "this", 15, t)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func TestBaseBindsRecord(t *testing.T) {
 | 
					func TestBaseBindsRecord(t *testing.T) {
 | 
				
			||||||
| 
						 | 
					@ -682,48 +682,10 @@ func TestApisBindsCount(t *testing.T) {
 | 
				
			||||||
	vm := goja.New()
 | 
						vm := goja.New()
 | 
				
			||||||
	apisBinds(vm)
 | 
						apisBinds(vm)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	testBindsCount(vm, "this", 7, t)
 | 
						testBindsCount(vm, "this", 6, t)
 | 
				
			||||||
	testBindsCount(vm, "$apis", 10, 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) {
 | 
					func TestApisBindsApiError(t *testing.T) {
 | 
				
			||||||
	app, _ := tests.NewTestApp()
 | 
						app, _ := tests.NewTestApp()
 | 
				
			||||||
	defer app.Cleanup()
 | 
						defer app.Cleanup()
 | 
				
			||||||
| 
						 | 
					@ -843,7 +805,7 @@ func TestLoadingArrayOf(t *testing.T) {
 | 
				
			||||||
	vm.Set("$app", app)
 | 
						vm.Set("$app", app)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	_, err := vm.RunString(`
 | 
						_, err := vm.RunString(`
 | 
				
			||||||
		let result = $arrayOf(new DynamicModel({
 | 
							let result = arrayOf(new DynamicModel({
 | 
				
			||||||
			id:   "",
 | 
								id:   "",
 | 
				
			||||||
			text: "",
 | 
								text: "",
 | 
				
			||||||
		}))
 | 
							}))
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -4,16 +4,96 @@ import (
 | 
				
			||||||
	"log"
 | 
						"log"
 | 
				
			||||||
	"os"
 | 
						"os"
 | 
				
			||||||
	"reflect"
 | 
						"reflect"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/pocketbase/pocketbase/core"
 | 
				
			||||||
	"github.com/pocketbase/pocketbase/plugins/jsvm"
 | 
						"github.com/pocketbase/pocketbase/plugins/jsvm"
 | 
				
			||||||
 | 
						"github.com/pocketbase/pocketbase/tools/list"
 | 
				
			||||||
	"github.com/pocketbase/tygoja"
 | 
						"github.com/pocketbase/tygoja"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const heading = `
 | 
					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
 | 
					// 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
 | 
					 * $app is the current running PocketBase instance that is globally
 | 
				
			||||||
 * available in each .pb.js file.
 | 
					 * available in each .pb.js file.
 | 
				
			||||||
| 
						 | 
					@ -21,23 +101,23 @@ const heading = `
 | 
				
			||||||
 * @namespace
 | 
					 * @namespace
 | 
				
			||||||
 * @group PocketBase
 | 
					 * @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.
 | 
					 * Usually used to populate DB result into an array of models.
 | 
				
			||||||
 *
 | 
					 *
 | 
				
			||||||
 * Example:
 | 
					 * Example:
 | 
				
			||||||
 *
 | 
					 *
 | 
				
			||||||
 * ` + "```" + `js
 | 
					 * ` + "```" + `js
 | 
				
			||||||
 * const records = $arrayOf(new Record)
 | 
					 * const records = arrayOf(new Record)
 | 
				
			||||||
 *
 | 
					 *
 | 
				
			||||||
 * $app.dao().recordQuery(collection).limit(10).all(records)
 | 
					 * $app.dao().recordQuery(collection).limit(10).all(records)
 | 
				
			||||||
 * ` + "```" + `
 | 
					 * ` + "```" + `
 | 
				
			||||||
 *
 | 
					 *
 | 
				
			||||||
 * @group PocketBase
 | 
					 * @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.
 | 
					 * DynamicModel creates a new dynamic model with fields from the provided data shape.
 | 
				
			||||||
| 
						 | 
					@ -498,27 +578,6 @@ declare class TestS3FilesystemForm implements forms.TestS3Filesystem {
 | 
				
			||||||
// apisBinds
 | 
					// 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
 | 
					interface ApiError extends apis.ApiError{} // merge
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * @inheritDoc
 | 
					 * @inheritDoc
 | 
				
			||||||
| 
						 | 
					@ -594,7 +653,7 @@ declare namespace $apis {
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Migrate defines a single migration upgrade/downgrade action.
 | 
					 * 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
 | 
					 * @group PocketBase
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
| 
						 | 
					@ -604,8 +663,10 @@ declare function migrate(
 | 
				
			||||||
): void;
 | 
					): void;
 | 
				
			||||||
`
 | 
					`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var mapper = &jsvm.FieldMapper{}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func main() {
 | 
					func main() {
 | 
				
			||||||
	mapper := &jsvm.FieldMapper{}
 | 
						declarations := heading + hooksDeclarations()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	gen := tygoja.New(tygoja.Config{
 | 
						gen := tygoja.New(tygoja.Config{
 | 
				
			||||||
		Packages: map[string][]string{
 | 
							Packages: map[string][]string{
 | 
				
			||||||
| 
						 | 
					@ -626,7 +687,7 @@ func main() {
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
		Indent:               " ", // use only a single space to reduce slight the size
 | 
							Indent:               " ", // use only a single space to reduce slight the size
 | 
				
			||||||
		WithPackageFunctions: true,
 | 
							WithPackageFunctions: true,
 | 
				
			||||||
		Heading:              heading,
 | 
							Heading:              declarations,
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	result, err := gen.Generate()
 | 
						result, err := gen.Generate()
 | 
				
			||||||
| 
						 | 
					@ -638,3 +699,45 @@ func main() {
 | 
				
			||||||
		log.Fatal(err)
 | 
							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"
 | 
				
			||||||
	"github.com/dop251/goja_nodejs/console"
 | 
						"github.com/dop251/goja_nodejs/console"
 | 
				
			||||||
	"github.com/dop251/goja_nodejs/eventloop"
 | 
					 | 
				
			||||||
	"github.com/dop251/goja_nodejs/process"
 | 
						"github.com/dop251/goja_nodejs/process"
 | 
				
			||||||
	"github.com/dop251/goja_nodejs/require"
 | 
						"github.com/dop251/goja_nodejs/require"
 | 
				
			||||||
	"github.com/fatih/color"
 | 
						"github.com/fatih/color"
 | 
				
			||||||
| 
						 | 
					@ -49,6 +48,13 @@ type Config struct {
 | 
				
			||||||
	// If not set it fallbacks to a relative "pb_data/../pb_hooks" directory.
 | 
						// If not set it fallbacks to a relative "pb_data/../pb_hooks" directory.
 | 
				
			||||||
	HooksDir string
 | 
						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.
 | 
						// MigrationsDir specifies the JS migrations directory.
 | 
				
			||||||
	//
 | 
						//
 | 
				
			||||||
	// If not set it fallbacks to a relative "pb_data/../pb_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()
 | 
						sharedBinds := func(vm *goja.Runtime) {
 | 
				
			||||||
 | 
					 | 
				
			||||||
	loop.Run(func(vm *goja.Runtime) {
 | 
					 | 
				
			||||||
		registry.Enable(vm)
 | 
							registry.Enable(vm)
 | 
				
			||||||
		console.Enable(vm)
 | 
							console.Enable(vm)
 | 
				
			||||||
		process.Enable(vm)
 | 
							process.Enable(vm)
 | 
				
			||||||
| 
						 | 
					@ -186,26 +191,35 @@ func (p *plugin) registerHooks() error {
 | 
				
			||||||
		securityBinds(vm)
 | 
							securityBinds(vm)
 | 
				
			||||||
		formsBinds(vm)
 | 
							formsBinds(vm)
 | 
				
			||||||
		apisBinds(vm)
 | 
							apisBinds(vm)
 | 
				
			||||||
 | 
					 | 
				
			||||||
		vm.Set("$app", p.app)
 | 
							vm.Set("$app", p.app)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		for file, content := range files {
 | 
						// initiliaze the executor vms
 | 
				
			||||||
			_, err := vm.RunString(string(content))
 | 
						executors := newPool(p.config.HooksPoolSize, func() *goja.Runtime {
 | 
				
			||||||
			if err != nil {
 | 
							executor := goja.New()
 | 
				
			||||||
				if p.config.HooksWatch {
 | 
							sharedBinds(executor)
 | 
				
			||||||
					color.Red("Failed to execute %s: %v", file, err)
 | 
							return executor
 | 
				
			||||||
				} else {
 | 
					 | 
				
			||||||
					panic(err)
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
			}
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	loop.Start()
 | 
						// 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 := loader.RunString(string(content))
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								if p.config.HooksWatch {
 | 
				
			||||||
 | 
									color.Red("Failed to execute %s: %v", file, err)
 | 
				
			||||||
 | 
								} else {
 | 
				
			||||||
 | 
									panic(err)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	p.app.OnTerminate().Add(func(e *core.TerminateEvent) error {
 | 
						p.app.OnTerminate().Add(func(e *core.TerminateEvent) error {
 | 
				
			||||||
		loop.StopNoWait()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		return nil
 | 
							return nil
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -243,11 +257,12 @@ func (p *plugin) watchHooks() error {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// start listening for events.
 | 
						// start listening for events.
 | 
				
			||||||
	go func() {
 | 
						go func() {
 | 
				
			||||||
 | 
							defer stopDebounceTimer()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		for {
 | 
							for {
 | 
				
			||||||
			select {
 | 
								select {
 | 
				
			||||||
			case event, ok := <-watcher.Events:
 | 
								case event, ok := <-watcher.Events:
 | 
				
			||||||
				if !ok {
 | 
									if !ok {
 | 
				
			||||||
					stopDebounceTimer()
 | 
					 | 
				
			||||||
					return
 | 
										return
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -266,7 +281,6 @@ func (p *plugin) watchHooks() error {
 | 
				
			||||||
				})
 | 
									})
 | 
				
			||||||
			case err, ok := <-watcher.Errors:
 | 
								case err, ok := <-watcher.Errors:
 | 
				
			||||||
				if !ok {
 | 
									if !ok {
 | 
				
			||||||
					stopDebounceTimer()
 | 
					 | 
				
			||||||
					return
 | 
										return
 | 
				
			||||||
				}
 | 
									}
 | 
				
			||||||
				color.Red("Watch error:", err)
 | 
									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