Pure package detail
lplist
gno.land/p/moul/ulist/lplist/v1
Indexed deployment identity with independently loaded latest RPC source. Functions and Render are realm-only RPC capabilities.
Indexed deployment
Identity
- Package path
- gno.land/p/moul/ulist/lplist/v1
- Block
- 23360
- Deployed (UTC)
- Transaction
- gSxct6OE8ayzLBPCA2DYUz0jNura3WYbiUKNJ3k9Wro=
Latest RPC state
Source
// Package lplist provides a layered proxy implementation for lists that allows transparent
// migration of data between different schema versions.
//
// LayeredProxyList wraps an existing list (source) with a new list (target) and optionally
// applies migrations to source data when it's accessed. This enables schema evolution without
// requiring upfront migration of all data, making it ideal for large datasets or when
// preserving original data is important.
//
// Key features:
// - Lazy migration: Data is only transformed when accessed, not stored in migrated form
// - Append-only source: Source data is treated as immutable to preserve original data
// - Chaining: Multiple LayeredProxyLists can be stacked for multi-step migrations
//
// Example usage:
//
// // Define data types for different schema versions
// type UserV1 struct {
// Name string
// Age int
// }
//
// type UserV2 struct {
// FullName string
// Age int
// Active bool
// }
//
// // Create source list with old schema
// sourceList := ulist.New()
// sourceList.Append(
// UserV1{Name: "Alice", Age: 30},
// UserV1{Name: "Bob", Age: 25},
// )
//
// // Define migration function from V1 to V2
// migrateUserV1ToV2 := func(v any) any {
// user := v.(UserV1)
// return UserV2{
// FullName: user.Name, // Name field renamed to FullName
// Age: user.Age,
// Active: true, // New field with default value
// }
// }
//
// // Create layered proxy with migration
// proxy := NewLayeredProxyList(sourceList, migrateUserV1ToV2)
//
// // Add new data directly in V2 format
// proxy.Append(UserV2{FullName: "Charlie", Age: 40, Active: false})
//
// // All access through proxy returns data in V2 format
// for i := 0; i < proxy.Size(); i++ {
// user := proxy.Get(i).(UserV2)
// fmt.Printf("User: %s, Age: %d, Active: %t\n", user.FullName, user.Age, user.Active)
// }
package lplist
import (
"errors"
"gno.land/p/moul/ulist/v1"
)
// MigratorFn is a function type that lazily converts values from source to target
type MigratorFn func(any) any
// LayeredProxyList represents a wrapper around an existing List that handles migration
type LayeredProxyList struct {
source ulist.IList
target *ulist.List
migrator MigratorFn
sourceHeight int // Store initial source size to optimize lookups
}
// NewLayeredProxyList creates a new LayeredProxyList instance that wraps an existing List
func NewLayeredProxyList(source ulist.IList, migrator MigratorFn) *LayeredProxyList {
sourceHeight := source.TotalSize()
target := ulist.New()
return &LayeredProxyList{
source: source,
target: target,
migrator: migrator,
sourceHeight: sourceHeight,
}
}
// Get retrieves the value at the specified index
// Uses sourceHeight to efficiently route requests
func (l *LayeredProxyList) Get(index int) any {
if index < l.sourceHeight {
// Direct access to source for indices below sourceHeight
val := l.source.Get(index)
if val == nil {
return nil
}
// Only apply migrator if it exists
if l.migrator != nil {
return l.migrator(val)
}
return val
}
// For indices >= sourceHeight, adjust index to be relative to target list starting at 0
targetIndex := index - l.sourceHeight
return l.target.Get(targetIndex)
}
// Append adds one or more values to the target list
func (l *LayeredProxyList) Append(values ...any) {
l.target.Append(values...)
}
// Delete marks elements as deleted in the appropriate list
func (l *LayeredProxyList) Delete(indices ...int) error {
for _, index := range indices {
if index < l.sourceHeight {
err := l.source.Delete(index)
if err != nil {
return err
}
}
}
for _, index := range indices {
targetIndex := index - l.sourceHeight
err := l.target.Delete(targetIndex)
if err != nil {
return err
}
}
return nil
}
// Size returns the total number of active elements
func (l *LayeredProxyList) Size() int {
return l.source.Size() + l.target.Size()
}
// TotalSize returns the total number of elements in the list
func (l *LayeredProxyList) TotalSize() int {
return l.sourceHeight + l.target.TotalSize()
}
// MustDelete deletes elements, panicking on error
func (l *LayeredProxyList) MustDelete(indices ...int) {
if err := l.Delete(indices...); err != nil {
panic(err)
}
}
// MustGet retrieves a value, panicking if not found
func (l *LayeredProxyList) MustGet(index int) any {
val := l.Get(index)
if val == nil {
panic(ulist.ErrDeleted)
}
return val
}
// GetRange returns elements between start and end indices
func (l *LayeredProxyList) GetRange(start, end int) []ulist.Entry {
var entries []ulist.Entry
l.Iterator(start, end, func(index int, value any) bool {
entries = append(entries, ulist.Entry{Index: index, Value: value})
return false
})
return entries
}
// GetRangeByOffset returns elements starting from offset
func (l *LayeredProxyList) GetRangeByOffset(offset int, count int) []ulist.Entry {
var entries []ulist.Entry
l.IteratorByOffset(offset, count, func(index int, value any) bool {
entries = append(entries, ulist.Entry{Index: index, Value: value})
return false
})
return entries
}
// Iterator performs iteration between start and end indices
func (l *LayeredProxyList) Iterator(start, end int, cb ulist.IterCbFn) bool {
// For empty list or invalid range
if start < 0 && end < 0 {
return false
}
// Normalize indices
if start < 0 {
start = 0
}
if end < 0 {
end = 0
}
totalSize := l.TotalSize()
if end >= totalSize {
end = totalSize - 1
}
if start >= totalSize {
start = totalSize - 1
}
// Handle reverse iteration
if start > end {
for i := start; i >= end; i-- {
val := l.Get(i)
if val != nil {
if cb(i, val) {
return true
}
}
}
return false
}
// Handle forward iteration
for i := start; i <= end; i++ {
val := l.Get(i)
if val != nil {
if cb(i, val) {
return true
}
}
}
return false
}
// IteratorByOffset performs iteration starting from offset
func (l *LayeredProxyList) IteratorByOffset(offset int, count int, cb ulist.IterCbFn) bool {
if count == 0 {
return false
}
// Normalize offset
if offset < 0 {
offset = 0
}
totalSize := l.TotalSize()
if offset >= totalSize {
offset = totalSize - 1
}
// Determine end based on count direction
var end int
if count > 0 {
end = totalSize - 1
} else {
end = 0
}
wrapperReturned := false
// Calculate absolute value manually instead of using abs function
remaining := count
if remaining < 0 {
remaining = -remaining
}
wrapper := func(index int, value any) bool {
if remaining <= 0 {
wrapperReturned = true
return true
}
remaining--
return cb(index, value)
}
ret := l.Iterator(offset, end, wrapper)
if wrapperReturned {
return false
}
return ret
}
// Set updates the value at the specified index
func (l *LayeredProxyList) Set(index int, value any) error {
if index < l.sourceHeight {
// Cannot modify source list directly
return errors.New("cannot modify source list directly")
}
// Adjust index to be relative to target list starting at 0
targetIndex := index - l.sourceHeight
return l.target.Set(targetIndex, value)
}
// MustSet updates the value at the specified index, panicking on error
func (l *LayeredProxyList) MustSet(index int, value any) {
if err := l.Set(index, value); err != nil {
panic(err)
}
}
// GetByOffset returns elements starting from offset with count determining direction
func (l *LayeredProxyList) GetByOffset(offset int, count int) []ulist.Entry {
var entries []ulist.Entry
l.IteratorByOffset(offset, count, func(index int, value any) bool {
entries = append(entries, ulist.Entry{Index: index, Value: value})
return false
})
return entries
}
// Verify that LayeredProxyList implements IList
var _ ulist.IList = (*LayeredProxyList)(nil)
The verified vm/qfuncs operation accepts realm paths only.
Pure packages expose source files but do not have Realm Render.