mirror of
https://github.com/Rain-kl/OpenFlare.git
synced 2026-09-28 05:46:36 +08:00
feat(core): implement scoped revertible effects for context extpoints
This commit is contained in:
+24
-11
@@ -175,9 +175,22 @@ func (c *Context) Events() *EventBus {
|
||||
return c.events
|
||||
}
|
||||
|
||||
// Router returns the RouterExtension registry.
|
||||
// On registers an event listener on the EventBus and automatically attaches its Disposer
|
||||
// to this Context's teardown stack for automatic revocation when disposed.
|
||||
func (c *Context) On(topic string, handler any) Disposer {
|
||||
disposer := c.events.On(topic, handler)
|
||||
c.OnDispose(disposer)
|
||||
return disposer
|
||||
}
|
||||
|
||||
// Effect registers a reversible side-effect cleanup callback on this Context.
|
||||
func (c *Context) Effect(fn any) {
|
||||
c.OnDispose(fn)
|
||||
}
|
||||
|
||||
// Router returns the scoped RouterExtension registry with automatic disposer tracking.
|
||||
func (c *Context) Router() extpoints.RouterExtension {
|
||||
return c.router
|
||||
return newScopedRouterExtension(c, c.router)
|
||||
}
|
||||
|
||||
// Migrations returns the MigrationExtension registry.
|
||||
@@ -185,34 +198,34 @@ func (c *Context) Migrations() extpoints.MigrationExtension {
|
||||
return c.migrations
|
||||
}
|
||||
|
||||
// Tasks returns the TaskExtension registry.
|
||||
// Tasks returns the scoped TaskExtension registry with automatic disposer tracking.
|
||||
func (c *Context) Tasks() extpoints.TaskExtension {
|
||||
return c.tasks
|
||||
return newScopedTaskExtension(c, c.tasks)
|
||||
}
|
||||
|
||||
// Task is an alias for Tasks().
|
||||
func (c *Context) Task() extpoints.TaskExtension {
|
||||
return c.tasks
|
||||
return c.Tasks()
|
||||
}
|
||||
|
||||
// Schedules returns the ScheduleExtension registry.
|
||||
// Schedules returns the scoped ScheduleExtension registry with automatic disposer tracking.
|
||||
func (c *Context) Schedules() extpoints.ScheduleExtension {
|
||||
return c.schedules
|
||||
return newScopedScheduleExtension(c, c.schedules)
|
||||
}
|
||||
|
||||
// Schedule is an alias for Schedules().
|
||||
func (c *Context) Schedule() extpoints.ScheduleExtension {
|
||||
return c.schedules
|
||||
return c.Schedules()
|
||||
}
|
||||
|
||||
// Settings returns the SettingExtension registry.
|
||||
// Settings returns the scoped SettingExtension registry with automatic disposer tracking.
|
||||
func (c *Context) Settings() extpoints.SettingExtension {
|
||||
return c.settings
|
||||
return newScopedSettingExtension(c, c.settings)
|
||||
}
|
||||
|
||||
// Setting is an alias for Settings().
|
||||
func (c *Context) Setting() extpoints.SettingExtension {
|
||||
return c.settings
|
||||
return c.Settings()
|
||||
}
|
||||
|
||||
// DB returns the contracts.DBService registered in the IoC container, or nil if not registered.
|
||||
|
||||
@@ -491,12 +491,45 @@ func TestContextExtensionPointsAccessors(t *testing.T) {
|
||||
|
||||
child := ctx.Fork()
|
||||
assert.Equal(t, ctx.Events(), child.Events())
|
||||
assert.Equal(t, ctx.Router(), child.Router())
|
||||
assert.Equal(t, ctx.Migrations(), child.Migrations())
|
||||
assert.Equal(t, ctx.Tasks(), child.Tasks())
|
||||
assert.Equal(t, ctx.Task(), child.Task())
|
||||
assert.Equal(t, ctx.Schedules(), child.Schedules())
|
||||
assert.Equal(t, ctx.Schedule(), child.Schedule())
|
||||
assert.Equal(t, ctx.Settings(), child.Settings())
|
||||
assert.Equal(t, ctx.Setting(), child.Setting())
|
||||
assert.NotNil(t, child.Router())
|
||||
assert.NotNil(t, child.Tasks())
|
||||
assert.NotNil(t, child.Task())
|
||||
assert.NotNil(t, child.Schedules())
|
||||
assert.NotNil(t, child.Schedule())
|
||||
assert.NotNil(t, child.Settings())
|
||||
assert.NotNil(t, child.Setting())
|
||||
}
|
||||
|
||||
func TestContext_ScopedExtpoints_RevertibleEffects(t *testing.T) {
|
||||
root := core.NewContext(context.Background())
|
||||
child := root.Fork()
|
||||
|
||||
// Register route, task, schedule, setting, event on child
|
||||
child.Router().GET("/test-route", func() {})
|
||||
assert.Equal(t, 1, len(root.Router().Routes()))
|
||||
|
||||
child.Tasks().Register("test:task", func() {})
|
||||
assert.Equal(t, 1, len(root.Tasks().Tasks()))
|
||||
|
||||
child.Schedules().RegisterCron("@hourly", "test:cron", nil)
|
||||
assert.Equal(t, 1, len(root.Schedules().Schedules()))
|
||||
|
||||
child.Settings().Register(core.SettingSchema{Key: "test.key", Default: "val"})
|
||||
assert.Equal(t, 1, len(root.Settings().Schemas()))
|
||||
|
||||
child.On("test:event", func() {})
|
||||
assert.Equal(t, 1, root.Events().Listeners("test:event"))
|
||||
|
||||
// Dispose child
|
||||
err := child.Dispose()
|
||||
assert.NoError(t, err)
|
||||
|
||||
// All child effects should be cleanly revoked in LIFO order
|
||||
assert.Equal(t, 0, len(root.Router().Routes()))
|
||||
assert.Equal(t, 0, len(root.Tasks().Tasks()))
|
||||
assert.Equal(t, 0, len(root.Schedules().Schedules()))
|
||||
assert.Equal(t, 0, len(root.Settings().Schemas()))
|
||||
assert.Equal(t, 0, root.Events().Listeners("test:event"))
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,201 @@
|
||||
// Copyright 2026 Arctel.net
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package core
|
||||
|
||||
import (
|
||||
"Wavelet/core/extpoints"
|
||||
)
|
||||
|
||||
// scopedRouterExtension wraps a RouterExtension to automatically register
|
||||
// teardown disposers on the associated Context when routes are declared.
|
||||
type scopedRouterExtension struct {
|
||||
underlying extpoints.RouterExtension
|
||||
ctx *Context
|
||||
}
|
||||
|
||||
func newScopedRouterExtension(ctx *Context, underlying extpoints.RouterExtension) extpoints.RouterExtension {
|
||||
return &scopedRouterExtension{
|
||||
underlying: underlying,
|
||||
ctx: ctx,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *scopedRouterExtension) Use(middlewares ...any) {
|
||||
s.underlying.Use(middlewares...)
|
||||
}
|
||||
|
||||
func (s *scopedRouterExtension) Group(prefix string, middlewares ...any) extpoints.RouterExtension {
|
||||
subGroup := s.underlying.Group(prefix, middlewares...)
|
||||
return newScopedRouterExtension(s.ctx, subGroup)
|
||||
}
|
||||
|
||||
func (s *scopedRouterExtension) Handle(method string, path string, handlers ...any) extpoints.RouteDefinition {
|
||||
rd := s.underlying.Handle(method, path, handlers...)
|
||||
routeID := rd.ID
|
||||
s.ctx.OnDispose(func() error {
|
||||
s.underlying.UnregisterByID(routeID)
|
||||
return nil
|
||||
})
|
||||
return rd
|
||||
}
|
||||
|
||||
func (s *scopedRouterExtension) GET(path string, handlers ...any) extpoints.RouteDefinition {
|
||||
return s.Handle("GET", path, handlers...)
|
||||
}
|
||||
|
||||
func (s *scopedRouterExtension) POST(path string, handlers ...any) extpoints.RouteDefinition {
|
||||
return s.Handle("POST", path, handlers...)
|
||||
}
|
||||
|
||||
func (s *scopedRouterExtension) PUT(path string, handlers ...any) extpoints.RouteDefinition {
|
||||
return s.Handle("PUT", path, handlers...)
|
||||
}
|
||||
|
||||
func (s *scopedRouterExtension) DELETE(path string, handlers ...any) extpoints.RouteDefinition {
|
||||
return s.Handle("DELETE", path, handlers...)
|
||||
}
|
||||
|
||||
func (s *scopedRouterExtension) PATCH(path string, handlers ...any) extpoints.RouteDefinition {
|
||||
return s.Handle("PATCH", path, handlers...)
|
||||
}
|
||||
|
||||
func (s *scopedRouterExtension) HEAD(path string, handlers ...any) extpoints.RouteDefinition {
|
||||
return s.Handle("HEAD", path, handlers...)
|
||||
}
|
||||
|
||||
func (s *scopedRouterExtension) OPTIONS(path string, handlers ...any) extpoints.RouteDefinition {
|
||||
return s.Handle("OPTIONS", path, handlers...)
|
||||
}
|
||||
|
||||
func (s *scopedRouterExtension) Any(path string, handlers ...any) []extpoints.RouteDefinition {
|
||||
methods := []string{"GET", "POST", "PUT", "DELETE", "PATCH", "HEAD", "OPTIONS"}
|
||||
defs := make([]extpoints.RouteDefinition, 0, len(methods))
|
||||
for _, m := range methods {
|
||||
defs = append(defs, s.Handle(m, path, handlers...))
|
||||
}
|
||||
return defs
|
||||
}
|
||||
|
||||
func (s *scopedRouterExtension) Routes() []extpoints.RouteDefinition {
|
||||
return s.underlying.Routes()
|
||||
}
|
||||
|
||||
func (s *scopedRouterExtension) Middlewares() []any {
|
||||
return s.underlying.Middlewares()
|
||||
}
|
||||
|
||||
func (s *scopedRouterExtension) Unregister(method, path string) bool {
|
||||
return s.underlying.Unregister(method, path)
|
||||
}
|
||||
|
||||
func (s *scopedRouterExtension) UnregisterByID(id uint64) bool {
|
||||
return s.underlying.UnregisterByID(id)
|
||||
}
|
||||
|
||||
// scopedTaskExtension wraps a TaskExtension to automatically register
|
||||
// teardown disposers on the associated Context when task handlers are declared.
|
||||
type scopedTaskExtension struct {
|
||||
underlying extpoints.TaskExtension
|
||||
ctx *Context
|
||||
}
|
||||
|
||||
func newScopedTaskExtension(ctx *Context, underlying extpoints.TaskExtension) extpoints.TaskExtension {
|
||||
return &scopedTaskExtension{
|
||||
underlying: underlying,
|
||||
ctx: ctx,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *scopedTaskExtension) Register(pattern string, handler any, opts ...extpoints.TaskOption) {
|
||||
s.underlying.Register(pattern, handler, opts...)
|
||||
s.ctx.OnDispose(func() error {
|
||||
s.underlying.Unregister(pattern)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (s *scopedTaskExtension) Tasks() []extpoints.TaskDefinition {
|
||||
return s.underlying.Tasks()
|
||||
}
|
||||
|
||||
func (s *scopedTaskExtension) Get(pattern string) (extpoints.TaskDefinition, bool) {
|
||||
return s.underlying.Get(pattern)
|
||||
}
|
||||
|
||||
func (s *scopedTaskExtension) Unregister(pattern string) bool {
|
||||
return s.underlying.Unregister(pattern)
|
||||
}
|
||||
|
||||
// scopedScheduleExtension wraps a ScheduleExtension to automatically register
|
||||
// teardown disposers on the associated Context when cron/scheduled tasks are declared.
|
||||
type scopedScheduleExtension struct {
|
||||
underlying extpoints.ScheduleExtension
|
||||
ctx *Context
|
||||
}
|
||||
|
||||
func newScopedScheduleExtension(ctx *Context, underlying extpoints.ScheduleExtension) extpoints.ScheduleExtension {
|
||||
return &scopedScheduleExtension{
|
||||
underlying: underlying,
|
||||
ctx: ctx,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *scopedScheduleExtension) Register(spec string, taskType string, payload any, opts ...extpoints.ScheduleOption) {
|
||||
s.underlying.Register(spec, taskType, payload, opts...)
|
||||
s.ctx.OnDispose(func() error {
|
||||
s.underlying.Unregister(taskType)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (s *scopedScheduleExtension) RegisterCron(spec string, taskType string, payload any, opts ...extpoints.ScheduleOption) {
|
||||
s.Register(spec, taskType, payload, opts...)
|
||||
}
|
||||
|
||||
func (s *scopedScheduleExtension) Schedules() []extpoints.ScheduleDefinition {
|
||||
return s.underlying.Schedules()
|
||||
}
|
||||
|
||||
func (s *scopedScheduleExtension) Get(taskType string) (extpoints.ScheduleDefinition, bool) {
|
||||
return s.underlying.Get(taskType)
|
||||
}
|
||||
|
||||
func (s *scopedScheduleExtension) Unregister(taskType string) bool {
|
||||
return s.underlying.Unregister(taskType)
|
||||
}
|
||||
|
||||
// scopedSettingExtension wraps a SettingExtension to automatically register
|
||||
// teardown disposers on the associated Context when settings schemas are declared.
|
||||
type scopedSettingExtension struct {
|
||||
underlying extpoints.SettingExtension
|
||||
ctx *Context
|
||||
}
|
||||
|
||||
func newScopedSettingExtension(ctx *Context, underlying extpoints.SettingExtension) extpoints.SettingExtension {
|
||||
return &scopedSettingExtension{
|
||||
underlying: underlying,
|
||||
ctx: ctx,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *scopedSettingExtension) Register(schema extpoints.SettingSchema) {
|
||||
s.underlying.Register(schema)
|
||||
key := schema.Key
|
||||
s.ctx.OnDispose(func() error {
|
||||
s.underlying.Unregister(key)
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func (s *scopedSettingExtension) Schemas() []extpoints.SettingSchema {
|
||||
return s.underlying.Schemas()
|
||||
}
|
||||
|
||||
func (s *scopedSettingExtension) Get(key string) (extpoints.SettingSchema, bool) {
|
||||
return s.underlying.Get(key)
|
||||
}
|
||||
|
||||
func (s *scopedSettingExtension) Unregister(key string) bool {
|
||||
return s.underlying.Unregister(key)
|
||||
}
|
||||
Reference in New Issue
Block a user