cache jsvm reflect created types
This commit is contained in:
parent
39df26ee21
commit
4824701b6c
|
@ -12,6 +12,8 @@
|
||||||
|
|
||||||
- Invalidate all record tokens when the auth record email is changed programmatically or by a superuser ([#5964](https://github.com/pocketbase/pocketbase/issues/5964)).
|
- Invalidate all record tokens when the auth record email is changed programmatically or by a superuser ([#5964](https://github.com/pocketbase/pocketbase/issues/5964)).
|
||||||
|
|
||||||
|
- Added cache for the JSVM `arrayOf(m)`, `DynamicModel`, etc. dynamic `reflect` created types.
|
||||||
|
|
||||||
- ⚠️ Removed the "dry submit" when executing the collections Create API rule
|
- ⚠️ Removed the "dry submit" when executing the collections Create API rule
|
||||||
(you can find more details why this change was introduced and how it could affect your app in https://github.com/pocketbase/pocketbase/discussions/6073).
|
(you can find more details why this change was introduced and how it could affect your app in https://github.com/pocketbase/pocketbase/discussions/6073).
|
||||||
For most users it should be non-breaking change, BUT if you have Create API rules that uses self-references or view counters you may have to adjust them manually.
|
For most users it should be non-breaking change, BUT if you have Create API rules that uses self-references or view counters you may have to adjust them manually.
|
||||||
|
@ -19,14 +21,14 @@
|
||||||
(_or in other words, `@collection.example.someField != "test"` will result to `true` if `example` collection has no records because it satisfies the condition that all available "example" records mustn't have `someField` equal to "test"_).
|
(_or in other words, `@collection.example.someField != "test"` will result to `true` if `example` collection has no records because it satisfies the condition that all available "example" records mustn't have `someField` equal to "test"_).
|
||||||
|
|
||||||
- ⚠️ Changed the type definition of `store.Store[T any]` to `store.Store[K comparable, T any]` to allow support for custom store key types.
|
- ⚠️ Changed the type definition of `store.Store[T any]` to `store.Store[K comparable, T any]` to allow support for custom store key types.
|
||||||
For most users it should be non-breaking change, BUT if you are creating manually `store.New[any](nil)` instances you'll have to specify the key generic type, aka. `store.New[string, any](nil)`.
|
For most users it should be non-breaking change, BUT if you are calling `store.New[any](nil)` instances you'll have to specify the store key type, aka. `store.New[string, any](nil)`.
|
||||||
|
|
||||||
|
|
||||||
## v0.23.12
|
## v0.23.12
|
||||||
|
|
||||||
- Added warning logs in case of mismatched `modernc.org/sqlite` and `modernc.org/libc` versions ([#6136](https://github.com/pocketbase/pocketbase/issues/6136#issuecomment-2556336962)).
|
- Added warning logs in case of mismatched `modernc.org/sqlite` and `modernc.org/libc` versions ([#6136](https://github.com/pocketbase/pocketbase/issues/6136#issuecomment-2556336962)).
|
||||||
|
|
||||||
- Skipped the default body size limit middleware for the backup upload endpooint ([#6152](https://github.com/pocketbase/pocketbase/issues/6152)).
|
- Skipped the default body size limit middleware for the backup upload endpoint ([#6152](https://github.com/pocketbase/pocketbase/issues/6152)).
|
||||||
|
|
||||||
|
|
||||||
## v0.23.11
|
## v0.23.11
|
||||||
|
|
|
@ -30,6 +30,7 @@ import (
|
||||||
"github.com/pocketbase/pocketbase/tools/mailer"
|
"github.com/pocketbase/pocketbase/tools/mailer"
|
||||||
"github.com/pocketbase/pocketbase/tools/router"
|
"github.com/pocketbase/pocketbase/tools/router"
|
||||||
"github.com/pocketbase/pocketbase/tools/security"
|
"github.com/pocketbase/pocketbase/tools/security"
|
||||||
|
"github.com/pocketbase/pocketbase/tools/store"
|
||||||
"github.com/pocketbase/pocketbase/tools/subscriptions"
|
"github.com/pocketbase/pocketbase/tools/subscriptions"
|
||||||
"github.com/pocketbase/pocketbase/tools/types"
|
"github.com/pocketbase/pocketbase/tools/types"
|
||||||
"github.com/spf13/cast"
|
"github.com/spf13/cast"
|
||||||
|
@ -295,6 +296,8 @@ func wrapMiddlewares(executors *vmsPool, rawMiddlewares ...goja.Value) ([]*hook.
|
||||||
return wrappedMiddlewares, nil
|
return wrappedMiddlewares, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var cachedArrayOfTypes = store.New[reflect.Type, reflect.Type](nil)
|
||||||
|
|
||||||
func baseBinds(vm *goja.Runtime) {
|
func baseBinds(vm *goja.Runtime) {
|
||||||
vm.SetFieldNameMapper(FieldMapper{})
|
vm.SetFieldNameMapper(FieldMapper{})
|
||||||
|
|
||||||
|
@ -348,10 +351,11 @@ 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 := cachedArrayOfTypes.GetOrSet(mt, func() reflect.Type {
|
||||||
elem := reflect.New(st).Elem()
|
return reflect.SliceOf(mt)
|
||||||
|
})
|
||||||
|
|
||||||
return elem.Addr().Interface()
|
return reflect.New(st).Elem().Addr().Interface()
|
||||||
})
|
})
|
||||||
|
|
||||||
vm.Set("unmarshal", func(data, dst any) error {
|
vm.Set("unmarshal", func(data, dst any) error {
|
||||||
|
@ -899,12 +903,42 @@ func httpClientBinds(vm *goja.Runtime) {
|
||||||
|
|
||||||
// -------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
|
|
||||||
|
// normalizeException checks if the provided error is a goja.Exception
|
||||||
|
// and attempts to return its underlying Go error.
|
||||||
|
//
|
||||||
|
// note: using just goja.Exception.Unwrap() is insufficient and may falsely result in nil.
|
||||||
|
func normalizeException(err error) error {
|
||||||
|
if err == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var cachedFactoryFuncTypes = store.New[string, reflect.Type](nil)
|
||||||
|
|
||||||
// registerFactoryAsConstructor registers the factory function as native JS constructor.
|
// registerFactoryAsConstructor registers the factory function as native JS constructor.
|
||||||
//
|
//
|
||||||
// If there is missing or nil arguments, their type zero value is used.
|
// If there is missing or nil arguments, their type zero value is used.
|
||||||
func registerFactoryAsConstructor(vm *goja.Runtime, constructorName string, factoryFunc any) {
|
func registerFactoryAsConstructor(vm *goja.Runtime, constructorName string, factoryFunc any) {
|
||||||
rv := reflect.ValueOf(factoryFunc)
|
rv := reflect.ValueOf(factoryFunc)
|
||||||
rt := reflect.TypeOf(factoryFunc)
|
rt := cachedFactoryFuncTypes.GetOrSet(constructorName, func() reflect.Type {
|
||||||
|
return reflect.TypeOf(factoryFunc)
|
||||||
|
})
|
||||||
totalArgs := rt.NumIn()
|
totalArgs := rt.NumIn()
|
||||||
|
|
||||||
vm.Set(constructorName, func(call goja.ConstructorCall) *goja.Object {
|
vm.Set(constructorName, func(call goja.ConstructorCall) *goja.Object {
|
||||||
|
@ -970,6 +1004,8 @@ func structConstructorUnmarshal(vm *goja.Runtime, call goja.ConstructorCall, ins
|
||||||
return instanceValue
|
return instanceValue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var cachedDynamicModels = store.New[string, *dynamicModelType](nil)
|
||||||
|
|
||||||
// newDynamicModel creates a new dynamic struct with fields based
|
// newDynamicModel creates a new dynamic struct with fields based
|
||||||
// on the specified "shape".
|
// on the specified "shape".
|
||||||
//
|
//
|
||||||
|
@ -980,7 +1016,40 @@ func structConstructorUnmarshal(vm *goja.Runtime, call goja.ConstructorCall, ins
|
||||||
// "total": 0,
|
// "total": 0,
|
||||||
// })
|
// })
|
||||||
func newDynamicModel(shape map[string]any) any {
|
func newDynamicModel(shape map[string]any) any {
|
||||||
shapeValues := make([]reflect.Value, 0, len(shape))
|
var modelType *dynamicModelType
|
||||||
|
|
||||||
|
shapeRaw, err := json.Marshal(shape)
|
||||||
|
if err != nil {
|
||||||
|
modelType = getDynamicModelStruct(shape)
|
||||||
|
} else {
|
||||||
|
modelType = cachedDynamicModels.GetOrSet(string(shapeRaw), func() *dynamicModelType {
|
||||||
|
return getDynamicModelStruct(shape)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
rvShapeValues := make([]reflect.Value, len(modelType.shapeValues))
|
||||||
|
for i, v := range modelType.shapeValues {
|
||||||
|
rvShapeValues[i] = reflect.ValueOf(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
elem := reflect.New(modelType.structType).Elem()
|
||||||
|
|
||||||
|
for i, v := range rvShapeValues {
|
||||||
|
elem.Field(i).Set(v)
|
||||||
|
}
|
||||||
|
|
||||||
|
return elem.Addr().Interface()
|
||||||
|
}
|
||||||
|
|
||||||
|
type dynamicModelType struct {
|
||||||
|
structType reflect.Type
|
||||||
|
shapeValues []any
|
||||||
|
}
|
||||||
|
|
||||||
|
func getDynamicModelStruct(shape map[string]any) *dynamicModelType {
|
||||||
|
result := new(dynamicModelType)
|
||||||
|
result.shapeValues = make([]any, 0, len(shape))
|
||||||
|
|
||||||
structFields := make([]reflect.StructField, 0, len(shape))
|
structFields := make([]reflect.StructField, 0, len(shape))
|
||||||
|
|
||||||
for k, v := range shape {
|
for k, v := range shape {
|
||||||
|
@ -1001,7 +1070,7 @@ func newDynamicModel(shape map[string]any) any {
|
||||||
vt = reflect.TypeOf(newV)
|
vt = reflect.TypeOf(newV)
|
||||||
}
|
}
|
||||||
|
|
||||||
shapeValues = append(shapeValues, reflect.ValueOf(v))
|
result.shapeValues = append(result.shapeValues, v)
|
||||||
|
|
||||||
structFields = append(structFields, reflect.StructField{
|
structFields = append(structFields, reflect.StructField{
|
||||||
Name: inflector.UcFirst(k), // ensures that the field is exportable
|
Name: inflector.UcFirst(k), // ensures that the field is exportable
|
||||||
|
@ -1010,38 +1079,7 @@ func newDynamicModel(shape map[string]any) any {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
st := reflect.StructOf(structFields)
|
result.structType = reflect.StructOf(structFields)
|
||||||
elem := reflect.New(st).Elem()
|
|
||||||
|
|
||||||
for i, v := range shapeValues {
|
return result
|
||||||
elem.Field(i).Set(v)
|
|
||||||
}
|
|
||||||
|
|
||||||
return elem.Addr().Interface()
|
|
||||||
}
|
|
||||||
|
|
||||||
// normalizeException checks if the provided error is a goja.Exception
|
|
||||||
// and attempts to return its underlying Go error.
|
|
||||||
//
|
|
||||||
// note: using just goja.Exception.Unwrap() is insufficient and may falsely result in nil.
|
|
||||||
func normalizeException(err error) error {
|
|
||||||
if err == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue