diff --git a/internal/testext/testext.go b/internal/testext/testext.go new file mode 100644 index 0000000..214cd56 --- /dev/null +++ b/internal/testext/testext.go @@ -0,0 +1,102 @@ +// Package testext 给本库自己的测试提供一个**有状态的**扩展。 +// +// 本库只定义 Extension 接口,不带任何内置扩展——扩展该由用引擎的人按自己的 +// 场景定义(框架侧的那套见 framework 的 internal/jscript/ext)。但测"作用域 +// 共享状态""扩展改名""VM 复用后状态还在不在"这些行为,手上得有一个真的扩展, +// 所以这里放一个最小的。 +// +// 它只为测试存在,不是给外部用的示范——真要写扩展看 Extension 接口的文档。 +package testext + +import ( + "sync" + + "git.fsdpf.net/go/jscriptx" +) + +// Store 是一份并发安全的键值表,同时也是一个 jscriptx 扩展。 +type Store struct { + name string + + mu sync.RWMutex + data map[string]any +} + +// New 造一个,全局名默认叫 store。 +func New() *Store { return &Store{name: "store", data: map[string]any{}} } + +// Named 造一个用别的全局名的,用来测改名。 +func Named(name string) *Store { return &Store{name: name, data: map[string]any{}} } + +func (s *Store) Name() string { return s.name } + +func (s *Store) Module() (string, string) { return "@jscriptx/store", typings } + +func (s *Store) Bindings() map[string]any { + return map[string]any{ + "Get": s.Get, + "GetOr": s.GetOr, + "Set": s.Set, + "Incr": s.Incr, + "Len": s.Len, + } +} + +// Get 取值,没有返回 nil(到脚本里是 null)。 +func (s *Store) Get(key string) any { + s.mu.RLock() + defer s.mu.RUnlock() + return s.data[key] +} + +// GetOr 取值,没有返回 fallback。 +func (s *Store) GetOr(key string, fallback any) any { + s.mu.RLock() + defer s.mu.RUnlock() + if v, ok := s.data[key]; ok { + return v + } + return fallback +} + +// Set 写值。 +func (s *Store) Set(key string, v any) { + s.mu.Lock() + defer s.mu.Unlock() + s.data[key] = v +} + +// Incr 累加,返回加完的值。键不存在按 0 算。 +// +// 用 float64 而不是整数:JS 的数字就是 float64,脚本写进来的值也是这个类型, +// 用整数会让"Go 侧读回来"的断言在类型上对不上。 +func (s *Store) Incr(key string, delta float64) float64 { + s.mu.Lock() + defer s.mu.Unlock() + + n, _ := s.data[key].(float64) + n += delta + s.data[key] = n + return n +} + +// Len 返回键的个数。 +func (s *Store) Len() int { + s.mu.RLock() + defer s.mu.RUnlock() + return len(s.data) +} + +var _ jscriptx.Extension = (*Store)(nil) + +const typings = `export interface Store { + Get(key: string): any + GetOr(key: string, fallback: any): any + Set(key: string, value: any): void + Incr(key: string, delta: number): number + Len(): number +} + +declare const store: Store +export default store +`