You've already forked pocketbase
							
							
				mirror of
				https://github.com/pocketbase/pocketbase.git
				synced 2025-10-31 08:37:38 +02:00 
			
		
		
		
	updated WIP:v0.9.0 changelog
This commit is contained in:
		
							
								
								
									
										77
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										77
									
								
								CHANGELOG.md
									
									
									
									
									
								
							| @@ -1,6 +1,78 @@ | ||||
| ## (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 | ||||
|     // Old: | ||||
|     app.NewMailClient().Send(from, to, subject, html, attachments?) | ||||
| @@ -19,8 +91,7 @@ | ||||
|       Text: "custom plain text version", | ||||
|     }) | ||||
|     ``` | ||||
|  | ||||
| - Added the new `*mailer.Message` to the `MailerRecordEvent`, `MailerAdminEvent` event structs. | ||||
|     The new `*mailer.Message` struct is also now a member of the `MailerRecordEvent` and `MailerAdminEvent` events. | ||||
|  | ||||
|  | ||||
| ## v0.8.0 | ||||
|   | ||||
| @@ -15,9 +15,15 @@ import ( | ||||
| func NewBaseVM(app core.App) *goja.Runtime { | ||||
| 	vm := goja.New() | ||||
| 	vm.SetFieldNameMapper(goja.UncapFieldNameMapper()) | ||||
|  | ||||
| 	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) { | ||||
| 		raw, err := json.Marshal(src) | ||||
| 		if err != nil { | ||||
| @@ -31,59 +37,41 @@ func NewBaseVM(app core.App) *goja.Runtime { | ||||
| 		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 { | ||||
| 		instance := &models.Collection{} | ||||
| 		instanceValue := vm.ToValue(instance).(*goja.Object) | ||||
| 		instanceValue.SetPrototype(call.This.Prototype()) | ||||
| 		return instanceValue | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| func recordConstructor(vm *goja.Runtime) { | ||||
| 	vm.Set("Record", func(call goja.ConstructorCall) *goja.Object { | ||||
| 		instance := &models.Record{} | ||||
| 		instanceValue := vm.ToValue(instance).(*goja.Object) | ||||
| 		instanceValue.SetPrototype(call.This.Prototype()) | ||||
| 		return instanceValue | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| func adminConstructor(vm *goja.Runtime) { | ||||
| 	vm.Set("Admin", func(call goja.ConstructorCall) *goja.Object { | ||||
| 		instance := &models.Admin{} | ||||
| 		instanceValue := vm.ToValue(instance).(*goja.Object) | ||||
| 		instanceValue.SetPrototype(call.This.Prototype()) | ||||
| 		return instanceValue | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| func schemaConstructor(vm *goja.Runtime) { | ||||
| 	vm.Set("Schema", func(call goja.ConstructorCall) *goja.Object { | ||||
| 		instance := &schema.Schema{} | ||||
| 		instanceValue := vm.ToValue(instance).(*goja.Object) | ||||
| 		instanceValue.SetPrototype(call.This.Prototype()) | ||||
| 		return instanceValue | ||||
| 	}) | ||||
|  | ||||
| 	vm.Set("SchemaField", func(call goja.ConstructorCall) *goja.Object { | ||||
| 		instance := &schema.SchemaField{} | ||||
| 		instanceValue := vm.ToValue(instance).(*goja.Object) | ||||
| 		instanceValue.SetPrototype(call.This.Prototype()) | ||||
| 		return instanceValue | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| func daoConstructor(vm *goja.Runtime) { | ||||
| 	vm.Set("Dao", func(call goja.ConstructorCall) *goja.Object { | ||||
| 		db, ok := call.Argument(0).Export().(dbx.Builder) | ||||
| 		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() | ||||
| 	vm.Set("$dbx", obj) | ||||
|  | ||||
| @@ -145,7 +133,6 @@ func apisBind(vm *goja.Runtime) { | ||||
| 	obj.Set("unauthorizedError", apis.NewUnauthorizedError) | ||||
|  | ||||
| 	// record helpers | ||||
| 	obj.Set("getRequestData", apis.GetRequestData) | ||||
| 	obj.Set("requestData", apis.RequestData) | ||||
| 	obj.Set("enrichRecord", apis.EnrichRecord) | ||||
| 	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 { | ||||
| 	const cmdDesc = `Supported arguments are: | ||||
| - up                   - runs all available migrations | ||||
| - down [number]        - reverts the last [number] applied migrations | ||||
| - create name [folder] - 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) | ||||
| - up            - runs all available migrations | ||||
| - down [number] - reverts the last [number] applied migrations | ||||
| - create name   - creates new blank migration template file | ||||
| - collections   - creates new migration file with snapshot of the local collections configuration | ||||
| ` | ||||
|  | ||||
| 	command := &cobra.Command{ | ||||
| @@ -143,14 +143,7 @@ func (p *plugin) migrateCreateHandler(template string, args []string) error { | ||||
| 	} | ||||
|  | ||||
| 	name := args[0] | ||||
|  | ||||
| 	var dir string | ||||
| 	if len(args) == 2 { | ||||
| 		dir = args[1] | ||||
| 	} | ||||
| 	if dir == "" { | ||||
| 		dir = p.options.Dir | ||||
| 	} | ||||
| 	dir := p.options.Dir | ||||
|  | ||||
| 	resultFilePath := path.Join( | ||||
| 		dir, | ||||
|   | ||||
| @@ -164,7 +164,7 @@ func init() { | ||||
|  | ||||
| 		expectedName := "_created_new_name." + s.lang | ||||
| 		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()) | ||||
| @@ -335,7 +335,7 @@ func init() { | ||||
|  | ||||
| 		expectedName := "_deleted_test456." + s.lang | ||||
| 		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()) | ||||
| @@ -681,7 +681,7 @@ func init() { | ||||
|  | ||||
| 		expectedName := "_updated_test456." + s.lang | ||||
| 		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()) | ||||
|   | ||||
| @@ -648,27 +648,29 @@ func (p *plugin) goDiffTemplate(new *models.Collection, old *models.Collection) | ||||
|  | ||||
| 	up := strings.Join(upParts, "\n\t\t") | ||||
| 	down := strings.Join(downParts, "\n\t\t") | ||||
|  | ||||
| 	var optImports string | ||||
|  | ||||
| 	combined := up + down | ||||
|  | ||||
| 	// generate imports | ||||
| 	// --- | ||||
| 	var imports string | ||||
|  | ||||
| 	if strings.Contains(combined, "json.Unmarshal(") || | ||||
| 		strings.Contains(combined, "json.Marshal(") { | ||||
| 		optImports += "\n\t\"encoding/json\"\n" | ||||
| 		imports += "\n\t\"encoding/json\"\n" | ||||
| 	} | ||||
|  | ||||
| 	optImports += "\n\t\"github.com/pocketbase/dbx\"" | ||||
| 	optImports += "\n\t\"github.com/pocketbase/pocketbase/daos\"" | ||||
| 	optImports += "\n\tm \"github.com/pocketbase/pocketbase/migrations\"" | ||||
| 	imports += "\n\t\"github.com/pocketbase/dbx\"" | ||||
| 	imports += "\n\t\"github.com/pocketbase/pocketbase/daos\"" | ||||
| 	imports += "\n\tm \"github.com/pocketbase/pocketbase/migrations\"" | ||||
|  | ||||
| 	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(") { | ||||
| 		optImports += "\n\t\"github.com/pocketbase/pocketbase/tools/types\"" | ||||
| 		imports += "\n\t\"github.com/pocketbase/pocketbase/tools/types\"" | ||||
| 	} | ||||
| 	// --- | ||||
|  | ||||
| 	const template = `package %s | ||||
|  | ||||
| @@ -705,7 +707,7 @@ func init() { | ||||
| 	return fmt.Sprintf( | ||||
| 		template, | ||||
| 		filepath.Base(p.options.Dir), | ||||
| 		optImports, | ||||
| 		imports, | ||||
| 		old.Id, strings.TrimSpace(up), | ||||
| 		new.Id, strings.TrimSpace(down), | ||||
| 	), nil | ||||
|   | ||||
		Reference in New Issue
	
	Block a user