merge v0.23.0-rc changes
This commit is contained in:
@@ -252,6 +252,7 @@ func (p *plugin) update(withBackup bool) error {
|
||||
fmt.Print("\n")
|
||||
color.Cyan("Here is a list with some of the %s changes:", latest.Tag)
|
||||
// remove the update command note to avoid "stuttering"
|
||||
// (@todo consider moving to a config option)
|
||||
releaseNotes := strings.TrimSpace(strings.Replace(latest.Body, "> _To update the prebuilt executable you can run `./"+p.config.ArchiveExecutable+" update`._", "", 1))
|
||||
color.Cyan(releaseNotes)
|
||||
fmt.Print("\n")
|
||||
|
||||
@@ -26,11 +26,13 @@ func TestCompareVersions(t *testing.T) {
|
||||
{"3.2.4", "3.2.3", -1},
|
||||
}
|
||||
|
||||
for i, s := range scenarios {
|
||||
result := compareVersions(s.a, s.b)
|
||||
for _, s := range scenarios {
|
||||
t.Run(s.a+"VS"+s.b, func(t *testing.T) {
|
||||
result := compareVersions(s.a, s.b)
|
||||
|
||||
if result != s.expected {
|
||||
t.Fatalf("[%d] Expected %q vs %q to result in %d, got %d", i, s.a, s.b, s.expected, result)
|
||||
}
|
||||
if result != s.expected {
|
||||
t.Fatalf("Expected %q vs %q to result in %d, got %d", s.a, s.b, s.expected, result)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
+263
-190
@@ -12,29 +12,23 @@ import (
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/dop251/goja"
|
||||
validation "github.com/go-ozzo/ozzo-validation/v4"
|
||||
"github.com/labstack/echo/v5"
|
||||
"github.com/labstack/echo/v5/middleware"
|
||||
"github.com/golang-jwt/jwt/v4"
|
||||
"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/mails"
|
||||
"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/rest"
|
||||
"github.com/pocketbase/pocketbase/tools/router"
|
||||
"github.com/pocketbase/pocketbase/tools/security"
|
||||
"github.com/pocketbase/pocketbase/tools/subscriptions"
|
||||
"github.com/pocketbase/pocketbase/tools/types"
|
||||
@@ -49,11 +43,11 @@ func hooksBinds(app core.App, loader *goja.Runtime, executors *vmsPool) {
|
||||
appType := reflect.TypeOf(app)
|
||||
appValue := reflect.ValueOf(app)
|
||||
totalMethods := appType.NumMethod()
|
||||
excludeHooks := []string{"OnBeforeServe"}
|
||||
excludeHooks := []string{"OnServe"}
|
||||
|
||||
for i := 0; i < totalMethods; i++ {
|
||||
method := appType.Method(i)
|
||||
if !strings.HasPrefix(method.Name, "On") || list.ExistInSlice(method.Name, excludeHooks) {
|
||||
if !strings.HasPrefix(method.Name, "On") || slices.Contains(excludeHooks, method.Name) {
|
||||
continue // not a hook or excluded
|
||||
}
|
||||
|
||||
@@ -69,9 +63,9 @@ func hooksBinds(app core.App, loader *goja.Runtime, executors *vmsPool) {
|
||||
}
|
||||
|
||||
hookInstance := appValue.MethodByName(method.Name).Call(tagsAsValues)[0]
|
||||
addFunc := hookInstance.MethodByName("Add")
|
||||
hookBindFunc := hookInstance.MethodByName("BindFunc")
|
||||
|
||||
handlerType := addFunc.Type().In(0)
|
||||
handlerType := hookBindFunc.Type().In(0)
|
||||
|
||||
handler := reflect.MakeFunc(handlerType, func(args []reflect.Value) (results []reflect.Value) {
|
||||
handlerArgs := make([]any, len(args))
|
||||
@@ -84,15 +78,10 @@ func hooksBinds(app core.App, loader *goja.Runtime, executors *vmsPool) {
|
||||
res, err := executor.RunProgram(pr)
|
||||
executor.Set("__args", goja.Undefined())
|
||||
|
||||
// check for returned error or false
|
||||
// check for returned error value
|
||||
if res != nil {
|
||||
switch v := res.Export().(type) {
|
||||
case error:
|
||||
return v
|
||||
case bool:
|
||||
if !v {
|
||||
return hook.StopPropagation
|
||||
}
|
||||
if resErr, ok := res.Export().(error); ok {
|
||||
return resErr
|
||||
}
|
||||
}
|
||||
|
||||
@@ -103,20 +92,16 @@ func hooksBinds(app core.App, loader *goja.Runtime, executors *vmsPool) {
|
||||
})
|
||||
|
||||
// register the wrapped hook handler
|
||||
addFunc.Call([]reflect.Value{handler})
|
||||
hookBindFunc.Call([]reflect.Value{handler})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func cronBinds(app core.App, loader *goja.Runtime, executors *vmsPool) {
|
||||
scheduler := cron.New()
|
||||
|
||||
var wasServeTriggered bool
|
||||
|
||||
loader.Set("cronAdd", func(jobId, cronExpr, handler string) {
|
||||
pr := goja.MustCompile("", "{("+handler+").apply(undefined)}", true)
|
||||
|
||||
err := scheduler.Add(jobId, cronExpr, func() {
|
||||
err := app.Cron().Add(jobId, cronExpr, func() {
|
||||
err := executors.run(func(executor *goja.Runtime) error {
|
||||
_, err := executor.RunProgram(pr)
|
||||
return err
|
||||
@@ -133,32 +118,29 @@ func cronBinds(app core.App, loader *goja.Runtime, executors *vmsPool) {
|
||||
if err != nil {
|
||||
panic("[cronAdd] failed to register cron job " + jobId + ": " + err.Error())
|
||||
}
|
||||
|
||||
// start the ticker (if not already)
|
||||
if wasServeTriggered && scheduler.Total() > 0 && !scheduler.HasStarted() {
|
||||
scheduler.Start()
|
||||
}
|
||||
})
|
||||
|
||||
// note: it is not necessary needed but it is here for consistency
|
||||
loader.Set("cronRemove", func(jobId string) {
|
||||
scheduler.Remove(jobId)
|
||||
|
||||
// stop the ticker if there are no other jobs
|
||||
if scheduler.Total() == 0 {
|
||||
scheduler.Stop()
|
||||
}
|
||||
app.Cron().Remove(jobId)
|
||||
})
|
||||
|
||||
app.OnBeforeServe().Add(func(e *core.ServeEvent) error {
|
||||
// start the ticker (if not already)
|
||||
if scheduler.Total() > 0 && !scheduler.HasStarted() {
|
||||
scheduler.Start()
|
||||
}
|
||||
// register the removal helper also in the executors to allow removing cron jobs from everywhere
|
||||
oldFactory := executors.factory
|
||||
executors.factory = func() *goja.Runtime {
|
||||
vm := oldFactory()
|
||||
|
||||
wasServeTriggered = true
|
||||
vm.Set("cronRemove", func(jobId string) {
|
||||
app.Cron().Remove(jobId)
|
||||
})
|
||||
|
||||
return nil
|
||||
})
|
||||
return vm
|
||||
}
|
||||
for _, item := range executors.items {
|
||||
item.vm.Set("cronRemove", func(jobId string) {
|
||||
app.Cron().Remove(jobId)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func routerBinds(app core.App, loader *goja.Runtime, executors *vmsPool) {
|
||||
@@ -168,15 +150,15 @@ func routerBinds(app core.App, loader *goja.Runtime, executors *vmsPool) {
|
||||
panic("[routerAdd] failed to wrap middlewares: " + err.Error())
|
||||
}
|
||||
|
||||
wrappedHandler, err := wrapHandler(executors, handler)
|
||||
wrappedHandler, err := wrapHandlerFunc(executors, handler)
|
||||
if err != nil {
|
||||
panic("[routerAdd] failed to wrap handler: " + err.Error())
|
||||
}
|
||||
|
||||
app.OnBeforeServe().Add(func(e *core.ServeEvent) error {
|
||||
e.Router.Add(strings.ToUpper(method), path, wrappedHandler, wrappedMiddlewares...)
|
||||
app.OnServe().BindFunc(func(e *core.ServeEvent) error {
|
||||
e.Router.Route(strings.ToUpper(method), path, wrappedHandler).Bind(wrappedMiddlewares...)
|
||||
|
||||
return nil
|
||||
return e.Next()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -186,40 +168,28 @@ func routerBinds(app core.App, loader *goja.Runtime, executors *vmsPool) {
|
||||
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
|
||||
app.OnServe().BindFunc(func(e *core.ServeEvent) error {
|
||||
e.Router.Bind(wrappedMiddlewares...)
|
||||
return e.Next()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func wrapHandler(executors *vmsPool, handler goja.Value) (echo.HandlerFunc, error) {
|
||||
func wrapHandlerFunc(executors *vmsPool, handler goja.Value) (hook.HandlerFunc[*core.RequestEvent], 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
|
||||
case hook.HandlerFunc[*core.RequestEvent]:
|
||||
// "native" handler func - no need to wrap
|
||||
return h, nil
|
||||
case func(goja.FunctionCall) goja.Value, string:
|
||||
pr := goja.MustCompile("", "{("+handler.String()+").apply(undefined, __args)}", true)
|
||||
|
||||
wrappedHandler := func(c echo.Context) error {
|
||||
wrappedHandler := func(e *core.RequestEvent) error {
|
||||
return executors.run(func(executor *goja.Runtime) error {
|
||||
executor.Set("__args", []any{c})
|
||||
executor.Set("__args", []any{e})
|
||||
res, err := executor.RunProgram(pr)
|
||||
executor.Set("__args", goja.Undefined())
|
||||
|
||||
@@ -240,29 +210,44 @@ func wrapHandler(executors *vmsPool, handler goja.Value) (echo.HandlerFunc, erro
|
||||
}
|
||||
}
|
||||
|
||||
func wrapMiddlewares(executors *vmsPool, rawMiddlewares ...goja.Value) ([]echo.MiddlewareFunc, error) {
|
||||
wrappedMiddlewares := make([]echo.MiddlewareFunc, len(rawMiddlewares))
|
||||
type gojaHookHandler struct {
|
||||
priority int
|
||||
id string
|
||||
serializedFunc string
|
||||
}
|
||||
|
||||
func wrapMiddlewares(executors *vmsPool, rawMiddlewares ...goja.Value) ([]*hook.Handler[*core.RequestEvent], error) {
|
||||
wrappedMiddlewares := make([]*hook.Handler[*core.RequestEvent], len(rawMiddlewares))
|
||||
|
||||
for i, m := range rawMiddlewares {
|
||||
if m == nil {
|
||||
return nil, errors.New("middleware func must be non-nil")
|
||||
return nil, errors.New("middleware must be non-nil")
|
||||
}
|
||||
|
||||
switch v := m.Export().(type) {
|
||||
case echo.MiddlewareFunc:
|
||||
// "native" middleware - no need to wrap
|
||||
case *hook.Handler[*core.RequestEvent]:
|
||||
// "native" middleware handler - 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)
|
||||
case hook.HandlerFunc[*core.RequestEvent]:
|
||||
// "native" middleware func - wrap as handler
|
||||
wrappedMiddlewares[i] = &hook.Handler[*core.RequestEvent]{
|
||||
Func: v,
|
||||
}
|
||||
case *gojaHookHandler:
|
||||
if v.serializedFunc == "" {
|
||||
return nil, errors.New("missing or invalid Middleware function")
|
||||
}
|
||||
|
||||
wrappedMiddlewares[i] = func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
pr := goja.MustCompile("", "{("+v.serializedFunc+").apply(undefined, __args)}", true)
|
||||
|
||||
wrappedMiddlewares[i] = &hook.Handler[*core.RequestEvent]{
|
||||
Id: v.id,
|
||||
Priority: v.priority,
|
||||
Func: func(e *core.RequestEvent) error {
|
||||
return executors.run(func(executor *goja.Runtime) error {
|
||||
executor.Set("__args", []any{next})
|
||||
executor.Set("__args2", []any{c})
|
||||
executor.Set("__args", []any{e})
|
||||
res, err := executor.RunProgram(pr)
|
||||
executor.Set("__args", goja.Undefined())
|
||||
executor.Set("__args2", goja.Undefined())
|
||||
|
||||
// check for returned error
|
||||
if res != nil {
|
||||
@@ -273,7 +258,28 @@ func wrapMiddlewares(executors *vmsPool, rawMiddlewares ...goja.Value) ([]echo.M
|
||||
|
||||
return err
|
||||
})
|
||||
}
|
||||
},
|
||||
}
|
||||
case func(goja.FunctionCall) goja.Value, string:
|
||||
pr := goja.MustCompile("", "{("+m.String()+").apply(undefined, __args)}", true)
|
||||
|
||||
wrappedMiddlewares[i] = &hook.Handler[*core.RequestEvent]{
|
||||
Func: func(e *core.RequestEvent) error {
|
||||
return executors.run(func(executor *goja.Runtime) error {
|
||||
executor.Set("__args", []any{e})
|
||||
res, err := executor.RunProgram(pr)
|
||||
executor.Set("__args", goja.Undefined())
|
||||
|
||||
// check for returned error
|
||||
if res != nil {
|
||||
if v, ok := res.Export().(error); ok {
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
})
|
||||
},
|
||||
}
|
||||
default:
|
||||
return nil, errors.New("unsupported goja middleware type")
|
||||
@@ -286,9 +292,10 @@ func wrapMiddlewares(executors *vmsPool, rawMiddlewares ...goja.Value) ([]echo.M
|
||||
func baseBinds(vm *goja.Runtime) {
|
||||
vm.SetFieldNameMapper(FieldMapper{})
|
||||
|
||||
// deprecated: use toString
|
||||
vm.Set("readerToString", func(r io.Reader, maxBytes int) (string, error) {
|
||||
if maxBytes == 0 {
|
||||
maxBytes = rest.DefaultMaxMemory
|
||||
maxBytes = router.DefaultMaxMemory
|
||||
}
|
||||
|
||||
limitReader := io.LimitReader(r, int64(maxBytes))
|
||||
@@ -301,6 +308,34 @@ func baseBinds(vm *goja.Runtime) {
|
||||
return string(bodyBytes), nil
|
||||
})
|
||||
|
||||
vm.Set("toString", func(raw any, maxReaderBytes int) (string, error) {
|
||||
switch v := raw.(type) {
|
||||
case io.Reader:
|
||||
if maxReaderBytes == 0 {
|
||||
maxReaderBytes = router.DefaultMaxMemory
|
||||
}
|
||||
|
||||
limitReader := io.LimitReader(v, int64(maxReaderBytes))
|
||||
|
||||
bodyBytes, readErr := io.ReadAll(limitReader)
|
||||
if readErr != nil {
|
||||
return "", readErr
|
||||
}
|
||||
|
||||
return string(bodyBytes), nil
|
||||
default:
|
||||
str, err := cast.ToStringE(v)
|
||||
if err == nil {
|
||||
return str, nil
|
||||
}
|
||||
|
||||
// as a last attempt try to json encode the value
|
||||
rawBytes, _ := json.Marshal(raw)
|
||||
|
||||
return string(rawBytes), nil
|
||||
}
|
||||
})
|
||||
|
||||
vm.Set("sleep", func(milliseconds int64) {
|
||||
time.Sleep(time.Duration(milliseconds) * time.Millisecond)
|
||||
})
|
||||
@@ -313,6 +348,15 @@ func baseBinds(vm *goja.Runtime) {
|
||||
return elem.Addr().Interface()
|
||||
})
|
||||
|
||||
vm.Set("unmarshal", func(data, dst any) error {
|
||||
raw, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return json.Unmarshal(raw, &dst)
|
||||
})
|
||||
|
||||
vm.Set("DynamicModel", func(call goja.ConstructorCall) *goja.Object {
|
||||
shape, ok := call.Argument(0).Export().(map[string]any)
|
||||
if !ok || len(shape) == 0 {
|
||||
@@ -327,17 +371,17 @@ func baseBinds(vm *goja.Runtime) {
|
||||
})
|
||||
|
||||
vm.Set("Record", func(call goja.ConstructorCall) *goja.Object {
|
||||
var instance *models.Record
|
||||
var instance *core.Record
|
||||
|
||||
collection, ok := call.Argument(0).Export().(*models.Collection)
|
||||
collection, ok := call.Argument(0).Export().(*core.Collection)
|
||||
if ok {
|
||||
instance = models.NewRecord(collection)
|
||||
instance = core.NewRecord(collection)
|
||||
data, ok := call.Argument(1).Export().(map[string]any)
|
||||
if ok {
|
||||
instance.Load(data)
|
||||
}
|
||||
} else {
|
||||
instance = &models.Record{}
|
||||
instance = &core.Record{}
|
||||
}
|
||||
|
||||
instanceValue := vm.ToValue(instance).(*goja.Object)
|
||||
@@ -347,24 +391,91 @@ func baseBinds(vm *goja.Runtime) {
|
||||
})
|
||||
|
||||
vm.Set("Collection", func(call goja.ConstructorCall) *goja.Object {
|
||||
instance := &models.Collection{}
|
||||
instance := &core.Collection{}
|
||||
return structConstructorUnmarshal(vm, call, instance)
|
||||
})
|
||||
registerFactoryAsConstructor(vm, "BaseCollection", core.NewBaseCollection)
|
||||
registerFactoryAsConstructor(vm, "AuthCollection", core.NewAuthCollection)
|
||||
registerFactoryAsConstructor(vm, "ViewCollection", core.NewViewCollection)
|
||||
|
||||
vm.Set("FieldsList", func(call goja.ConstructorCall) *goja.Object {
|
||||
instance := &core.FieldsList{}
|
||||
return structConstructorUnmarshal(vm, call, instance)
|
||||
})
|
||||
|
||||
vm.Set("Admin", func(call goja.ConstructorCall) *goja.Object {
|
||||
instance := &models.Admin{}
|
||||
return structConstructorUnmarshal(vm, call, instance)
|
||||
})
|
||||
// fields
|
||||
// ---
|
||||
vm.Set("Field", func(call goja.ConstructorCall) *goja.Object {
|
||||
data, _ := call.Argument(0).Export().(map[string]any)
|
||||
rawDataSlice, _ := json.Marshal([]any{data})
|
||||
|
||||
vm.Set("Schema", func(call goja.ConstructorCall) *goja.Object {
|
||||
instance := &schema.Schema{}
|
||||
return structConstructorUnmarshal(vm, call, instance)
|
||||
})
|
||||
fieldsList := core.NewFieldsList()
|
||||
_ = fieldsList.UnmarshalJSON(rawDataSlice)
|
||||
|
||||
vm.Set("SchemaField", func(call goja.ConstructorCall) *goja.Object {
|
||||
instance := &schema.SchemaField{}
|
||||
if len(fieldsList) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
field := fieldsList[0]
|
||||
|
||||
fieldValue := vm.ToValue(field).(*goja.Object)
|
||||
fieldValue.SetPrototype(call.This.Prototype())
|
||||
|
||||
return fieldValue
|
||||
})
|
||||
vm.Set("NumberField", func(call goja.ConstructorCall) *goja.Object {
|
||||
instance := &core.NumberField{}
|
||||
return structConstructorUnmarshal(vm, call, instance)
|
||||
})
|
||||
vm.Set("BoolField", func(call goja.ConstructorCall) *goja.Object {
|
||||
instance := &core.BoolField{}
|
||||
return structConstructorUnmarshal(vm, call, instance)
|
||||
})
|
||||
vm.Set("TextField", func(call goja.ConstructorCall) *goja.Object {
|
||||
instance := &core.TextField{}
|
||||
return structConstructorUnmarshal(vm, call, instance)
|
||||
})
|
||||
vm.Set("URLField", func(call goja.ConstructorCall) *goja.Object {
|
||||
instance := &core.URLField{}
|
||||
return structConstructorUnmarshal(vm, call, instance)
|
||||
})
|
||||
vm.Set("EmailField", func(call goja.ConstructorCall) *goja.Object {
|
||||
instance := &core.EmailField{}
|
||||
return structConstructorUnmarshal(vm, call, instance)
|
||||
})
|
||||
vm.Set("EditorField", func(call goja.ConstructorCall) *goja.Object {
|
||||
instance := &core.EditorField{}
|
||||
return structConstructorUnmarshal(vm, call, instance)
|
||||
})
|
||||
vm.Set("PasswordField", func(call goja.ConstructorCall) *goja.Object {
|
||||
instance := &core.PasswordField{}
|
||||
return structConstructorUnmarshal(vm, call, instance)
|
||||
})
|
||||
vm.Set("DateField", func(call goja.ConstructorCall) *goja.Object {
|
||||
instance := &core.DateField{}
|
||||
return structConstructorUnmarshal(vm, call, instance)
|
||||
})
|
||||
vm.Set("AutodateField", func(call goja.ConstructorCall) *goja.Object {
|
||||
instance := &core.AutodateField{}
|
||||
return structConstructorUnmarshal(vm, call, instance)
|
||||
})
|
||||
vm.Set("JSONField", func(call goja.ConstructorCall) *goja.Object {
|
||||
instance := &core.JSONField{}
|
||||
return structConstructorUnmarshal(vm, call, instance)
|
||||
})
|
||||
vm.Set("RelationField", func(call goja.ConstructorCall) *goja.Object {
|
||||
instance := &core.RelationField{}
|
||||
return structConstructorUnmarshal(vm, call, instance)
|
||||
})
|
||||
vm.Set("SelectField", func(call goja.ConstructorCall) *goja.Object {
|
||||
instance := &core.SelectField{}
|
||||
return structConstructorUnmarshal(vm, call, instance)
|
||||
})
|
||||
vm.Set("FileField", func(call goja.ConstructorCall) *goja.Object {
|
||||
instance := &core.FileField{}
|
||||
return structConstructorUnmarshal(vm, call, instance)
|
||||
})
|
||||
// ---
|
||||
|
||||
vm.Set("MailerMessage", func(call goja.ConstructorCall) *goja.Object {
|
||||
instance := &mailer.Message{}
|
||||
@@ -377,10 +488,28 @@ func baseBinds(vm *goja.Runtime) {
|
||||
})
|
||||
|
||||
vm.Set("RequestInfo", func(call goja.ConstructorCall) *goja.Object {
|
||||
instance := &models.RequestInfo{Context: models.RequestInfoContextDefault}
|
||||
instance := &core.RequestInfo{Context: core.RequestInfoContextDefault}
|
||||
return structConstructor(vm, call, instance)
|
||||
})
|
||||
|
||||
// ```js
|
||||
// new Middleware((e) => {
|
||||
// return e.next()
|
||||
// }, 100, "example_middleware")
|
||||
// ```
|
||||
vm.Set("Middleware", func(call goja.ConstructorCall) *goja.Object {
|
||||
instance := &gojaHookHandler{}
|
||||
|
||||
instance.serializedFunc = call.Argument(0).String()
|
||||
instance.priority = cast.ToInt(call.Argument(1).Export())
|
||||
instance.id = cast.ToString(call.Argument(2).Export())
|
||||
|
||||
instanceValue := vm.ToValue(instance).(*goja.Object)
|
||||
instanceValue.SetPrototype(call.This.Prototype())
|
||||
|
||||
return instanceValue
|
||||
})
|
||||
|
||||
vm.Set("DateTime", func(call goja.ConstructorCall) *goja.Object {
|
||||
instance := types.NowDateTime()
|
||||
|
||||
@@ -406,24 +535,6 @@ func baseBinds(vm *goja.Runtime) {
|
||||
return instanceValue
|
||||
})
|
||||
|
||||
vm.Set("Dao", func(call goja.ConstructorCall) *goja.Object {
|
||||
concurrentDB, _ := call.Argument(0).Export().(dbx.Builder)
|
||||
if concurrentDB == nil {
|
||||
panic("[Dao] missing required Dao(concurrentDB, [nonconcurrentDB]) argument")
|
||||
}
|
||||
|
||||
nonConcurrentDB, _ := call.Argument(1).Export().(dbx.Builder)
|
||||
if nonConcurrentDB == nil {
|
||||
nonConcurrentDB = concurrentDB
|
||||
}
|
||||
|
||||
instance := daos.NewMultiDB(concurrentDB, nonConcurrentDB)
|
||||
instanceValue := vm.ToValue(instance).(*goja.Object)
|
||||
instanceValue.SetPrototype(call.This.Prototype())
|
||||
|
||||
return instanceValue
|
||||
})
|
||||
|
||||
vm.Set("Cookie", func(call goja.ConstructorCall) *goja.Object {
|
||||
instance := &http.Cookie{}
|
||||
return structConstructor(vm, call, instance)
|
||||
@@ -462,30 +573,10 @@ func mailsBinds(vm *goja.Runtime) {
|
||||
obj := vm.NewObject()
|
||||
vm.Set("$mails", obj)
|
||||
|
||||
// admin
|
||||
obj.Set("sendAdminPasswordReset", mails.SendAdminPasswordReset)
|
||||
|
||||
// record
|
||||
obj.Set("sendRecordPasswordReset", mails.SendRecordPasswordReset)
|
||||
obj.Set("sendRecordVerification", mails.SendRecordVerification)
|
||||
obj.Set("sendRecordChangeEmail", mails.SendRecordChangeEmail)
|
||||
}
|
||||
|
||||
func tokensBinds(vm *goja.Runtime) {
|
||||
obj := vm.NewObject()
|
||||
vm.Set("$tokens", obj)
|
||||
|
||||
// admin
|
||||
obj.Set("adminAuthToken", tokens.NewAdminAuthToken)
|
||||
obj.Set("adminResetPasswordToken", tokens.NewAdminResetPasswordToken)
|
||||
obj.Set("adminFileToken", tokens.NewAdminFileToken)
|
||||
|
||||
// record
|
||||
obj.Set("recordAuthToken", tokens.NewRecordAuthToken)
|
||||
obj.Set("recordVerifyToken", tokens.NewRecordVerifyToken)
|
||||
obj.Set("recordResetPasswordToken", tokens.NewRecordResetPasswordToken)
|
||||
obj.Set("recordChangeEmailToken", tokens.NewRecordChangeEmailToken)
|
||||
obj.Set("recordFileToken", tokens.NewRecordFileToken)
|
||||
obj.Set("sendRecordOTP", mails.SendRecordOTP)
|
||||
}
|
||||
|
||||
func securityBinds(vm *goja.Runtime) {
|
||||
@@ -502,6 +593,7 @@ func securityBinds(vm *goja.Runtime) {
|
||||
|
||||
// random
|
||||
obj.Set("randomString", security.RandomString)
|
||||
obj.Set("randomStringByRegex", security.RandomStringByRegex)
|
||||
obj.Set("randomStringWithAlphabet", security.RandomStringWithAlphabet)
|
||||
obj.Set("pseudorandomString", security.PseudorandomString)
|
||||
obj.Set("pseudorandomStringWithAlphabet", security.PseudorandomStringWithAlphabet)
|
||||
@@ -513,7 +605,9 @@ func securityBinds(vm *goja.Runtime) {
|
||||
obj.Set("parseJWT", func(token string, verificationKey string) (map[string]any, error) {
|
||||
return security.ParseJWT(token, verificationKey)
|
||||
})
|
||||
obj.Set("createJWT", security.NewJWT)
|
||||
obj.Set("createJWT", func(payload jwt.MapClaims, signingKey string, secDuration int) (string, error) {
|
||||
return security.NewJWT(payload, signingKey, time.Duration(secDuration)*time.Second)
|
||||
})
|
||||
|
||||
// encryption
|
||||
obj.Set("encrypt", security.Encrypt)
|
||||
@@ -535,7 +629,7 @@ func filesystemBinds(vm *goja.Runtime) {
|
||||
obj.Set("fileFromPath", filesystem.NewFileFromPath)
|
||||
obj.Set("fileFromBytes", filesystem.NewFileFromBytes)
|
||||
obj.Set("fileFromMultipart", filesystem.NewFileFromMultipart)
|
||||
obj.Set("fileFromUrl", func(url string, secTimeout int) (*filesystem.File, error) {
|
||||
obj.Set("fileFromURL", func(url string, secTimeout int) (*filesystem.File, error) {
|
||||
if secTimeout == 0 {
|
||||
secTimeout = 120
|
||||
}
|
||||
@@ -543,7 +637,7 @@ func filesystemBinds(vm *goja.Runtime) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), time.Duration(secTimeout)*time.Second)
|
||||
defer cancel()
|
||||
|
||||
return filesystem.NewFileFromUrl(ctx, url)
|
||||
return filesystem.NewFileFromURL(ctx, url)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -592,24 +686,8 @@ func osBinds(vm *goja.Runtime) {
|
||||
}
|
||||
|
||||
func formsBinds(vm *goja.Runtime) {
|
||||
registerFactoryAsConstructor(vm, "AdminLoginForm", forms.NewAdminLogin)
|
||||
registerFactoryAsConstructor(vm, "AdminPasswordResetConfirmForm", forms.NewAdminPasswordResetConfirm)
|
||||
registerFactoryAsConstructor(vm, "AdminPasswordResetRequestForm", forms.NewAdminPasswordResetRequest)
|
||||
registerFactoryAsConstructor(vm, "AdminUpsertForm", forms.NewAdminUpsert)
|
||||
registerFactoryAsConstructor(vm, "AppleClientSecretCreateForm", forms.NewAppleClientSecretCreate)
|
||||
registerFactoryAsConstructor(vm, "CollectionUpsertForm", forms.NewCollectionUpsert)
|
||||
registerFactoryAsConstructor(vm, "CollectionsImportForm", forms.NewCollectionsImport)
|
||||
registerFactoryAsConstructor(vm, "RealtimeSubscribeForm", forms.NewRealtimeSubscribe)
|
||||
registerFactoryAsConstructor(vm, "RecordEmailChangeConfirmForm", forms.NewRecordEmailChangeConfirm)
|
||||
registerFactoryAsConstructor(vm, "RecordEmailChangeRequestForm", forms.NewRecordEmailChangeRequest)
|
||||
registerFactoryAsConstructor(vm, "RecordOAuth2LoginForm", forms.NewRecordOAuth2Login)
|
||||
registerFactoryAsConstructor(vm, "RecordPasswordLoginForm", forms.NewRecordPasswordLogin)
|
||||
registerFactoryAsConstructor(vm, "RecordPasswordResetConfirmForm", forms.NewRecordPasswordResetConfirm)
|
||||
registerFactoryAsConstructor(vm, "RecordPasswordResetRequestForm", forms.NewRecordPasswordResetRequest)
|
||||
registerFactoryAsConstructor(vm, "RecordUpsertForm", forms.NewRecordUpsert)
|
||||
registerFactoryAsConstructor(vm, "RecordVerificationConfirmForm", forms.NewRecordVerificationConfirm)
|
||||
registerFactoryAsConstructor(vm, "RecordVerificationRequestForm", forms.NewRecordVerificationRequest)
|
||||
registerFactoryAsConstructor(vm, "SettingsUpsertForm", forms.NewSettingsUpsert)
|
||||
registerFactoryAsConstructor(vm, "TestEmailSendForm", forms.NewTestEmailSend)
|
||||
registerFactoryAsConstructor(vm, "TestS3FilesystemForm", forms.NewTestS3Filesystem)
|
||||
}
|
||||
@@ -618,33 +696,33 @@ func apisBinds(vm *goja.Runtime) {
|
||||
obj := vm.NewObject()
|
||||
vm.Set("$apis", obj)
|
||||
|
||||
obj.Set("staticDirectoryHandler", func(dir string, indexFallback bool) echo.HandlerFunc {
|
||||
return apis.StaticDirectoryHandler(os.DirFS(dir), indexFallback)
|
||||
obj.Set("static", func(dir string, indexFallback bool) hook.HandlerFunc[*core.RequestEvent] {
|
||||
return apis.Static(os.DirFS(dir), indexFallback)
|
||||
})
|
||||
|
||||
// middlewares
|
||||
obj.Set("requireGuestOnly", apis.RequireGuestOnly)
|
||||
obj.Set("requireRecordAuth", apis.RequireRecordAuth)
|
||||
obj.Set("requireAdminAuth", apis.RequireAdminAuth)
|
||||
obj.Set("requireAdminAuthOnlyIfAny", apis.RequireAdminAuthOnlyIfAny)
|
||||
obj.Set("requireAdminOrRecordAuth", apis.RequireAdminOrRecordAuth)
|
||||
obj.Set("requireAdminOrOwnerAuth", apis.RequireAdminOrOwnerAuth)
|
||||
obj.Set("activityLogger", apis.ActivityLogger)
|
||||
obj.Set("gzip", middleware.Gzip)
|
||||
obj.Set("bodyLimit", middleware.BodyLimit)
|
||||
obj.Set("requireAuth", apis.RequireAuth)
|
||||
obj.Set("requireSuperuserAuth", apis.RequireSuperuserAuth)
|
||||
obj.Set("requireSuperuserAuthOnlyIfAny", apis.RequireSuperuserAuthOnlyIfAny)
|
||||
obj.Set("requireSuperuserOrOwnerAuth", apis.RequireSuperuserOrOwnerAuth)
|
||||
obj.Set("skipSuccessActivityLog", apis.SkipSuccessActivityLog)
|
||||
obj.Set("gzip", apis.Gzip)
|
||||
obj.Set("bodyLimit", apis.BodyLimit)
|
||||
|
||||
// record helpers
|
||||
obj.Set("requestInfo", apis.RequestInfo)
|
||||
obj.Set("recordAuthResponse", apis.RecordAuthResponse)
|
||||
obj.Set("enrichRecord", apis.EnrichRecord)
|
||||
obj.Set("enrichRecords", apis.EnrichRecords)
|
||||
|
||||
// api errors
|
||||
registerFactoryAsConstructor(vm, "ApiError", apis.NewApiError)
|
||||
registerFactoryAsConstructor(vm, "NotFoundError", apis.NewNotFoundError)
|
||||
registerFactoryAsConstructor(vm, "BadRequestError", apis.NewBadRequestError)
|
||||
registerFactoryAsConstructor(vm, "ForbiddenError", apis.NewForbiddenError)
|
||||
registerFactoryAsConstructor(vm, "UnauthorizedError", apis.NewUnauthorizedError)
|
||||
registerFactoryAsConstructor(vm, "ApiError", router.NewApiError)
|
||||
registerFactoryAsConstructor(vm, "NotFoundError", router.NewNotFoundError)
|
||||
registerFactoryAsConstructor(vm, "BadRequestError", router.NewBadRequestError)
|
||||
registerFactoryAsConstructor(vm, "ForbiddenError", router.NewForbiddenError)
|
||||
registerFactoryAsConstructor(vm, "UnauthorizedError", router.NewUnauthorizedError)
|
||||
registerFactoryAsConstructor(vm, "TooManyRequestsError", router.NewTooManyRequestsError)
|
||||
registerFactoryAsConstructor(vm, "InternalServerError", router.NewInternalServerError)
|
||||
}
|
||||
|
||||
func httpClientBinds(vm *goja.Runtime) {
|
||||
@@ -661,7 +739,7 @@ func httpClientBinds(vm *goja.Runtime) {
|
||||
})
|
||||
|
||||
type sendResult struct {
|
||||
Json any `json:"json"`
|
||||
JSON any `json:"json"`
|
||||
Headers map[string][]string `json:"headers"`
|
||||
Cookies map[string]*http.Cookie `json:"cookies"`
|
||||
Raw string `json:"raw"`
|
||||
@@ -727,6 +805,8 @@ func httpClientBinds(vm *goja.Runtime) {
|
||||
reqBody = bytes.NewReader(encoded)
|
||||
} else {
|
||||
switch v := config.Body.(type) {
|
||||
case io.Reader:
|
||||
reqBody = v
|
||||
case FormData:
|
||||
body, mp, err := v.toMultipart()
|
||||
if err != nil {
|
||||
@@ -755,13 +835,6 @@ func httpClientBinds(vm *goja.Runtime) {
|
||||
req.Header.Set("content-type", contentType)
|
||||
}
|
||||
|
||||
// @todo consider removing during the refactoring
|
||||
//
|
||||
// fallback to json content-type
|
||||
if req.Header.Get("content-type") == "" {
|
||||
req.Header.Set("content-type", "application/json")
|
||||
}
|
||||
|
||||
res, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -787,12 +860,12 @@ func httpClientBinds(vm *goja.Runtime) {
|
||||
|
||||
if len(result.Raw) != 0 {
|
||||
// try as map
|
||||
result.Json = map[string]any{}
|
||||
if err := json.Unmarshal(bodyRaw, &result.Json); err != nil {
|
||||
result.JSON = map[string]any{}
|
||||
if err := json.Unmarshal(bodyRaw, &result.JSON); err != nil {
|
||||
// try as slice
|
||||
result.Json = []any{}
|
||||
if err := json.Unmarshal(bodyRaw, &result.Json); err != nil {
|
||||
result.Json = nil
|
||||
result.JSON = []any{}
|
||||
if err := json.Unmarshal(bodyRaw, &result.JSON); err != nil {
|
||||
result.JSON = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -864,7 +937,7 @@ func structConstructor(vm *goja.Runtime, call goja.ConstructorCall, instance any
|
||||
func structConstructorUnmarshal(vm *goja.Runtime, call goja.ConstructorCall, instance any) *goja.Object {
|
||||
if data := call.Argument(0).Export(); data != nil {
|
||||
if raw, err := json.Marshal(data); err == nil {
|
||||
json.Unmarshal(raw, instance)
|
||||
_ = json.Unmarshal(raw, instance)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -893,13 +966,13 @@ func newDynamicModel(shape map[string]any) any {
|
||||
switch kind := vt.Kind(); kind {
|
||||
case reflect.Map:
|
||||
raw, _ := json.Marshal(v)
|
||||
newV := types.JsonMap{}
|
||||
newV := types.JSONMap[any]{}
|
||||
newV.Scan(raw)
|
||||
v = newV
|
||||
vt = reflect.TypeOf(v)
|
||||
case reflect.Slice, reflect.Array:
|
||||
raw, _ := json.Marshal(v)
|
||||
newV := types.JsonArray[any]{}
|
||||
newV := types.JSONArray[any]{}
|
||||
newV.Scan(raw)
|
||||
v = newV
|
||||
vt = reflect.TypeOf(newV)
|
||||
|
||||
+315
-298
File diff suppressed because it is too large
Load Diff
@@ -84,9 +84,7 @@ func (data FormData) Values() []any {
|
||||
result := make([]any, 0, len(data))
|
||||
|
||||
for _, values := range data {
|
||||
for _, v := range values {
|
||||
result = append(result, v)
|
||||
}
|
||||
result = append(result, values...)
|
||||
}
|
||||
|
||||
return result
|
||||
|
||||
+10484
-9895
File diff suppressed because it is too large
Load Diff
@@ -71,9 +71,9 @@ declare function cronRemove(jobId: string): void;
|
||||
* Example:
|
||||
*
|
||||
* ` + "```" + `js
|
||||
* routerAdd("GET", "/hello", (c) => {
|
||||
* return c.json(200, {"message": "Hello!"})
|
||||
* }, $apis.requireAdminOrRecordAuth())
|
||||
* routerAdd("GET", "/hello", (e) => {
|
||||
* return e.json(200, {"message": "Hello!"})
|
||||
* }, $apis.requireAuth())
|
||||
* ` + "```" + `
|
||||
*
|
||||
* _Note that this method is available only in pb_hooks context._
|
||||
@@ -83,8 +83,8 @@ declare function cronRemove(jobId: string): void;
|
||||
declare function routerAdd(
|
||||
method: string,
|
||||
path: string,
|
||||
handler: echo.HandlerFunc,
|
||||
...middlewares: Array<string|echo.MiddlewareFunc>,
|
||||
handler: (e: core.RequestEvent) => void,
|
||||
...middlewares: Array<string|((e: core.RequestEvent) => void)|Middleware>,
|
||||
): void;
|
||||
|
||||
/**
|
||||
@@ -94,11 +94,9 @@ declare function routerAdd(
|
||||
* Example:
|
||||
*
|
||||
* ` + "```" + `js
|
||||
* routerUse((next) => {
|
||||
* return (c) => {
|
||||
* console.log(c.path())
|
||||
* return next(c)
|
||||
* }
|
||||
* routerUse((e) => {
|
||||
* console.log(e.request.url.path)
|
||||
* return e.next()
|
||||
* })
|
||||
* ` + "```" + `
|
||||
*
|
||||
@@ -106,34 +104,7 @@ declare function routerAdd(
|
||||
*
|
||||
* @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;
|
||||
declare function routerUse(...middlewares: Array<string|((e: core.RequestEvent) => void)|Middleware): void;
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// baseBinds
|
||||
@@ -151,7 +122,7 @@ declare var __hooks: string
|
||||
//
|
||||
// See https://www.typescriptlang.org/docs/handbook/2/mapped-types.html#key-remapping-via-as
|
||||
type excludeHooks<Type> = {
|
||||
[Property in keyof Type as Exclude<Property, ` + "`on${string}`" + `>]: Type[Property]
|
||||
[Property in keyof Type as Exclude<Property, ` + "`on${string}`" + `|'cron'>]: Type[Property]
|
||||
};
|
||||
|
||||
// core.App without the on* hook methods
|
||||
@@ -194,22 +165,34 @@ declare var $app: PocketBase
|
||||
declare var $template: template.Registry
|
||||
|
||||
/**
|
||||
* readerToString reads the content of the specified io.Reader until
|
||||
* EOF or maxBytes are reached.
|
||||
* This method is superseded by toString.
|
||||
*
|
||||
* If maxBytes is not specified it will read up to 32MB.
|
||||
* @deprecated
|
||||
* @group PocketBase
|
||||
*/
|
||||
declare function readerToString(reader: any, maxBytes?: number): string;
|
||||
|
||||
/**
|
||||
* toString stringifies the specified value.
|
||||
*
|
||||
* Note that after this call the reader can't be used anymore.
|
||||
* Support optional second maxBytes argument to limit the max read bytes
|
||||
* when the value is a io.Reader (default to 32MB).
|
||||
*
|
||||
* Types that don't have explicit string representation are json serialized.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ` + "```" + `js
|
||||
* const rawBody = readerToString(c.request().body)
|
||||
* // io.Reader
|
||||
* const ex1 = toString(e.request.body)
|
||||
*
|
||||
* // slice of bytes ("hello")
|
||||
* const ex2 = toString([104 101 108 108 111])
|
||||
* ` + "```" + `
|
||||
*
|
||||
* @group PocketBase
|
||||
*/
|
||||
declare function readerToString(reader: any, maxBytes?: number): string;
|
||||
declare function toString(val: any, maxBytes?: number): string;
|
||||
|
||||
/**
|
||||
* sleep pauses the current goroutine for at least the specified user duration (in ms).
|
||||
@@ -244,15 +227,18 @@ declare function arrayOf<T>(model: T): Array<T>;
|
||||
/**
|
||||
* DynamicModel creates a new dynamic model with fields from the provided data shape.
|
||||
*
|
||||
* Note that in order to use 0 as double/float initialization number you have to use negative zero (` + "`-0`" + `).
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ` + "```" + `js
|
||||
* const model = new DynamicModel({
|
||||
* name: ""
|
||||
* age: 0,
|
||||
* active: false,
|
||||
* roles: [],
|
||||
* meta: {}
|
||||
* name: ""
|
||||
* age: 0, // int64
|
||||
* totalSpent: -0, // float64
|
||||
* active: false,
|
||||
* roles: [],
|
||||
* meta: {}
|
||||
* })
|
||||
* ` + "```" + `
|
||||
*
|
||||
@@ -279,12 +265,12 @@ declare class DynamicModel {
|
||||
* @group PocketBase
|
||||
*/
|
||||
declare const Record: {
|
||||
new(collection?: models.Collection, data?: { [key:string]: any }): models.Record
|
||||
new(collection?: core.Collection, data?: { [key:string]: any }): core.Record
|
||||
|
||||
// note: declare as "newable" const due to conflict with the Record TS utility type
|
||||
}
|
||||
|
||||
interface Collection extends models.Collection{} // merge
|
||||
interface Collection extends core.Collection{} // merge
|
||||
/**
|
||||
* Collection model class.
|
||||
*
|
||||
@@ -295,12 +281,13 @@ interface Collection extends models.Collection{} // merge
|
||||
* listRule: "@request.auth.id != '' || status = 'public'",
|
||||
* viewRule: "@request.auth.id != '' || status = 'public'",
|
||||
* deleteRule: "@request.auth.id != ''",
|
||||
* schema: [
|
||||
* fields: [
|
||||
* {
|
||||
* name: "title",
|
||||
* type: "text",
|
||||
* required: true,
|
||||
* options: { min: 6, max: 100 },
|
||||
* min: 6,
|
||||
* max: 100,
|
||||
* },
|
||||
* {
|
||||
* name: "description",
|
||||
@@ -312,44 +299,242 @@ interface Collection extends models.Collection{} // merge
|
||||
*
|
||||
* @group PocketBase
|
||||
*/
|
||||
declare class Collection implements models.Collection {
|
||||
constructor(data?: Partial<models.Collection>)
|
||||
declare class Collection implements core.Collection {
|
||||
constructor(data?: Partial<core.Collection>)
|
||||
}
|
||||
|
||||
interface Admin extends models.Admin{} // merge
|
||||
interface BaseCollection extends core.Collection{} // merge
|
||||
/**
|
||||
* Admin model class.
|
||||
* Alias for a "base" collection class.
|
||||
*
|
||||
* ` + "```" + `js
|
||||
* const admin = new Admin()
|
||||
* admin.email = "test@example.com"
|
||||
* admin.setPassword(1234567890)
|
||||
* const collection = new BaseCollection({
|
||||
* name: "article",
|
||||
* listRule: "@request.auth.id != '' || status = 'public'",
|
||||
* viewRule: "@request.auth.id != '' || status = 'public'",
|
||||
* deleteRule: "@request.auth.id != ''",
|
||||
* fields: [
|
||||
* {
|
||||
* name: "title",
|
||||
* type: "text",
|
||||
* required: true,
|
||||
* min: 6,
|
||||
* max: 100,
|
||||
* },
|
||||
* {
|
||||
* name: "description",
|
||||
* type: "text",
|
||||
* },
|
||||
* ]
|
||||
* })
|
||||
* ` + "```" + `
|
||||
*
|
||||
* @group PocketBase
|
||||
*/
|
||||
declare class Admin implements models.Admin {
|
||||
constructor(data?: Partial<models.Admin>)
|
||||
declare class BaseCollection implements core.Collection {
|
||||
constructor(data?: Partial<core.Collection>)
|
||||
}
|
||||
|
||||
interface Schema extends schema.Schema{} // merge
|
||||
interface AuthCollection extends core.Collection{} // merge
|
||||
/**
|
||||
* Schema model class, usually used to define the Collection.schema field.
|
||||
* Alias for an "auth" collection class.
|
||||
*
|
||||
* ` + "```" + `js
|
||||
* const collection = new AuthCollection({
|
||||
* name: "clients",
|
||||
* listRule: "@request.auth.id != '' || status = 'public'",
|
||||
* viewRule: "@request.auth.id != '' || status = 'public'",
|
||||
* deleteRule: "@request.auth.id != ''",
|
||||
* fields: [
|
||||
* {
|
||||
* name: "title",
|
||||
* type: "text",
|
||||
* required: true,
|
||||
* min: 6,
|
||||
* max: 100,
|
||||
* },
|
||||
* {
|
||||
* name: "description",
|
||||
* type: "text",
|
||||
* },
|
||||
* ]
|
||||
* })
|
||||
* ` + "```" + `
|
||||
*
|
||||
* @group PocketBase
|
||||
*/
|
||||
declare class Schema implements schema.Schema {
|
||||
constructor(data?: Partial<schema.Schema>)
|
||||
declare class AuthCollection implements core.Collection {
|
||||
constructor(data?: Partial<core.Collection>)
|
||||
}
|
||||
|
||||
interface SchemaField extends schema.SchemaField{} // merge
|
||||
interface ViewCollection extends core.Collection{} // merge
|
||||
/**
|
||||
* SchemaField model class, usually used as part of the Schema model.
|
||||
* Alias for a "view" collection class.
|
||||
*
|
||||
* ` + "```" + `js
|
||||
* const collection = new ViewCollection({
|
||||
* name: "clients",
|
||||
* listRule: "@request.auth.id != '' || status = 'public'",
|
||||
* viewRule: "@request.auth.id != '' || status = 'public'",
|
||||
* deleteRule: "@request.auth.id != ''",
|
||||
* viewQuery: "SELECT id, title from posts",
|
||||
* })
|
||||
* ` + "```" + `
|
||||
*
|
||||
* @group PocketBase
|
||||
*/
|
||||
declare class SchemaField implements schema.SchemaField {
|
||||
constructor(data?: Partial<schema.SchemaField>)
|
||||
declare class ViewCollection implements core.Collection {
|
||||
constructor(data?: Partial<core.Collection>)
|
||||
}
|
||||
|
||||
interface FieldsList extends core.FieldsList{} // merge
|
||||
/**
|
||||
* FieldsList model class, usually used to define the Collection.fields.
|
||||
*
|
||||
* @group PocketBase
|
||||
*/
|
||||
declare class FieldsList implements core.FieldsList {
|
||||
constructor(data?: Partial<core.FieldsList>)
|
||||
}
|
||||
|
||||
interface Field extends core.Field{} // merge
|
||||
/**
|
||||
* Field model class, usually used as part of the FieldsList model.
|
||||
*
|
||||
* @group PocketBase
|
||||
*/
|
||||
declare class Field implements core.Field {
|
||||
constructor(data?: Partial<core.Field>)
|
||||
}
|
||||
|
||||
interface NumberField extends core.NumberField{} // merge
|
||||
/**
|
||||
* NumberField class defines a single "number" collection field.
|
||||
*
|
||||
* @group PocketBase
|
||||
*/
|
||||
declare class NumberField implements core.NumberField {
|
||||
constructor(data?: Partial<core.NumberField>)
|
||||
}
|
||||
|
||||
interface BoolField extends core.BoolField{} // merge
|
||||
/**
|
||||
* BoolField class defines a single "bool" collection field.
|
||||
*
|
||||
* @group PocketBase
|
||||
*/
|
||||
declare class BoolField implements core.BoolField {
|
||||
constructor(data?: Partial<core.BoolField>)
|
||||
}
|
||||
|
||||
interface TextField extends core.TextField{} // merge
|
||||
/**
|
||||
* TextField class defines a single "text" collection field.
|
||||
*
|
||||
* @group PocketBase
|
||||
*/
|
||||
declare class TextField implements core.TextField {
|
||||
constructor(data?: Partial<core.TextField>)
|
||||
}
|
||||
|
||||
interface URLField extends core.URLField{} // merge
|
||||
/**
|
||||
* URLField class defines a single "url" collection field.
|
||||
*
|
||||
* @group PocketBase
|
||||
*/
|
||||
declare class URLField implements core.URLField {
|
||||
constructor(data?: Partial<core.URLField>)
|
||||
}
|
||||
|
||||
interface EmailField extends core.EmailField{} // merge
|
||||
/**
|
||||
* EmailField class defines a single "email" collection field.
|
||||
*
|
||||
* @group PocketBase
|
||||
*/
|
||||
declare class EmailField implements core.EmailField {
|
||||
constructor(data?: Partial<core.EmailField>)
|
||||
}
|
||||
|
||||
interface EditorField extends core.EditorField{} // merge
|
||||
/**
|
||||
* EditorField class defines a single "editor" collection field.
|
||||
*
|
||||
* @group PocketBase
|
||||
*/
|
||||
declare class EditorField implements core.EditorField {
|
||||
constructor(data?: Partial<core.EditorField>)
|
||||
}
|
||||
|
||||
interface PasswordField extends core.PasswordField{} // merge
|
||||
/**
|
||||
* PasswordField class defines a single "password" collection field.
|
||||
*
|
||||
* @group PocketBase
|
||||
*/
|
||||
declare class PasswordField implements core.PasswordField {
|
||||
constructor(data?: Partial<core.PasswordField>)
|
||||
}
|
||||
|
||||
interface DateField extends core.DateField{} // merge
|
||||
/**
|
||||
* DateField class defines a single "date" collection field.
|
||||
*
|
||||
* @group PocketBase
|
||||
*/
|
||||
declare class DateField implements core.DateField {
|
||||
constructor(data?: Partial<core.DateField>)
|
||||
}
|
||||
|
||||
interface AutodateField extends core.AutodateField{} // merge
|
||||
/**
|
||||
* AutodateField class defines a single "autodate" collection field.
|
||||
*
|
||||
* @group PocketBase
|
||||
*/
|
||||
declare class AutodateField implements core.AutodateField {
|
||||
constructor(data?: Partial<core.AutodateField>)
|
||||
}
|
||||
|
||||
interface JSONField extends core.JSONField{} // merge
|
||||
/**
|
||||
* JSONField class defines a single "json" collection field.
|
||||
*
|
||||
* @group PocketBase
|
||||
*/
|
||||
declare class JSONField implements core.JSONField {
|
||||
constructor(data?: Partial<core.JSONField>)
|
||||
}
|
||||
|
||||
interface RelationField extends core.RelationField{} // merge
|
||||
/**
|
||||
* RelationField class defines a single "relation" collection field.
|
||||
*
|
||||
* @group PocketBase
|
||||
*/
|
||||
declare class RelationField implements core.RelationField {
|
||||
constructor(data?: Partial<core.RelationField>)
|
||||
}
|
||||
|
||||
interface SelectField extends core.SelectField{} // merge
|
||||
/**
|
||||
* SelectField class defines a single "select" collection field.
|
||||
*
|
||||
* @group PocketBase
|
||||
*/
|
||||
declare class SelectField implements core.SelectField {
|
||||
constructor(data?: Partial<core.SelectField>)
|
||||
}
|
||||
|
||||
interface FileField extends core.FileField{} // merge
|
||||
/**
|
||||
* FileField class defines a single "file" collection field.
|
||||
*
|
||||
* @group PocketBase
|
||||
*/
|
||||
declare class FileField implements core.FileField {
|
||||
constructor(data?: Partial<core.FileField>)
|
||||
}
|
||||
|
||||
interface MailerMessage extends mailer.Message{} // merge
|
||||
@@ -397,31 +582,55 @@ declare class Command implements cobra.Command {
|
||||
constructor(cmd?: Partial<cobra.Command>)
|
||||
}
|
||||
|
||||
interface RequestInfo extends models.RequestInfo{} // merge
|
||||
interface RequestInfo extends core.RequestInfo{} // merge
|
||||
/**
|
||||
* RequestInfo defines a single models.RequestInfo instance, usually used
|
||||
* RequestInfo defines a single core.RequestInfo instance, usually used
|
||||
* as part of various filter checks.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ` + "```" + `js
|
||||
* const authRecord = $app.dao().findAuthRecordByEmail("users", "test@example.com")
|
||||
* const authRecord = $app.findAuthRecordByEmail("users", "test@example.com")
|
||||
*
|
||||
* const info = new RequestInfo({
|
||||
* authRecord: authRecord,
|
||||
* data: {"name": 123},
|
||||
* headers: {"x-token": "..."},
|
||||
* auth: authRecord,
|
||||
* body: {"name": 123},
|
||||
* headers: {"x-token": "..."},
|
||||
* })
|
||||
*
|
||||
* const record = $app.dao().findFirstRecordByData("articles", "slug", "hello")
|
||||
* const record = $app.findFirstRecordByData("articles", "slug", "hello")
|
||||
*
|
||||
* const canAccess = $app.dao().canAccessRecord(record, info, "@request.auth.id != '' && @request.data.name = 123")
|
||||
* const canAccess = $app.canAccessRecord(record, info, "@request.auth.id != '' && @request.body.name = 123")
|
||||
* ` + "```" + `
|
||||
*
|
||||
* @group PocketBase
|
||||
*/
|
||||
declare class RequestInfo implements models.RequestInfo {
|
||||
constructor(date?: Partial<models.RequestInfo>)
|
||||
declare class RequestInfo implements core.RequestInfo {
|
||||
constructor(info?: Partial<core.RequestInfo>)
|
||||
}
|
||||
|
||||
/**
|
||||
* Middleware defines a single request middleware handler.
|
||||
*
|
||||
* This class is usually used when you want to explicitly specify a priority to your custom route middleware.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ` + "```" + `js
|
||||
* routerUse(new Middleware((e) => {
|
||||
* console.log(e.request.url.path)
|
||||
* return e.next()
|
||||
* }, -10))
|
||||
* ` + "```" + `
|
||||
*
|
||||
* @group PocketBase
|
||||
*/
|
||||
declare class Middleware {
|
||||
constructor(
|
||||
func: string|((e: core.RequestEvent) => void),
|
||||
priority?: number,
|
||||
id?: string,
|
||||
)
|
||||
}
|
||||
|
||||
interface DateTime extends types.DateTime{} // merge
|
||||
@@ -457,15 +666,6 @@ declare class ValidationError implements ozzo_validation.Error {
|
||||
constructor(code?: string, message?: string)
|
||||
}
|
||||
|
||||
interface Dao extends daos.Dao{} // merge
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @group PocketBase
|
||||
*/
|
||||
declare class Dao implements daos.Dao {
|
||||
constructor(concurrentDB?: dbx.Builder, nonconcurrentDB?: dbx.Builder)
|
||||
}
|
||||
|
||||
interface Cookie extends http.Cookie{} // merge
|
||||
/**
|
||||
* A Cookie represents an HTTP cookie as sent in the Set-Cookie header of an
|
||||
@@ -551,44 +751,21 @@ declare namespace $dbx {
|
||||
export let notBetween: dbx.notBetween
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// tokensBinds
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* ` + "`" + `$tokens` + "`" + ` defines high level helpers to generate
|
||||
* various admins and auth records tokens (auth, forgotten password, etc.).
|
||||
*
|
||||
* For more control over the generated token, you can check ` + "`" + `$security` + "`" + `.
|
||||
*
|
||||
* @group PocketBase
|
||||
*/
|
||||
declare namespace $tokens {
|
||||
let adminAuthToken: tokens.newAdminAuthToken
|
||||
let adminResetPasswordToken: tokens.newAdminResetPasswordToken
|
||||
let adminFileToken: tokens.newAdminFileToken
|
||||
let recordAuthToken: tokens.newRecordAuthToken
|
||||
let recordVerifyToken: tokens.newRecordVerifyToken
|
||||
let recordResetPasswordToken: tokens.newRecordResetPasswordToken
|
||||
let recordChangeEmailToken: tokens.newRecordChangeEmailToken
|
||||
let recordFileToken: tokens.newRecordFileToken
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
// mailsBinds
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* ` + "`" + `$mails` + "`" + ` defines helpers to send common
|
||||
* admins and auth records emails like verification, password reset, etc.
|
||||
* auth records emails like verification, password reset, etc.
|
||||
*
|
||||
* @group PocketBase
|
||||
*/
|
||||
declare namespace $mails {
|
||||
let sendAdminPasswordReset: mails.sendAdminPasswordReset
|
||||
let sendRecordPasswordReset: mails.sendRecordPasswordReset
|
||||
let sendRecordVerification: mails.sendRecordVerification
|
||||
let sendRecordChangeEmail: mails.sendRecordChangeEmail
|
||||
let sendRecordOTP: mails.sendRecordOTP
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
@@ -604,6 +781,7 @@ declare namespace $mails {
|
||||
declare namespace $security {
|
||||
let randomString: security.randomString
|
||||
let randomStringWithAlphabet: security.randomStringWithAlphabet
|
||||
let randomStringByRegex: security.randomStringByRegex
|
||||
let pseudorandomString: security.pseudorandomString
|
||||
let pseudorandomStringWithAlphabet: security.pseudorandomStringWithAlphabet
|
||||
let encrypt: security.encrypt
|
||||
@@ -614,7 +792,11 @@ declare namespace $security {
|
||||
let md5: security.md5
|
||||
let sha256: security.sha256
|
||||
let sha512: security.sha512
|
||||
let createJWT: security.newJWT
|
||||
|
||||
/**
|
||||
* {@inheritDoc security.newJWT}
|
||||
*/
|
||||
export function createJWT(payload: { [key:string]: any }, signingKey: string, secDuration: number): string
|
||||
|
||||
/**
|
||||
* {@inheritDoc security.parseUnverifiedJWT}
|
||||
@@ -739,42 +921,6 @@ declare namespace $os {
|
||||
// formsBinds
|
||||
// -------------------------------------------------------------------
|
||||
|
||||
interface AdminLoginForm extends forms.AdminLogin{} // merge
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @group PocketBase
|
||||
*/
|
||||
declare class AdminLoginForm implements forms.AdminLogin {
|
||||
constructor(app: CoreApp)
|
||||
}
|
||||
|
||||
interface AdminPasswordResetConfirmForm extends forms.AdminPasswordResetConfirm{} // merge
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @group PocketBase
|
||||
*/
|
||||
declare class AdminPasswordResetConfirmForm implements forms.AdminPasswordResetConfirm {
|
||||
constructor(app: CoreApp)
|
||||
}
|
||||
|
||||
interface AdminPasswordResetRequestForm extends forms.AdminPasswordResetRequest{} // merge
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @group PocketBase
|
||||
*/
|
||||
declare class AdminPasswordResetRequestForm implements forms.AdminPasswordResetRequest {
|
||||
constructor(app: CoreApp)
|
||||
}
|
||||
|
||||
interface AdminUpsertForm extends forms.AdminUpsert{} // merge
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @group PocketBase
|
||||
*/
|
||||
declare class AdminUpsertForm implements forms.AdminUpsert {
|
||||
constructor(app: CoreApp, admin: models.Admin)
|
||||
}
|
||||
|
||||
interface AppleClientSecretCreateForm extends forms.AppleClientSecretCreate{} // merge
|
||||
/**
|
||||
* @inheritDoc
|
||||
@@ -784,119 +930,13 @@ declare class AppleClientSecretCreateForm implements forms.AppleClientSecretCrea
|
||||
constructor(app: CoreApp)
|
||||
}
|
||||
|
||||
interface CollectionUpsertForm extends forms.CollectionUpsert{} // merge
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @group PocketBase
|
||||
*/
|
||||
declare class CollectionUpsertForm implements forms.CollectionUpsert {
|
||||
constructor(app: CoreApp, collection: models.Collection)
|
||||
}
|
||||
|
||||
interface CollectionsImportForm extends forms.CollectionsImport{} // merge
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @group PocketBase
|
||||
*/
|
||||
declare class CollectionsImportForm implements forms.CollectionsImport {
|
||||
constructor(app: CoreApp)
|
||||
}
|
||||
|
||||
interface RealtimeSubscribeForm extends forms.RealtimeSubscribe{} // merge
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @group PocketBase
|
||||
*/
|
||||
declare class RealtimeSubscribeForm implements forms.RealtimeSubscribe {}
|
||||
|
||||
interface RecordEmailChangeConfirmForm extends forms.RecordEmailChangeConfirm{} // merge
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @group PocketBase
|
||||
*/
|
||||
declare class RecordEmailChangeConfirmForm implements forms.RecordEmailChangeConfirm {
|
||||
constructor(app: CoreApp, collection: models.Collection)
|
||||
}
|
||||
|
||||
interface RecordEmailChangeRequestForm extends forms.RecordEmailChangeRequest{} // merge
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @group PocketBase
|
||||
*/
|
||||
declare class RecordEmailChangeRequestForm implements forms.RecordEmailChangeRequest {
|
||||
constructor(app: CoreApp, record: models.Record)
|
||||
}
|
||||
|
||||
interface RecordOAuth2LoginForm extends forms.RecordOAuth2Login{} // merge
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @group PocketBase
|
||||
*/
|
||||
declare class RecordOAuth2LoginForm implements forms.RecordOAuth2Login {
|
||||
constructor(app: CoreApp, collection: models.Collection, optAuthRecord?: models.Record)
|
||||
}
|
||||
|
||||
interface RecordPasswordLoginForm extends forms.RecordPasswordLogin{} // merge
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @group PocketBase
|
||||
*/
|
||||
declare class RecordPasswordLoginForm implements forms.RecordPasswordLogin {
|
||||
constructor(app: CoreApp, collection: models.Collection)
|
||||
}
|
||||
|
||||
interface RecordPasswordResetConfirmForm extends forms.RecordPasswordResetConfirm{} // merge
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @group PocketBase
|
||||
*/
|
||||
declare class RecordPasswordResetConfirmForm implements forms.RecordPasswordResetConfirm {
|
||||
constructor(app: CoreApp, collection: models.Collection)
|
||||
}
|
||||
|
||||
interface RecordPasswordResetRequestForm extends forms.RecordPasswordResetRequest{} // merge
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @group PocketBase
|
||||
*/
|
||||
declare class RecordPasswordResetRequestForm implements forms.RecordPasswordResetRequest {
|
||||
constructor(app: CoreApp, collection: models.Collection)
|
||||
}
|
||||
|
||||
interface RecordUpsertForm extends forms.RecordUpsert{} // merge
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @group PocketBase
|
||||
*/
|
||||
declare class RecordUpsertForm implements forms.RecordUpsert {
|
||||
constructor(app: CoreApp, record: models.Record)
|
||||
}
|
||||
|
||||
interface RecordVerificationConfirmForm extends forms.RecordVerificationConfirm{} // merge
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @group PocketBase
|
||||
*/
|
||||
declare class RecordVerificationConfirmForm implements forms.RecordVerificationConfirm {
|
||||
constructor(app: CoreApp, collection: models.Collection)
|
||||
}
|
||||
|
||||
interface RecordVerificationRequestForm extends forms.RecordVerificationRequest{} // merge
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @group PocketBase
|
||||
*/
|
||||
declare class RecordVerificationRequestForm implements forms.RecordVerificationRequest {
|
||||
constructor(app: CoreApp, collection: models.Collection)
|
||||
}
|
||||
|
||||
interface SettingsUpsertForm extends forms.SettingsUpsert{} // merge
|
||||
/**
|
||||
* @inheritDoc
|
||||
* @group PocketBase
|
||||
*/
|
||||
declare class SettingsUpsertForm implements forms.SettingsUpsert {
|
||||
constructor(app: CoreApp)
|
||||
constructor(app: CoreApp, record: core.Record)
|
||||
}
|
||||
|
||||
interface TestEmailSendForm extends forms.TestEmailSend{} // merge
|
||||
@@ -971,6 +1011,26 @@ declare class UnauthorizedError implements apis.ApiError {
|
||||
constructor(message?: string, data?: any)
|
||||
}
|
||||
|
||||
interface TooManyRequestsError extends apis.ApiError{} // merge
|
||||
/**
|
||||
* TooManyRequestsError returns 429 ApiError.
|
||||
*
|
||||
* @group PocketBase
|
||||
*/
|
||||
declare class TooManyRequestsError implements apis.ApiError {
|
||||
constructor(message?: string, data?: any)
|
||||
}
|
||||
|
||||
interface InternalServerError extends apis.ApiError{} // merge
|
||||
/**
|
||||
* InternalServerError returns 429 ApiError.
|
||||
*
|
||||
* @group PocketBase
|
||||
*/
|
||||
declare class InternalServerError implements apis.ApiError {
|
||||
constructor(message?: string, data?: any)
|
||||
}
|
||||
|
||||
/**
|
||||
* ` + "`" + `$apis` + "`" + ` defines commonly used PocketBase api helpers and middlewares.
|
||||
*
|
||||
@@ -983,21 +1043,19 @@ declare namespace $apis {
|
||||
* If a file resource is missing and indexFallback is set, the request
|
||||
* will be forwarded to the base index.html (useful for SPA).
|
||||
*/
|
||||
export function staticDirectoryHandler(dir: string, indexFallback: boolean): echo.HandlerFunc
|
||||
export function static(dir: string, indexFallback: boolean): (e: core.RequestEvent) => void
|
||||
|
||||
let requireGuestOnly: apis.requireGuestOnly
|
||||
let requireRecordAuth: apis.requireRecordAuth
|
||||
let requireAdminAuth: apis.requireAdminAuth
|
||||
let requireAdminAuthOnlyIfAny: apis.requireAdminAuthOnlyIfAny
|
||||
let requireAdminOrRecordAuth: apis.requireAdminOrRecordAuth
|
||||
let requireAdminOrOwnerAuth: apis.requireAdminOrOwnerAuth
|
||||
let activityLogger: apis.activityLogger
|
||||
let requestInfo: apis.requestInfo
|
||||
let recordAuthResponse: apis.recordAuthResponse
|
||||
let gzip: middleware.gzip
|
||||
let bodyLimit: middleware.bodyLimit
|
||||
let enrichRecord: apis.enrichRecord
|
||||
let enrichRecords: apis.enrichRecords
|
||||
let requireGuestOnly: apis.requireGuestOnly
|
||||
let requireAuth: apis.requireAuth
|
||||
let requireSuperuserAuth: apis.requireSuperuserAuth
|
||||
let requireSuperuserAuthOnlyIfAny: apis.requireSuperuserAuthOnlyIfAny
|
||||
let requireSuperuserOrOwnerAuth: apis.requireSuperuserOrOwnerAuth
|
||||
let skipSuccessActivityLog: apis.skipSuccessActivityLog
|
||||
let gzip: apis.gzip
|
||||
let bodyLimit: apis.bodyLimit
|
||||
let recordAuthResponse: apis.recordAuthResponse
|
||||
let enrichRecord: apis.enrichRecord
|
||||
let enrichRecords: apis.enrichRecords
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------
|
||||
@@ -1023,9 +1081,10 @@ declare namespace $http {
|
||||
*
|
||||
* ` + "```" + `js
|
||||
* const res = $http.send({
|
||||
* url: "https://example.com",
|
||||
* body: JSON.stringify({"title": "test"})
|
||||
* method: "post",
|
||||
* method: "POST",
|
||||
* url: "https://example.com",
|
||||
* body: JSON.stringify({"title": "test"}),
|
||||
* headers: { 'Content-Type': 'application/json' }
|
||||
* })
|
||||
*
|
||||
* console.log(res.statusCode) // the response HTTP status code
|
||||
@@ -1042,7 +1101,7 @@ declare namespace $http {
|
||||
headers?: { [key:string]: string },
|
||||
timeout?: number, // default to 120
|
||||
|
||||
// deprecated, please use body instead
|
||||
// @deprecated please use body instead
|
||||
data?: { [key:string]: any },
|
||||
}): {
|
||||
statusCode: number,
|
||||
@@ -1065,8 +1124,8 @@ declare namespace $http {
|
||||
* @group PocketBase
|
||||
*/
|
||||
declare function migrate(
|
||||
up: (db: dbx.Builder) => void,
|
||||
down?: (db: dbx.Builder) => void
|
||||
up: (txApp: CoreApp) => void,
|
||||
down?: (txApp: CoreApp) => void
|
||||
): void;
|
||||
`
|
||||
|
||||
@@ -1077,13 +1136,11 @@ func main() {
|
||||
|
||||
gen := tygoja.New(tygoja.Config{
|
||||
Packages: map[string][]string{
|
||||
"github.com/labstack/echo/v5/middleware": {"Gzip", "BodyLimit"},
|
||||
"github.com/go-ozzo/ozzo-validation/v4": {"Error"},
|
||||
"github.com/pocketbase/dbx": {"*"},
|
||||
"github.com/pocketbase/pocketbase/tools/security": {"*"},
|
||||
"github.com/pocketbase/pocketbase/tools/filesystem": {"*"},
|
||||
"github.com/pocketbase/pocketbase/tools/template": {"*"},
|
||||
"github.com/pocketbase/pocketbase/tokens": {"*"},
|
||||
"github.com/pocketbase/pocketbase/mails": {"*"},
|
||||
"github.com/pocketbase/pocketbase/apis": {"*"},
|
||||
"github.com/pocketbase/pocketbase/forms": {"*"},
|
||||
@@ -1099,23 +1156,25 @@ func main() {
|
||||
return mapper.MethodName(nil, reflect.Method{Name: s})
|
||||
},
|
||||
TypeMappings: map[string]string{
|
||||
"crypto.*": "any",
|
||||
"acme.*": "any",
|
||||
"autocert.*": "any",
|
||||
"driver.*": "any",
|
||||
"reflect.*": "any",
|
||||
"fmt.*": "any",
|
||||
"rand.*": "any",
|
||||
"tls.*": "any",
|
||||
"asn1.*": "any",
|
||||
"pkix.*": "any",
|
||||
"x509.*": "any",
|
||||
"pflag.*": "any",
|
||||
"flag.*": "any",
|
||||
"log.*": "any",
|
||||
"http.Client": "any",
|
||||
"crypto.*": "any",
|
||||
"acme.*": "any",
|
||||
"autocert.*": "any",
|
||||
"driver.*": "any",
|
||||
"reflect.*": "any",
|
||||
"fmt.*": "any",
|
||||
"rand.*": "any",
|
||||
"tls.*": "any",
|
||||
"asn1.*": "any",
|
||||
"pkix.*": "any",
|
||||
"x509.*": "any",
|
||||
"pflag.*": "any",
|
||||
"flag.*": "any",
|
||||
"log.*": "any",
|
||||
"aws.*": "any",
|
||||
"http.Client": "any",
|
||||
"mail.Address": "{ address: string; name?: string; }", // prevents the LSP to complain in case no name is provided
|
||||
},
|
||||
Indent: " ", // use only a single space to reduce slight the size
|
||||
Indent: " ", // use only a single space to reduce slightly the size
|
||||
WithPackageFunctions: true,
|
||||
Heading: declarations,
|
||||
})
|
||||
@@ -1151,7 +1210,7 @@ func main() {
|
||||
func hooksDeclarations() string {
|
||||
var result strings.Builder
|
||||
|
||||
excluded := []string{"OnBeforeServe"}
|
||||
excluded := []string{"OnServe"}
|
||||
appType := reflect.TypeOf(struct{ core.App }{})
|
||||
totalMethods := appType.NumMethod()
|
||||
|
||||
@@ -1165,7 +1224,7 @@ func hooksDeclarations() string {
|
||||
|
||||
withTags := strings.HasPrefix(hookType.String(), "*hook.TaggedHook")
|
||||
|
||||
addMethod, ok := hookType.MethodByName("Add")
|
||||
addMethod, ok := hookType.MethodByName("BindFunc")
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
+37
-38
@@ -27,17 +27,12 @@ import (
|
||||
"github.com/dop251/goja_nodejs/require"
|
||||
"github.com/fatih/color"
|
||||
"github.com/fsnotify/fsnotify"
|
||||
"github.com/labstack/echo/v5"
|
||||
"github.com/pocketbase/dbx"
|
||||
"github.com/pocketbase/pocketbase/core"
|
||||
m "github.com/pocketbase/pocketbase/migrations"
|
||||
"github.com/pocketbase/pocketbase/plugins/jsvm/internal/types/generated"
|
||||
"github.com/pocketbase/pocketbase/tools/template"
|
||||
)
|
||||
|
||||
const (
|
||||
typesFileName = "types.d.ts"
|
||||
)
|
||||
const typesFileName = "types.d.ts"
|
||||
|
||||
// Config defines the config options of the jsvm plugin.
|
||||
type Config struct {
|
||||
@@ -131,9 +126,15 @@ func Register(app core.App, config Config) error {
|
||||
p.config.TypesDir = app.DataDir()
|
||||
}
|
||||
|
||||
p.app.OnAfterBootstrap().Add(func(e *core.BootstrapEvent) error {
|
||||
p.app.OnBootstrap().BindFunc(func(e *core.BootstrapEvent) error {
|
||||
err := e.Next()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// ensure that the user has the latest types declaration
|
||||
if err := p.refreshTypesFile(); err != nil {
|
||||
err = p.refreshTypesFile()
|
||||
if err != nil {
|
||||
color.Yellow("Unable to refresh app types file: %v", err)
|
||||
}
|
||||
|
||||
@@ -173,14 +174,13 @@ func (p *plugin) registerMigrations() error {
|
||||
process.Enable(vm)
|
||||
baseBinds(vm)
|
||||
dbxBinds(vm)
|
||||
tokensBinds(vm)
|
||||
securityBinds(vm)
|
||||
osBinds(vm)
|
||||
filepathBinds(vm)
|
||||
httpClientBinds(vm)
|
||||
|
||||
vm.Set("migrate", func(up, down func(db dbx.Builder) error) {
|
||||
m.AppMigrations.Register(up, down, file)
|
||||
vm.Set("migrate", func(up, down func(txApp core.App) error) {
|
||||
core.AppMigrations.Register(up, down, file)
|
||||
})
|
||||
|
||||
if p.config.OnInit != nil {
|
||||
@@ -238,9 +238,10 @@ func (p *plugin) registerHooks() error {
|
||||
return err
|
||||
}
|
||||
|
||||
p.app.OnBeforeServe().Add(func(e *core.ServeEvent) error {
|
||||
e.Router.HTTPErrorHandler = p.normalizeServeExceptions(e.Router.HTTPErrorHandler)
|
||||
return nil
|
||||
p.app.OnServe().BindFunc(func(e *core.ServeEvent) error {
|
||||
e.Router.BindFunc(p.normalizeServeExceptions)
|
||||
|
||||
return e.Next()
|
||||
})
|
||||
|
||||
// safe to be shared across multiple vms
|
||||
@@ -255,7 +256,6 @@ func (p *plugin) registerHooks() error {
|
||||
baseBinds(vm)
|
||||
dbxBinds(vm)
|
||||
filesystemBinds(vm)
|
||||
tokensBinds(vm)
|
||||
securityBinds(vm)
|
||||
osBinds(vm)
|
||||
filepathBinds(vm)
|
||||
@@ -311,32 +311,31 @@ func (p *plugin) registerHooks() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// normalizeExceptions wraps the provided error handler and returns a new one
|
||||
// with extracted goja exception error value for consistency when throwing or returning errors.
|
||||
func (p *plugin) normalizeServeExceptions(oldErrorHandler echo.HTTPErrorHandler) echo.HTTPErrorHandler {
|
||||
return func(c echo.Context, err error) {
|
||||
defer func() {
|
||||
oldErrorHandler(c, err)
|
||||
}()
|
||||
// normalizeExceptions registers a global error handler that
|
||||
// wraps the extracted goja exception error value for consistency
|
||||
// when throwing or returning errors.
|
||||
func (p *plugin) normalizeServeExceptions(e *core.RequestEvent) error {
|
||||
err := e.Next()
|
||||
|
||||
if err == nil || c.Response().Committed {
|
||||
return // no error or already committed
|
||||
}
|
||||
if err == nil || e.Written() {
|
||||
return err // no error or already committed
|
||||
}
|
||||
|
||||
jsException, ok := err.(*goja.Exception)
|
||||
if !ok {
|
||||
return // no exception
|
||||
}
|
||||
jsException, ok := err.(*goja.Exception)
|
||||
if !ok {
|
||||
return err // no exception
|
||||
}
|
||||
|
||||
switch v := jsException.Value().Export().(type) {
|
||||
case error:
|
||||
err = v
|
||||
case map[string]any: // goja.GoError
|
||||
if vErr, ok := v["value"].(error); ok {
|
||||
err = vErr
|
||||
}
|
||||
switch v := jsException.Value().Export().(type) {
|
||||
case error:
|
||||
err = v
|
||||
case map[string]any: // goja.GoError
|
||||
if vErr, ok := v["value"].(error); ok {
|
||||
err = vErr
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// watchHooks initializes a hooks file watcher that will restart the
|
||||
@@ -365,12 +364,12 @@ func (p *plugin) watchHooks() error {
|
||||
}
|
||||
}
|
||||
|
||||
p.app.OnTerminate().Add(func(e *core.TerminateEvent) error {
|
||||
p.app.OnTerminate().BindFunc(func(e *core.TerminateEvent) error {
|
||||
watcher.Close()
|
||||
|
||||
stopDebounceTimer()
|
||||
|
||||
return nil
|
||||
return e.Next()
|
||||
})
|
||||
|
||||
// start listening for events.
|
||||
|
||||
@@ -10,130 +10,86 @@ import (
|
||||
|
||||
"github.com/pocketbase/dbx"
|
||||
"github.com/pocketbase/pocketbase/core"
|
||||
"github.com/pocketbase/pocketbase/daos"
|
||||
"github.com/pocketbase/pocketbase/models"
|
||||
"github.com/pocketbase/pocketbase/tools/migrate"
|
||||
)
|
||||
|
||||
const collectionsStoreKey = "migratecmd_collections"
|
||||
|
||||
// onCollectionChange handles the automigration snapshot generation on
|
||||
// collection change event (create/update/delete).
|
||||
func (p *plugin) afterCollectionChange() func(*core.ModelEvent) error {
|
||||
return func(e *core.ModelEvent) error {
|
||||
if e.Model.TableName() != "_collections" {
|
||||
return nil // not a collection
|
||||
// automigrateOnCollectionChange handles the automigration snapshot
|
||||
// generation on collection change request event (create/update/delete).
|
||||
func (p *plugin) automigrateOnCollectionChange(e *core.CollectionRequestEvent) error {
|
||||
var err error
|
||||
var old *core.Collection
|
||||
if !e.Collection.IsNew() {
|
||||
old, err = e.App.FindCollectionByNameOrId(e.Collection.Id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// @todo replace with the OldModel when added to the ModelEvent
|
||||
oldCollections, err := p.getCachedCollections()
|
||||
err = e.Next()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
new, err := p.app.FindCollectionByNameOrId(e.Collection.Id)
|
||||
if err != nil && !errors.Is(err, sql.ErrNoRows) {
|
||||
return err
|
||||
}
|
||||
|
||||
// for now exclude OAuth2 configs from the migration
|
||||
if old != nil && old.IsAuth() {
|
||||
old.OAuth2.Providers = nil
|
||||
}
|
||||
if new != nil && new.IsAuth() {
|
||||
new.OAuth2.Providers = nil
|
||||
}
|
||||
|
||||
var template string
|
||||
var templateErr error
|
||||
if p.config.TemplateLang == TemplateLangJS {
|
||||
template, templateErr = p.jsDiffTemplate(new, old)
|
||||
} else {
|
||||
template, templateErr = p.goDiffTemplate(new, old)
|
||||
}
|
||||
if templateErr != nil {
|
||||
if errors.Is(templateErr, ErrEmptyTemplate) {
|
||||
return nil // no changes
|
||||
}
|
||||
return fmt.Errorf("failed to resolve template: %w", templateErr)
|
||||
}
|
||||
|
||||
var action string
|
||||
switch {
|
||||
case new == nil:
|
||||
action = "deleted_" + old.Name
|
||||
case old == nil:
|
||||
action = "created_" + new.Name
|
||||
default:
|
||||
action = "updated_" + old.Name
|
||||
}
|
||||
|
||||
name := fmt.Sprintf("%d_%s.%s", time.Now().Unix(), action, p.config.TemplateLang)
|
||||
filePath := filepath.Join(p.config.Dir, name)
|
||||
|
||||
return p.app.RunInTransaction(func(txApp core.App) error {
|
||||
// insert the migration entry
|
||||
_, err := txApp.DB().Insert(core.DefaultMigrationsTable, dbx.Params{
|
||||
"file": name,
|
||||
// use microseconds for more granular applied time in case
|
||||
// multiple collection changes happens at the ~exact time
|
||||
"applied": time.Now().UnixMicro(),
|
||||
}).Execute()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
old := oldCollections[e.Model.GetId()]
|
||||
|
||||
new, err := p.app.Dao().FindCollectionByNameOrId(e.Model.GetId())
|
||||
if err != nil && !errors.Is(err, sql.ErrNoRows) {
|
||||
return err
|
||||
// ensure that the local migrations dir exist
|
||||
if err := os.MkdirAll(p.config.Dir, os.ModePerm); err != nil {
|
||||
return fmt.Errorf("failed to create migration dir: %w", err)
|
||||
}
|
||||
|
||||
var template string
|
||||
var templateErr error
|
||||
if p.config.TemplateLang == TemplateLangJS {
|
||||
template, templateErr = p.jsDiffTemplate(new, old)
|
||||
} else {
|
||||
template, templateErr = p.goDiffTemplate(new, old)
|
||||
}
|
||||
if templateErr != nil {
|
||||
if errors.Is(templateErr, emptyTemplateErr) {
|
||||
return nil // no changes
|
||||
}
|
||||
return fmt.Errorf("failed to resolve template: %w", templateErr)
|
||||
if err := os.WriteFile(filePath, []byte(template), 0644); err != nil {
|
||||
return fmt.Errorf("failed to save automigrate file: %w", err)
|
||||
}
|
||||
|
||||
var action string
|
||||
switch {
|
||||
case new == nil:
|
||||
action = "deleted_" + old.Name
|
||||
case old == nil:
|
||||
action = "created_" + new.Name
|
||||
default:
|
||||
action = "updated_" + old.Name
|
||||
}
|
||||
|
||||
name := fmt.Sprintf("%d_%s.%s", time.Now().Unix(), action, p.config.TemplateLang)
|
||||
filePath := filepath.Join(p.config.Dir, name)
|
||||
|
||||
return p.app.Dao().RunInTransaction(func(txDao *daos.Dao) error {
|
||||
// insert the migration entry
|
||||
_, err := txDao.DB().Insert(migrate.DefaultMigrationsTable, dbx.Params{
|
||||
"file": name,
|
||||
// use microseconds for more granular applied time in case
|
||||
// multiple collection changes happens at the ~exact time
|
||||
"applied": time.Now().UnixMicro(),
|
||||
}).Execute()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// ensure that the local migrations dir exist
|
||||
if err := os.MkdirAll(p.config.Dir, os.ModePerm); err != nil {
|
||||
return fmt.Errorf("failed to create migration dir: %w", err)
|
||||
}
|
||||
|
||||
if err := os.WriteFile(filePath, []byte(template), 0644); err != nil {
|
||||
return fmt.Errorf("failed to save automigrate file: %w", err)
|
||||
}
|
||||
|
||||
p.updateSingleCachedCollection(new, old)
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (p *plugin) updateSingleCachedCollection(new, old *models.Collection) {
|
||||
cached, _ := p.app.Store().Get(collectionsStoreKey).(map[string]*models.Collection)
|
||||
|
||||
switch {
|
||||
case new == nil:
|
||||
delete(cached, old.Id)
|
||||
default:
|
||||
cached[new.Id] = new
|
||||
}
|
||||
|
||||
p.app.Store().Set(collectionsStoreKey, cached)
|
||||
}
|
||||
|
||||
func (p *plugin) refreshCachedCollections() error {
|
||||
if p.app.Dao() == nil {
|
||||
return errors.New("app is not initialized yet")
|
||||
}
|
||||
|
||||
var collections []*models.Collection
|
||||
if err := p.app.Dao().CollectionQuery().All(&collections); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cached := map[string]*models.Collection{}
|
||||
for _, c := range collections {
|
||||
cached[c.Id] = c
|
||||
}
|
||||
|
||||
p.app.Store().Set(collectionsStoreKey, cached)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *plugin) getCachedCollections() (map[string]*models.Collection, error) {
|
||||
if !p.app.Store().Has(collectionsStoreKey) {
|
||||
if err := p.refreshCachedCollections(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
result, _ := p.app.Store().Get(collectionsStoreKey).(map[string]*models.Collection)
|
||||
|
||||
return result, nil
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
package migratecmd
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
@@ -24,10 +25,7 @@ import (
|
||||
|
||||
"github.com/AlecAivazis/survey/v2"
|
||||
"github.com/pocketbase/pocketbase/core"
|
||||
"github.com/pocketbase/pocketbase/migrations"
|
||||
"github.com/pocketbase/pocketbase/models"
|
||||
"github.com/pocketbase/pocketbase/tools/inflector"
|
||||
"github.com/pocketbase/pocketbase/tools/migrate"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
@@ -82,22 +80,9 @@ func Register(app core.App, rootCmd *cobra.Command, config Config) error {
|
||||
|
||||
// watch for collection changes
|
||||
if p.config.Automigrate {
|
||||
// refresh the cache right after app bootstap
|
||||
p.app.OnAfterBootstrap().Add(func(e *core.BootstrapEvent) error {
|
||||
p.refreshCachedCollections()
|
||||
return nil
|
||||
})
|
||||
|
||||
// refresh the cache to ensure that it constains the latest changes
|
||||
// when migrations are applied on server start
|
||||
p.app.OnBeforeServe().Add(func(e *core.ServeEvent) error {
|
||||
p.refreshCachedCollections()
|
||||
return nil
|
||||
})
|
||||
|
||||
p.app.OnModelAfterCreate().Add(p.afterCollectionChange())
|
||||
p.app.OnModelAfterUpdate().Add(p.afterCollectionChange())
|
||||
p.app.OnModelAfterDelete().Add(p.afterCollectionChange())
|
||||
p.app.OnCollectionCreateRequest().BindFunc(p.automigrateOnCollectionChange)
|
||||
p.app.OnCollectionUpdateRequest().BindFunc(p.automigrateOnCollectionChange)
|
||||
p.app.OnCollectionDeleteRequest().BindFunc(p.automigrateOnCollectionChange)
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -139,10 +124,12 @@ func (p *plugin) createCommand() *cobra.Command {
|
||||
return err
|
||||
}
|
||||
default:
|
||||
runner, err := migrate.NewRunner(p.app.DB(), migrations.AppMigrations)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// note: system migrations are always applied as part of the bootstrap process
|
||||
var list = core.MigrationsList{}
|
||||
list.Copy(core.SystemMigrations)
|
||||
list.Copy(core.AppMigrations)
|
||||
|
||||
runner := core.NewMigrationsRunner(p.app, list)
|
||||
|
||||
if err := runner.Run(args...); err != nil {
|
||||
return err
|
||||
@@ -158,7 +145,7 @@ func (p *plugin) createCommand() *cobra.Command {
|
||||
|
||||
func (p *plugin) migrateCreateHandler(template string, args []string, interactive bool) (string, error) {
|
||||
if len(args) < 1 {
|
||||
return "", fmt.Errorf("Missing migration file name")
|
||||
return "", errors.New("Missing migration file name")
|
||||
}
|
||||
|
||||
name := args[0]
|
||||
@@ -214,9 +201,9 @@ func (p *plugin) migrateCollectionsHandler(args []string, interactive bool) (str
|
||||
createArgs := []string{"collections_snapshot"}
|
||||
createArgs = append(createArgs, args...)
|
||||
|
||||
collections := []*models.Collection{}
|
||||
if err := p.app.Dao().CollectionQuery().OrderBy("created ASC").All(&collections); err != nil {
|
||||
return "", fmt.Errorf("Failed to fetch migrations list: %v", err)
|
||||
collections := []*core.Collection{}
|
||||
if err := p.app.CollectionQuery().OrderBy("created ASC").All(&collections); err != nil {
|
||||
return "", fmt.Errorf("Failed to fetch migrations list: %v\n", err)
|
||||
}
|
||||
|
||||
var template string
|
||||
@@ -227,7 +214,7 @@ func (p *plugin) migrateCollectionsHandler(args []string, interactive bool) (str
|
||||
template, templateErr = p.goSnapshotTemplate(collections)
|
||||
}
|
||||
if templateErr != nil {
|
||||
return "", fmt.Errorf("Failed to resolve template: %v", templateErr)
|
||||
return "", fmt.Errorf("Failed to resolve template: %v\n", templateErr)
|
||||
}
|
||||
|
||||
return p.migrateCreateHandler(template, createArgs, interactive)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
+357
-393
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user