updated WIP:v0.9.0 changelog
This commit is contained in:
parent
33539452de
commit
328b99a690
77
CHANGELOG.md
77
CHANGELOG.md
|
@ -1,6 +1,78 @@
|
||||||
## (WIP) v0.9.0
|
## (WIP) v0.9.0
|
||||||
|
|
||||||
- Changes to the `mailer.Mailer` interface (_minor breaking if you are sending custom emails_):
|
- Added new event hooks:
|
||||||
|
```
|
||||||
|
app.OnBeforeBootstrap()
|
||||||
|
app.OnAfterBootstrap()
|
||||||
|
```
|
||||||
|
|
||||||
|
- Refactored the `migrate` command to support **external JavaScript migration files** using an embedded JS interpreter ([goja](https://github.com/dop251/goja)).
|
||||||
|
This allow writting custom migration scripts such as programmatically creating collections,
|
||||||
|
initializing default settings, running import scripts, etc., with a JavaScript API very similar to the Go one (_more documentation will be available soon_).
|
||||||
|
|
||||||
|
The `migrate` command is available by default for the prebult executable,
|
||||||
|
but if you use PocketBase as framework you need register it manually:
|
||||||
|
```go
|
||||||
|
migrationsDir := "" // default to "pb_migrations" (for js) and "migrations" (for go)
|
||||||
|
|
||||||
|
// load js files if you want to allow loading external JavaScript migrations
|
||||||
|
jsvm.MustRegisterMigrationsLoader(app, &jsvm.MigrationsLoaderOptions{
|
||||||
|
Dir: migrationsDir,
|
||||||
|
})
|
||||||
|
|
||||||
|
// init the `migrate` command
|
||||||
|
migratecmd.MustRegister(app, app.RootCmd, &migratecmd.Options{
|
||||||
|
TemplateLang: migratecmd.TemplateLangGo, // or migratecmd.TemplateLangJS
|
||||||
|
Dir: migrationsDir,
|
||||||
|
Automigrate: true,
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
**The refactoring also comes with automigrations support.**
|
||||||
|
|
||||||
|
If `Automigrate` is enabled (`true` by default for the prebuilt executable; can be disabled with `--automigrate=0`),
|
||||||
|
PocketBase will generate seamlessly in the background JS (or Go) migration file with your collection changes.
|
||||||
|
**The directory with the JS migrations can be committed to your git repo.**
|
||||||
|
All migrations (Go and JS) are automatically executed on server start.
|
||||||
|
Also note that the auto generated migrations are granural (in contrast to the `migrate collections` snapshot command)
|
||||||
|
and allow multiple developers to do changes on the collections independently (even editing the same collection) miniziming the eventual merge conflicts.
|
||||||
|
Here is a sample JS migration file that will be generated if you for example edit a single collection name:
|
||||||
|
```js
|
||||||
|
// pb_migrations/1669663597_updated_posts_old.js
|
||||||
|
migrate((db) => {
|
||||||
|
// up
|
||||||
|
const dao = new Dao(db)
|
||||||
|
const collection = dao.findCollectionByNameOrId("lngf8rb3dqu86r3")
|
||||||
|
collection.name = "posts_new"
|
||||||
|
return dao.saveCollection(collection)
|
||||||
|
}, (db) => {
|
||||||
|
// down
|
||||||
|
const dao = new Dao(db)
|
||||||
|
const collection = dao.findCollectionByNameOrId("lngf8rb3dqu86r3")
|
||||||
|
collection.name = "posts_old"
|
||||||
|
return dao.saveCollection(collection)
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
- Added new Dao helpers to make it easier fetching and updating the app settings from a migration:
|
||||||
|
```go
|
||||||
|
dao.FindSettings([optEncryptionKey])
|
||||||
|
dao.SaveSettings(newSettings, [optEncryptionKey])
|
||||||
|
```
|
||||||
|
|
||||||
|
- Moved `core.Settings` to `models/settings.Settings`:
|
||||||
|
```
|
||||||
|
core.Settings{} -> settings.Settings{}
|
||||||
|
core.NewSettings() -> settings.New()
|
||||||
|
core.MetaConfig{} -> settings.MetaConfig{}
|
||||||
|
core.LogsConfig{} -> settings.LogsConfig{}
|
||||||
|
core.SmtpConfig{} -> settings.SmtpConfig{}
|
||||||
|
core.S3Config{} -> settings.S3Config{}
|
||||||
|
core.TokenConfig{} -> settings.TokenConfig{}
|
||||||
|
core.AuthProviderConfig{} -> settings.AuthProviderConfig{}
|
||||||
|
```
|
||||||
|
|
||||||
|
- Changed the `mailer.Mailer` interface (**minor breaking if you are sending custom emails**):
|
||||||
```go
|
```go
|
||||||
// Old:
|
// Old:
|
||||||
app.NewMailClient().Send(from, to, subject, html, attachments?)
|
app.NewMailClient().Send(from, to, subject, html, attachments?)
|
||||||
|
@ -19,8 +91,7 @@
|
||||||
Text: "custom plain text version",
|
Text: "custom plain text version",
|
||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
The new `*mailer.Message` struct is also now a member of the `MailerRecordEvent` and `MailerAdminEvent` events.
|
||||||
- Added the new `*mailer.Message` to the `MailerRecordEvent`, `MailerAdminEvent` event structs.
|
|
||||||
|
|
||||||
|
|
||||||
## v0.8.0
|
## v0.8.0
|
||||||
|
|
|
@ -15,9 +15,15 @@ import (
|
||||||
func NewBaseVM(app core.App) *goja.Runtime {
|
func NewBaseVM(app core.App) *goja.Runtime {
|
||||||
vm := goja.New()
|
vm := goja.New()
|
||||||
vm.SetFieldNameMapper(goja.UncapFieldNameMapper())
|
vm.SetFieldNameMapper(goja.UncapFieldNameMapper())
|
||||||
|
|
||||||
vm.Set("$app", app)
|
vm.Set("$app", app)
|
||||||
|
|
||||||
|
baseBind(vm)
|
||||||
|
dbxBind(vm)
|
||||||
|
|
||||||
|
return vm
|
||||||
|
}
|
||||||
|
|
||||||
|
func baseBind(vm *goja.Runtime) {
|
||||||
vm.Set("unmarshal", func(src map[string]any, dest any) (any, error) {
|
vm.Set("unmarshal", func(src map[string]any, dest any) (any, error) {
|
||||||
raw, err := json.Marshal(src)
|
raw, err := json.Marshal(src)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -31,59 +37,41 @@ func NewBaseVM(app core.App) *goja.Runtime {
|
||||||
return dest, nil
|
return dest, nil
|
||||||
})
|
})
|
||||||
|
|
||||||
collectionConstructor(vm)
|
|
||||||
recordConstructor(vm)
|
|
||||||
adminConstructor(vm)
|
|
||||||
schemaConstructor(vm)
|
|
||||||
daoConstructor(vm)
|
|
||||||
dbxBinds(vm)
|
|
||||||
|
|
||||||
return vm
|
|
||||||
}
|
|
||||||
|
|
||||||
func collectionConstructor(vm *goja.Runtime) {
|
|
||||||
vm.Set("Collection", func(call goja.ConstructorCall) *goja.Object {
|
vm.Set("Collection", func(call goja.ConstructorCall) *goja.Object {
|
||||||
instance := &models.Collection{}
|
instance := &models.Collection{}
|
||||||
instanceValue := vm.ToValue(instance).(*goja.Object)
|
instanceValue := vm.ToValue(instance).(*goja.Object)
|
||||||
instanceValue.SetPrototype(call.This.Prototype())
|
instanceValue.SetPrototype(call.This.Prototype())
|
||||||
return instanceValue
|
return instanceValue
|
||||||
})
|
})
|
||||||
}
|
|
||||||
|
|
||||||
func recordConstructor(vm *goja.Runtime) {
|
|
||||||
vm.Set("Record", func(call goja.ConstructorCall) *goja.Object {
|
vm.Set("Record", func(call goja.ConstructorCall) *goja.Object {
|
||||||
instance := &models.Record{}
|
instance := &models.Record{}
|
||||||
instanceValue := vm.ToValue(instance).(*goja.Object)
|
instanceValue := vm.ToValue(instance).(*goja.Object)
|
||||||
instanceValue.SetPrototype(call.This.Prototype())
|
instanceValue.SetPrototype(call.This.Prototype())
|
||||||
return instanceValue
|
return instanceValue
|
||||||
})
|
})
|
||||||
}
|
|
||||||
|
|
||||||
func adminConstructor(vm *goja.Runtime) {
|
|
||||||
vm.Set("Admin", func(call goja.ConstructorCall) *goja.Object {
|
vm.Set("Admin", func(call goja.ConstructorCall) *goja.Object {
|
||||||
instance := &models.Admin{}
|
instance := &models.Admin{}
|
||||||
instanceValue := vm.ToValue(instance).(*goja.Object)
|
instanceValue := vm.ToValue(instance).(*goja.Object)
|
||||||
instanceValue.SetPrototype(call.This.Prototype())
|
instanceValue.SetPrototype(call.This.Prototype())
|
||||||
return instanceValue
|
return instanceValue
|
||||||
})
|
})
|
||||||
}
|
|
||||||
|
|
||||||
func schemaConstructor(vm *goja.Runtime) {
|
|
||||||
vm.Set("Schema", func(call goja.ConstructorCall) *goja.Object {
|
vm.Set("Schema", func(call goja.ConstructorCall) *goja.Object {
|
||||||
instance := &schema.Schema{}
|
instance := &schema.Schema{}
|
||||||
instanceValue := vm.ToValue(instance).(*goja.Object)
|
instanceValue := vm.ToValue(instance).(*goja.Object)
|
||||||
instanceValue.SetPrototype(call.This.Prototype())
|
instanceValue.SetPrototype(call.This.Prototype())
|
||||||
return instanceValue
|
return instanceValue
|
||||||
})
|
})
|
||||||
|
|
||||||
vm.Set("SchemaField", func(call goja.ConstructorCall) *goja.Object {
|
vm.Set("SchemaField", func(call goja.ConstructorCall) *goja.Object {
|
||||||
instance := &schema.SchemaField{}
|
instance := &schema.SchemaField{}
|
||||||
instanceValue := vm.ToValue(instance).(*goja.Object)
|
instanceValue := vm.ToValue(instance).(*goja.Object)
|
||||||
instanceValue.SetPrototype(call.This.Prototype())
|
instanceValue.SetPrototype(call.This.Prototype())
|
||||||
return instanceValue
|
return instanceValue
|
||||||
})
|
})
|
||||||
}
|
|
||||||
|
|
||||||
func daoConstructor(vm *goja.Runtime) {
|
|
||||||
vm.Set("Dao", func(call goja.ConstructorCall) *goja.Object {
|
vm.Set("Dao", func(call goja.ConstructorCall) *goja.Object {
|
||||||
db, ok := call.Argument(0).Export().(dbx.Builder)
|
db, ok := call.Argument(0).Export().(dbx.Builder)
|
||||||
if !ok || db == nil {
|
if !ok || db == nil {
|
||||||
|
@ -97,7 +85,7 @@ func daoConstructor(vm *goja.Runtime) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func dbxBinds(vm *goja.Runtime) {
|
func dbxBind(vm *goja.Runtime) {
|
||||||
obj := vm.NewObject()
|
obj := vm.NewObject()
|
||||||
vm.Set("$dbx", obj)
|
vm.Set("$dbx", obj)
|
||||||
|
|
||||||
|
@ -145,7 +133,6 @@ func apisBind(vm *goja.Runtime) {
|
||||||
obj.Set("unauthorizedError", apis.NewUnauthorizedError)
|
obj.Set("unauthorizedError", apis.NewUnauthorizedError)
|
||||||
|
|
||||||
// record helpers
|
// record helpers
|
||||||
obj.Set("getRequestData", apis.GetRequestData)
|
|
||||||
obj.Set("requestData", apis.RequestData)
|
obj.Set("requestData", apis.RequestData)
|
||||||
obj.Set("enrichRecord", apis.EnrichRecord)
|
obj.Set("enrichRecord", apis.EnrichRecord)
|
||||||
obj.Set("enrichRecords", apis.EnrichRecords)
|
obj.Set("enrichRecords", apis.EnrichRecords)
|
||||||
|
|
|
@ -95,10 +95,10 @@ func Register(app core.App, rootCmd *cobra.Command, options *Options) error {
|
||||||
|
|
||||||
func (p *plugin) createCommand() *cobra.Command {
|
func (p *plugin) createCommand() *cobra.Command {
|
||||||
const cmdDesc = `Supported arguments are:
|
const cmdDesc = `Supported arguments are:
|
||||||
- up - runs all available migrations
|
- up - runs all available migrations
|
||||||
- down [number] - reverts the last [number] applied migrations
|
- down [number] - reverts the last [number] applied migrations
|
||||||
- create name [folder] - creates new blank migration template file
|
- create name - creates new blank migration template file
|
||||||
- collections [folder] - creates new migration file with the latest local collections snapshot (similar to the automigrate but allows editing)
|
- collections - creates new migration file with snapshot of the local collections configuration
|
||||||
`
|
`
|
||||||
|
|
||||||
command := &cobra.Command{
|
command := &cobra.Command{
|
||||||
|
@ -143,14 +143,7 @@ func (p *plugin) migrateCreateHandler(template string, args []string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
name := args[0]
|
name := args[0]
|
||||||
|
dir := p.options.Dir
|
||||||
var dir string
|
|
||||||
if len(args) == 2 {
|
|
||||||
dir = args[1]
|
|
||||||
}
|
|
||||||
if dir == "" {
|
|
||||||
dir = p.options.Dir
|
|
||||||
}
|
|
||||||
|
|
||||||
resultFilePath := path.Join(
|
resultFilePath := path.Join(
|
||||||
dir,
|
dir,
|
||||||
|
|
|
@ -164,7 +164,7 @@ func init() {
|
||||||
|
|
||||||
expectedName := "_created_new_name." + s.lang
|
expectedName := "_created_new_name." + s.lang
|
||||||
if !strings.Contains(files[0].Name(), expectedName) {
|
if !strings.Contains(files[0].Name(), expectedName) {
|
||||||
t.Fatalf("Expected filename to contains %q, got %q", expectedName, files[0].Name())
|
t.Fatalf("[%d] Expected filename to contains %q, got %q", i, expectedName, files[0].Name())
|
||||||
}
|
}
|
||||||
|
|
||||||
fullPath := filepath.Join(migrationsDir, files[0].Name())
|
fullPath := filepath.Join(migrationsDir, files[0].Name())
|
||||||
|
@ -335,7 +335,7 @@ func init() {
|
||||||
|
|
||||||
expectedName := "_deleted_test456." + s.lang
|
expectedName := "_deleted_test456." + s.lang
|
||||||
if !strings.Contains(files[0].Name(), expectedName) {
|
if !strings.Contains(files[0].Name(), expectedName) {
|
||||||
t.Fatalf("Expected filename to contains %q, got %q", expectedName, files[0].Name())
|
t.Fatalf("[%d] Expected filename to contains %q, got %q", i, expectedName, files[0].Name())
|
||||||
}
|
}
|
||||||
|
|
||||||
fullPath := filepath.Join(migrationsDir, files[0].Name())
|
fullPath := filepath.Join(migrationsDir, files[0].Name())
|
||||||
|
@ -681,7 +681,7 @@ func init() {
|
||||||
|
|
||||||
expectedName := "_updated_test456." + s.lang
|
expectedName := "_updated_test456." + s.lang
|
||||||
if !strings.Contains(files[0].Name(), expectedName) {
|
if !strings.Contains(files[0].Name(), expectedName) {
|
||||||
t.Fatalf("Expected filename to contains %q, got %q", expectedName, files[0].Name())
|
t.Fatalf("[%d] Expected filename to contains %q, got %q", i, expectedName, files[0].Name())
|
||||||
}
|
}
|
||||||
|
|
||||||
fullPath := filepath.Join(migrationsDir, files[0].Name())
|
fullPath := filepath.Join(migrationsDir, files[0].Name())
|
||||||
|
|
|
@ -648,27 +648,29 @@ func (p *plugin) goDiffTemplate(new *models.Collection, old *models.Collection)
|
||||||
|
|
||||||
up := strings.Join(upParts, "\n\t\t")
|
up := strings.Join(upParts, "\n\t\t")
|
||||||
down := strings.Join(downParts, "\n\t\t")
|
down := strings.Join(downParts, "\n\t\t")
|
||||||
|
|
||||||
var optImports string
|
|
||||||
|
|
||||||
combined := up + down
|
combined := up + down
|
||||||
|
|
||||||
|
// generate imports
|
||||||
|
// ---
|
||||||
|
var imports string
|
||||||
|
|
||||||
if strings.Contains(combined, "json.Unmarshal(") ||
|
if strings.Contains(combined, "json.Unmarshal(") ||
|
||||||
strings.Contains(combined, "json.Marshal(") {
|
strings.Contains(combined, "json.Marshal(") {
|
||||||
optImports += "\n\t\"encoding/json\"\n"
|
imports += "\n\t\"encoding/json\"\n"
|
||||||
}
|
}
|
||||||
|
|
||||||
optImports += "\n\t\"github.com/pocketbase/dbx\""
|
imports += "\n\t\"github.com/pocketbase/dbx\""
|
||||||
optImports += "\n\t\"github.com/pocketbase/pocketbase/daos\""
|
imports += "\n\t\"github.com/pocketbase/pocketbase/daos\""
|
||||||
optImports += "\n\tm \"github.com/pocketbase/pocketbase/migrations\""
|
imports += "\n\tm \"github.com/pocketbase/pocketbase/migrations\""
|
||||||
|
|
||||||
if strings.Contains(combined, "schema.SchemaField{") {
|
if strings.Contains(combined, "schema.SchemaField{") {
|
||||||
optImports += "\n\t\"github.com/pocketbase/pocketbase/models/schema\""
|
imports += "\n\t\"github.com/pocketbase/pocketbase/models/schema\""
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.Contains(combined, "types.Pointer(") {
|
if strings.Contains(combined, "types.Pointer(") {
|
||||||
optImports += "\n\t\"github.com/pocketbase/pocketbase/tools/types\""
|
imports += "\n\t\"github.com/pocketbase/pocketbase/tools/types\""
|
||||||
}
|
}
|
||||||
|
// ---
|
||||||
|
|
||||||
const template = `package %s
|
const template = `package %s
|
||||||
|
|
||||||
|
@ -705,7 +707,7 @@ func init() {
|
||||||
return fmt.Sprintf(
|
return fmt.Sprintf(
|
||||||
template,
|
template,
|
||||||
filepath.Base(p.options.Dir),
|
filepath.Base(p.options.Dir),
|
||||||
optImports,
|
imports,
|
||||||
old.Id, strings.TrimSpace(up),
|
old.Id, strings.TrimSpace(up),
|
||||||
new.Id, strings.TrimSpace(down),
|
new.Id, strings.TrimSpace(down),
|
||||||
), nil
|
), nil
|
||||||
|
|
Loading…
Reference in New Issue