├── Makefile ├── id.go ├── go.mod ├── .gitignore ├── id_test.go ├── y_xml_event.go ├── skip.go ├── LICENSE ├── y_xml_hook.go ├── gc.go ├── event_handler.go ├── const.go ├── abstract_struct.go ├── observable.go ├── content_embed.go ├── abstract_content.go ├── content_binary.go ├── update_encoder_decoder_v2.go ├── content_format.go ├── content_deleted.go ├── y_string.go ├── content_any.go ├── content_doc.go ├── content_json.go ├── content_string.go ├── y_xml_text.go ├── update_encoder_v1.go ├── content_type.go ├── update_decoder_v1.go ├── type_define.go ├── snapshot.go ├── encoding_test.go ├── y_xml_element.go ├── y_array.go ├── permanent_user_data.go ├── utils_test.go ├── encoding.go ├── y_map.go ├── struct_store.go ├── doc.go ├── y_xml_fragment.go ├── y_event.go ├── relative_position.go ├── update_decoder_test.go ├── compatibility_test.go ├── delete_set.go ├── undo_manager.go ├── decoding.go ├── utils.go ├── README.md └── transaction.go /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: test 2 | 3 | # run unit test 4 | test: 5 | go test ./... 6 | 7 | # generate coverage statistics. 8 | cover: 9 | go test ./... -coverprofile cover.out 10 | 11 | # generate coverage statistics and open it in the browser. 12 | report: cover 13 | go tool cover -html=cover.out -------------------------------------------------------------------------------- /id.go: -------------------------------------------------------------------------------- 1 | package y_crdt 2 | 3 | type ID struct { 4 | AbstractType 5 | Client Number // client ID 6 | Clock Number // unique per client id, continuous number 7 | } 8 | 9 | // GenID generates a new ID with the given client and clock values. 10 | func GenID(client Number, clock Number) ID { 11 | return ID{ 12 | Client: client, 13 | Clock: clock, 14 | } 15 | } 16 | 17 | // CompareIDs compares two IDs for equality. 18 | func CompareIDs(a *ID, b *ID) bool { 19 | return a == b || (a != nil && b != nil && a.Client == b.Client && a.Clock == b.Clock) 20 | } 21 | 22 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/skyterra/y-crdt 2 | 3 | go 1.24.3 4 | 5 | require ( 6 | github.com/bytedance/mockey v1.2.13 7 | github.com/mitchellh/copystructure v1.2.0 8 | ) 9 | 10 | require ( 11 | github.com/gopherjs/gopherjs v1.12.80 // indirect 12 | github.com/jtolds/gls v4.20.0+incompatible // indirect 13 | github.com/mitchellh/reflectwalk v1.0.2 // indirect 14 | github.com/smartystreets/assertions v1.2.0 // indirect 15 | github.com/smartystreets/goconvey v1.7.2 // indirect 16 | golang.org/x/arch v0.11.0 // indirect 17 | golang.org/x/sys v0.26.0 // indirect 18 | ) 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # If you prefer the allow list template instead of the deny list, see community template: 2 | # https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore 3 | # 4 | # Binaries for programs and plugins 5 | *.exe 6 | *.exe~ 7 | *.dll 8 | *.so 9 | *.dylib 10 | 11 | # Test binary, built with `go test -c` 12 | *.test 13 | 14 | # Output of the go coverage tool, specifically when used with LiteIDE 15 | *.out 16 | 17 | # Dependency directories (remove the comment below to include it) 18 | # vendor/ 19 | 20 | # Go workspace file 21 | go.work 22 | go.work.sum 23 | 24 | # env file 25 | .env 26 | -------------------------------------------------------------------------------- /id_test.go: -------------------------------------------------------------------------------- 1 | package y_crdt 2 | 3 | import "testing" 4 | 5 | func TestGenID(t *testing.T) { 6 | id := GenID(1, 2) 7 | if id.Client != 1 { 8 | t.Errorf("id.Client = %d, want 1", id.Client) 9 | } 10 | 11 | if id.Clock != 2 { 12 | t.Errorf("id.Clock = %d, want 2", id.Clock) 13 | } 14 | } 15 | 16 | func TestCompareIDs(t *testing.T) { 17 | id1 := GenID(1, 2) 18 | id2 := GenID(1, 3) 19 | id3 := GenID(1, 2) 20 | if CompareIDs(&id1, &id2) { 21 | t.Error("CompareIDs(id1, id2) = true, want false") 22 | } 23 | 24 | if !CompareIDs(&id1, &id3) { 25 | t.Error("CompareIDs(id1, id3) = false, want true") 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /y_xml_event.go: -------------------------------------------------------------------------------- 1 | package y_crdt 2 | 3 | // YXmlEvent An Event that describes changes on a YXml Element or Yxml Fragment 4 | type YXmlEvent struct { 5 | YEvent 6 | ChildListChanged bool // Whether the children changed. 7 | AttributesChanged Set // Set of all changed attributes. 8 | } 9 | 10 | func NewYXmlEvent(target IAbstractType, subs Set, trans *Transaction) *YXmlEvent { 11 | e := &YXmlEvent{ 12 | YEvent: *NewYEvent(target, trans), 13 | ChildListChanged: false, 14 | AttributesChanged: NewSet(), 15 | } 16 | 17 | subs.Range(func(element interface{}) { 18 | if element == nil { 19 | e.ChildListChanged = true 20 | } else { 21 | e.AttributesChanged.Add(element) 22 | } 23 | }) 24 | 25 | return e 26 | } 27 | -------------------------------------------------------------------------------- /skip.go: -------------------------------------------------------------------------------- 1 | package y_crdt 2 | 3 | import "errors" 4 | 5 | const StructSkipRefNumber = 10 6 | 7 | type Skip struct { 8 | AbstractStruct 9 | } 10 | 11 | func (s *Skip) Deleted() bool { 12 | return true 13 | } 14 | 15 | func (s *Skip) Delete() { 16 | 17 | } 18 | 19 | func (s *Skip) MergeWith(right IAbstractStruct) bool { 20 | r, ok := right.(*Skip) 21 | if !ok { 22 | return false 23 | } 24 | 25 | s.Length += r.Length 26 | return true 27 | } 28 | 29 | func (s *Skip) Integrate(trans *Transaction, offset Number) { 30 | return 31 | } 32 | 33 | func (s *Skip) Write(encoder *UpdateEncoderV1, offset Number) { 34 | encoder.WriteInfo(StructSkipRefNumber) 35 | // write as VarUint because Skips can't make use of predictable length-encoding 36 | WriteVarUint(encoder.RestEncoder, uint64(s.Length-offset)) 37 | } 38 | 39 | func (s *Skip) GetMissing(trans *Transaction, store *StructStore) (Number, error) { 40 | return 0, errors.New("gc not support this function") 41 | } 42 | 43 | func NewSkip(id ID, length Number) *Skip { 44 | return &Skip{AbstractStruct{ID: id, Length: length}} 45 | } 46 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Qinghui Yao 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /y_xml_hook.go: -------------------------------------------------------------------------------- 1 | package y_crdt 2 | 3 | // You can manage binding to a custom type with YXmlHook. 4 | type YXmlHook struct { 5 | YMap 6 | HookName string 7 | } 8 | 9 | // Copy Creates an Item with the same effect as this Item (without position effect) 10 | func (y *YXmlHook) Copy() IAbstractType { 11 | return NewYXmlHook(y.HookName) 12 | } 13 | 14 | // Clone 15 | func (y *YXmlHook) Clone() IAbstractType { 16 | el := NewYXmlHook(y.HookName) 17 | y.ForEach(func(key string, value interface{}, yMap *YMap) { 18 | el.Set(key, value) 19 | }) 20 | return el 21 | } 22 | 23 | func (y *YXmlHook) ToDOM() { 24 | 25 | } 26 | 27 | // Transform the properties of this type to binary and write it to an 28 | // BinaryEncoder. 29 | // 30 | // This is called when this Item is sent to a remote peer. 31 | func (y *YXmlHook) Write(encoder *UpdateEncoderV1) { 32 | encoder.WriteTypeRef(YXmlHookRefID) 33 | err := encoder.WriteKey(y.HookName) 34 | if err != nil { 35 | Logf("[crdt] %s.", err.Error()) 36 | } 37 | } 38 | 39 | func NewYXmlHook(hookName string) *YXmlHook { 40 | h := &YXmlHook{ 41 | YMap: *NewYMap(nil), 42 | HookName: hookName, 43 | } 44 | return h 45 | } 46 | -------------------------------------------------------------------------------- /gc.go: -------------------------------------------------------------------------------- 1 | package y_crdt 2 | 3 | import "errors" 4 | 5 | const ( 6 | StructGCRefNumber = 0 7 | ) 8 | 9 | type GC struct { 10 | AbstractStruct 11 | } 12 | 13 | func (gc *GC) Deleted() bool { 14 | return true 15 | } 16 | 17 | func (gc *GC) Delete() { 18 | 19 | } 20 | 21 | func (gc *GC) MergeWith(right IAbstractStruct) bool { 22 | r, ok := right.(*GC) 23 | if !ok { 24 | return false 25 | } 26 | 27 | gc.Length += r.Length 28 | return true 29 | } 30 | 31 | func (gc *GC) Integrate(trans *Transaction, offset Number) { 32 | if offset > 0 { 33 | gc.ID.Clock += offset 34 | gc.Length -= offset 35 | } 36 | 37 | err := AddStruct(trans.Doc.Store, gc) 38 | if err != nil { 39 | Logf("[crdt] %s.", err.Error()) 40 | } 41 | } 42 | 43 | func (gc *GC) Write(encoder *UpdateEncoderV1, offset Number) { 44 | encoder.WriteInfo(StructGCRefNumber) 45 | encoder.WriteLen(gc.Length - offset) 46 | } 47 | 48 | func (gc *GC) GetMissing(trans *Transaction, store *StructStore) (Number, error) { 49 | return 0, errors.New("gc not support this function") 50 | } 51 | 52 | func NewGC(id ID, length Number) *GC { 53 | return &GC{ 54 | AbstractStruct{ID: id, Length: length}, 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /event_handler.go: -------------------------------------------------------------------------------- 1 | package y_crdt 2 | 3 | import "reflect" 4 | 5 | type EventListener func(interface{}, interface{}) 6 | 7 | type EventHandler struct { 8 | L []EventListener 9 | } 10 | 11 | func NewEventHandler() *EventHandler { 12 | return &EventHandler{} 13 | } 14 | 15 | // Adds an event listener that is called when 16 | func AddEventHandlerListener(eventHandler *EventHandler, f EventListener) { 17 | eventHandler.L = append(eventHandler.L, f) 18 | } 19 | 20 | // Removes an event listener. 21 | func RemoveEventHandlerListener(eventHandler *EventHandler, f EventListener) { 22 | length := len(eventHandler.L) 23 | 24 | for i := len(eventHandler.L) - 1; i >= 0; i-- { 25 | if reflect.ValueOf(eventHandler.L[i]).Pointer() == reflect.ValueOf(f).Pointer() { 26 | eventHandler.L = append(eventHandler.L[:i], eventHandler.L[i+1:]...) 27 | } 28 | } 29 | 30 | if length == len(eventHandler.L) { 31 | } 32 | } 33 | 34 | // Removes all event listeners. 35 | func RemoveAllEventHandlerListeners(eventHandler *EventHandler) { 36 | eventHandler.L = []EventListener{} 37 | } 38 | 39 | // Call all event listeners that were added via 40 | func CallEventHandlerListeners(eventHandler *EventHandler, arg0, arg1 interface{}) { 41 | for _, f := range eventHandler.L { 42 | f(arg0, arg1) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /const.go: -------------------------------------------------------------------------------- 1 | package y_crdt 2 | 3 | // define the mask to get the low n bits of a byte. 4 | const ( 5 | BITS0 = 0 6 | BITS1 = 1 // low 1 bit 7 | BITS2 = 3 // low 2 bits 8 | BITS3 = 7 // low 3 bits 9 | BITS4 = 15 // low 4 bits 10 | BITS5 = 31 // low 5 bits 11 | BITS6 = 63 // low 6 bits 12 | BITS7 = 127 // low 7 bits 13 | BITS8 = 255 // low 8 bits 14 | ) 15 | 16 | // define the mask to get the specific bit of a byte. 17 | const ( 18 | BIT1 = 1 // first bit 19 | BIT2 = 2 // second bit 20 | BIT3 = 4 // third bit 21 | BIT4 = 8 // fourth bit 22 | BIT5 = 16 // fifth bit 23 | BIT6 = 32 // sixth bit 24 | BIT7 = 64 // seventh bit 25 | BIT8 = 128 // eighth bit 26 | ) 27 | 28 | const ( 29 | KeywordUndefined = "undefined" 30 | ) 31 | 32 | // RefContent define reference content type 33 | const ( 34 | RefGC = iota // 0 GC is not ItemContent 35 | RefContentDeleted // 1 36 | RefContentJson // 2 37 | RefContentBinary // 3 38 | RefContentString // 4 39 | RefContentEmbed // 5 40 | RefContentFormat // 6 41 | RefContentType // 7 42 | RefContentAny // 8 43 | RefContentDoc // 9 44 | RefSkip // 10 Skip is not ItemContent 45 | ) 46 | 47 | // RefID define reference id 48 | const ( 49 | YArrayRefID = iota 50 | YMapRefID 51 | YTextRefID 52 | YXmlElementRefID 53 | YXmlFragmentRefID 54 | YXmlHookRefID 55 | YXmlTextRefID 56 | ) 57 | -------------------------------------------------------------------------------- /abstract_struct.go: -------------------------------------------------------------------------------- 1 | package y_crdt 2 | 3 | type IAbstractStruct interface { 4 | GetID() *ID 5 | GetLength() Number 6 | SetLength(length Number) 7 | Deleted() bool 8 | MergeWith(right IAbstractStruct) bool 9 | Write(encoder *UpdateEncoderV1, offset Number) 10 | Integrate(trans *Transaction, offset Number) 11 | GetMissing(trans *Transaction, store *StructStore) (Number, error) 12 | } 13 | 14 | type AbstractStruct struct { 15 | ID ID 16 | Length Number 17 | } 18 | 19 | func (s *AbstractStruct) GetID() *ID { 20 | return &s.ID 21 | } 22 | 23 | func (s *AbstractStruct) GetLength() Number { 24 | return s.Length 25 | } 26 | 27 | func (s *AbstractStruct) SetLength(length Number) { 28 | s.Length = length 29 | } 30 | 31 | func (s *AbstractStruct) Deleted() bool { 32 | return false 33 | } 34 | 35 | // Merge this struct with the item to the right. 36 | // This method is already assuming that `this.id.clock + this.length === this.id.clock`. 37 | // Also this method does *not* remove right from StructStore! 38 | // @param {AbstractStruct} right 39 | // @return {boolean} whether this merged with right 40 | func (s *AbstractStruct) MergeWith(right IAbstractStruct) bool { 41 | return false 42 | } 43 | 44 | func (s *AbstractStruct) Write(encoder *UpdateEncoderV1, offset Number) { 45 | 46 | } 47 | 48 | func (s *AbstractStruct) Integrate(trans *Transaction, offset Number) { 49 | 50 | } 51 | 52 | func (s *AbstractStruct) GetMissing(trans *Transaction, store *StructStore) (Number, error) { 53 | return 0, nil 54 | } 55 | -------------------------------------------------------------------------------- /observable.go: -------------------------------------------------------------------------------- 1 | package y_crdt 2 | 3 | type ObserverHandler struct { 4 | Once bool 5 | Callback func(v ...interface{}) 6 | } 7 | 8 | type Observable struct { 9 | Observers map[interface{}]Set 10 | } 11 | 12 | func (o *Observable) On(name interface{}, handle *ObserverHandler) { 13 | _, exist := o.Observers[name] 14 | if !exist { 15 | o.Observers[name] = NewSet() 16 | } 17 | 18 | o.Observers[name].Add(handle) 19 | } 20 | 21 | func (o *Observable) Once(name interface{}, handler *ObserverHandler) { 22 | handler.Once = true 23 | o.On(name, handler) 24 | } 25 | 26 | func (o *Observable) Off(name interface{}, handler *ObserverHandler) { 27 | observers, exist := o.Observers[name] 28 | if exist { 29 | observers.Delete(handler) 30 | if len(observers) == 0 { 31 | delete(o.Observers, name) 32 | } 33 | } 34 | } 35 | 36 | func (o *Observable) Emit(name interface{}, v ...interface{}) { 37 | observers, exist := o.Observers[name] 38 | if !exist { 39 | return 40 | } 41 | 42 | for h := range observers { 43 | handler, ok := h.(*ObserverHandler) 44 | if !ok { 45 | continue 46 | } 47 | 48 | if handler.Once { 49 | o.Off(name, handler) 50 | } 51 | 52 | handler.Callback(v...) 53 | } 54 | } 55 | 56 | func (o *Observable) Destroy() { 57 | o.Observers = make(map[interface{}]Set) 58 | } 59 | 60 | func NewObservable() *Observable { 61 | return &Observable{ 62 | Observers: make(map[interface{}]Set), 63 | } 64 | } 65 | 66 | func NewObserverHandler(f func(v ...interface{})) *ObserverHandler { 67 | return &ObserverHandler{ 68 | Callback: f, 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /content_embed.go: -------------------------------------------------------------------------------- 1 | package y_crdt 2 | 3 | import "github.com/mitchellh/copystructure" 4 | 5 | type ContentEmbed struct { 6 | Embed interface{} 7 | } 8 | 9 | func (c *ContentEmbed) GetLength() Number { 10 | return 1 11 | } 12 | 13 | func (c *ContentEmbed) GetContent() ArrayAny { 14 | return ArrayAny{c.Embed} 15 | } 16 | 17 | func (c *ContentEmbed) IsCountable() bool { 18 | return true 19 | } 20 | 21 | func (c *ContentEmbed) Copy() IAbstractContent { 22 | embed, err := copystructure.Copy(c.Embed) 23 | if err != nil { 24 | return nil 25 | } 26 | 27 | return NewContentEmbed(embed) 28 | } 29 | 30 | func (c *ContentEmbed) Splice(offset Number) IAbstractContent { 31 | return nil 32 | } 33 | 34 | func (c *ContentEmbed) MergeWith(right IAbstractContent) bool { 35 | return false 36 | } 37 | 38 | func (c *ContentEmbed) Integrate(trans *Transaction, item *Item) { 39 | 40 | } 41 | 42 | func (c *ContentEmbed) Delete(trans *Transaction) { 43 | 44 | } 45 | 46 | func (c *ContentEmbed) GC(store *StructStore) { 47 | 48 | } 49 | 50 | func (c *ContentEmbed) Write(encoder *UpdateEncoderV1, offset Number) error { 51 | return encoder.WriteJson(c.Embed) 52 | } 53 | 54 | func (c *ContentEmbed) GetRef() uint8 { 55 | return RefContentEmbed 56 | } 57 | 58 | func NewContentEmbed(embed interface{}) *ContentEmbed { 59 | return &ContentEmbed{ 60 | Embed: embed, 61 | } 62 | } 63 | 64 | func ReadContentEmbed(decoder *UpdateDecoderV1) (IAbstractContent, error) { 65 | embed, err := decoder.ReadJson() 66 | if err != nil { 67 | return nil, err 68 | } 69 | 70 | return NewContentEmbed(embed), nil 71 | } 72 | -------------------------------------------------------------------------------- /abstract_content.go: -------------------------------------------------------------------------------- 1 | package y_crdt 2 | 3 | import "errors" 4 | 5 | var ContentRefs = []func(*UpdateDecoderV1) (IAbstractContent, error){ 6 | func(v1 *UpdateDecoderV1) (IAbstractContent, error) { 7 | return nil, errors.New("unexpected case") 8 | }, // GC is not ItemContent 9 | ReadContentDeleted, // 1 10 | ReadContentJson, // 2 11 | ReadContentBinary, // 3 12 | ReadContentString, // 4 13 | ReadContentEmbed, // 5 14 | ReadContentFormat, // 6 15 | ReadContentType, // 7 16 | ReadContentAny, // 8 17 | ReadContentDoc, // 9 18 | func(v1 *UpdateDecoderV1) (IAbstractContent, error) { // 10 - Skip is not ItemContent 19 | return nil, errors.New("unexpected case") 20 | }, 21 | } 22 | 23 | type IAbstractContent interface { 24 | GetLength() Number 25 | GetContent() ArrayAny 26 | IsCountable() bool 27 | Copy() IAbstractContent 28 | Splice(offset Number) IAbstractContent 29 | MergeWith(right IAbstractContent) bool 30 | Integrate(trans *Transaction, item *Item) 31 | Delete(trans *Transaction) 32 | GC(store *StructStore) 33 | Write(encoder *UpdateEncoderV1, offset Number) error 34 | GetRef() uint8 35 | } 36 | 37 | func ReadItemContent(decoder *UpdateDecoderV1, info uint8) IAbstractContent { 38 | refID := int(info & BITS5) 39 | if refID >= len(ContentRefs) { 40 | Logf("[crdt] read item content failed. info:%d refID:%d err:index out of range", info, refID) 41 | return nil 42 | } 43 | 44 | c, err := ContentRefs[refID](decoder) 45 | if err != nil { 46 | Logf("[crdt] read item content failed. info:%d refID:%d err:%s", info, refID, err.Error()) 47 | return nil 48 | } 49 | 50 | return c 51 | } 52 | -------------------------------------------------------------------------------- /content_binary.go: -------------------------------------------------------------------------------- 1 | package y_crdt 2 | 3 | type ContentBinary struct { 4 | Content []uint8 5 | } 6 | 7 | func (c *ContentBinary) GetLength() Number { 8 | return 1 9 | } 10 | 11 | func (c *ContentBinary) GetContent() ArrayAny { 12 | return ArrayAny{c.Content} 13 | } 14 | 15 | func (c *ContentBinary) IsCountable() bool { 16 | return true 17 | } 18 | 19 | func (c *ContentBinary) Copy() IAbstractContent { 20 | content := make([]uint8, 0, len(c.Content)) 21 | for _, v := range c.Content { 22 | // content[i] = v 23 | content = append(content, v) 24 | } 25 | 26 | return NewContentBinary(content) 27 | } 28 | 29 | func (c *ContentBinary) Splice(offset Number) IAbstractContent { 30 | return nil 31 | } 32 | 33 | func (c *ContentBinary) MergeWith(right IAbstractContent) bool { 34 | return false 35 | } 36 | 37 | func (c *ContentBinary) Integrate(trans *Transaction, item *Item) { 38 | 39 | } 40 | 41 | func (c *ContentBinary) Delete(trans *Transaction) { 42 | 43 | } 44 | 45 | func (c *ContentBinary) GC(store *StructStore) { 46 | 47 | } 48 | 49 | func (c *ContentBinary) Write(encoder *UpdateEncoderV1, offset Number) error { 50 | encoder.WriteBuf(c.Content) 51 | return nil 52 | } 53 | 54 | func (c *ContentBinary) GetRef() uint8 { 55 | return RefContentBinary 56 | } 57 | 58 | func NewContentBinary(content []uint8) *ContentBinary { 59 | return &ContentBinary{ 60 | Content: content, 61 | } 62 | } 63 | 64 | func ReadContentBinary(decoder *UpdateDecoderV1) (IAbstractContent, error) { 65 | content, err := decoder.ReadBuf() 66 | if err != nil { 67 | return nil, err 68 | } 69 | 70 | return NewContentBinary(content), nil 71 | } 72 | -------------------------------------------------------------------------------- /update_encoder_decoder_v2.go: -------------------------------------------------------------------------------- 1 | package y_crdt 2 | 3 | import "bytes" 4 | 5 | type DSEncoderV2 struct { 6 | RestEncoder *bytes.Buffer 7 | DsCurrVal Number 8 | } 9 | 10 | type UpdateEncoderV2 struct { 11 | DSEncoderV2 12 | } 13 | 14 | func (v2 *DSEncoderV2) ToUint8Array() []uint8 { 15 | return v2.RestEncoder.Bytes() 16 | } 17 | 18 | func (v2 *DSEncoderV2) ResetDsCurVal() { 19 | v2.DsCurrVal = 0 20 | } 21 | 22 | func (v2 *DSEncoderV2) WriteDsClock(clock Number) { 23 | diff := clock - v2.DsCurrVal 24 | v2.DsCurrVal = clock 25 | WriteVarUint(v2.RestEncoder, uint64(diff)) 26 | } 27 | 28 | func (v2 *DSEncoderV2) WriteDsLen(length Number) { 29 | if length == 0 { 30 | return 31 | } 32 | 33 | WriteVarUint(v2.RestEncoder, uint64(length-1)) 34 | v2.DsCurrVal += length 35 | } 36 | 37 | func (v2 *UpdateEncoderV2) ToUint8Array() []uint8 { 38 | encoder := new(bytes.Buffer) 39 | WriteVarUint(encoder, 0) 40 | WriteVarUint8Array(encoder, nil) // keyClockEncoder 41 | WriteVarUint8Array(encoder, nil) // clientEncoder 42 | WriteVarUint8Array(encoder, nil) // leftClockEncoder 43 | WriteVarUint8Array(encoder, nil) // rightClockEncoder 44 | WriteVarUint8Array(encoder, nil) // infoEncoder 45 | WriteUint8Array(encoder, []uint8{1, 0}) // stringEncoder 46 | WriteVarUint8Array(encoder, nil) // parentInfoEncoder 47 | WriteVarUint8Array(encoder, nil) // typeRefEncoder 48 | WriteVarUint8Array(encoder, nil) // lenEncoder 49 | 50 | WriteUint8Array(encoder, v2.RestEncoder.Bytes()) 51 | return encoder.Bytes() 52 | } 53 | 54 | func NewUpdateEncoderV2() *UpdateEncoderV2 { 55 | return &UpdateEncoderV2{ 56 | DSEncoderV2{ 57 | RestEncoder: new(bytes.Buffer), 58 | DsCurrVal: 0, 59 | }, 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /content_format.go: -------------------------------------------------------------------------------- 1 | package y_crdt 2 | 3 | import "github.com/mitchellh/copystructure" 4 | 5 | type ContentFormat struct { 6 | Key string 7 | Value interface{} 8 | } 9 | 10 | func (c *ContentFormat) GetLength() Number { 11 | return 1 12 | } 13 | 14 | func (c *ContentFormat) GetContent() ArrayAny { 15 | return nil 16 | } 17 | 18 | func (c *ContentFormat) IsCountable() bool { 19 | return false 20 | } 21 | 22 | func (c *ContentFormat) Copy() IAbstractContent { 23 | value, err := copystructure.Copy(c.Value) 24 | if err != nil { 25 | return nil 26 | } 27 | 28 | return NewContentFormat(c.Key, value) 29 | } 30 | 31 | func (c *ContentFormat) Splice(offset Number) IAbstractContent { 32 | // 不支持 33 | return nil 34 | } 35 | 36 | func (c *ContentFormat) MergeWith(right IAbstractContent) bool { 37 | return false 38 | } 39 | 40 | func (c *ContentFormat) Integrate(trans *Transaction, item *Item) { 41 | // todo searchmarker are currently unsupported for rich text documents 42 | (item.Parent).(IAbstractType).SetSearchMarker(nil) 43 | } 44 | 45 | func (c *ContentFormat) Delete(trans *Transaction) { 46 | 47 | } 48 | 49 | func (c *ContentFormat) GC(store *StructStore) { 50 | 51 | } 52 | 53 | func (c *ContentFormat) Write(encoder *UpdateEncoderV1, offset Number) error { 54 | encoder.WriteKey(c.Key) 55 | encoder.WriteJson(c.Value) 56 | return nil 57 | } 58 | 59 | func (c *ContentFormat) GetRef() uint8 { 60 | return RefContentFormat 61 | } 62 | 63 | func NewContentFormat(key string, value interface{}) *ContentFormat { 64 | return &ContentFormat{ 65 | Key: key, 66 | Value: value, 67 | } 68 | } 69 | 70 | func ReadContentFormat(decoder *UpdateDecoderV1) (IAbstractContent, error) { 71 | key, err := decoder.ReadString() 72 | if err != nil { 73 | return nil, err 74 | } 75 | 76 | value, err := decoder.ReadJson() 77 | if err != nil { 78 | return nil, err 79 | } 80 | 81 | return NewContentFormat(key, value), nil 82 | } 83 | -------------------------------------------------------------------------------- /content_deleted.go: -------------------------------------------------------------------------------- 1 | package y_crdt 2 | 3 | import "errors" 4 | 5 | type ContentDeleted struct { 6 | Length Number 7 | } 8 | 9 | func (c *ContentDeleted) GetLength() Number { 10 | return c.Length 11 | } 12 | 13 | func (c *ContentDeleted) GetContent() ArrayAny { 14 | return nil 15 | } 16 | 17 | func (c *ContentDeleted) IsCountable() bool { 18 | return false 19 | } 20 | 21 | func (c *ContentDeleted) Copy() IAbstractContent { 22 | return NewContentDeleted(c.Length) 23 | } 24 | 25 | func (c *ContentDeleted) Splice(offset Number) IAbstractContent { 26 | if offset > c.Length { 27 | offset = c.Length 28 | } 29 | 30 | right := NewContentDeleted(c.Length - offset) 31 | c.Length = offset 32 | return right 33 | } 34 | 35 | func (c *ContentDeleted) MergeWith(right IAbstractContent) bool { 36 | r, ok := right.(*ContentDeleted) 37 | if !ok { 38 | return false 39 | } 40 | 41 | c.Length += r.Length 42 | return true 43 | } 44 | 45 | func (c *ContentDeleted) Integrate(trans *Transaction, item *Item) { 46 | AddToDeleteSet(trans.DeleteSet, item.ID.Client, item.ID.Clock, c.Length) 47 | item.MarkDeleted() 48 | } 49 | 50 | func (c *ContentDeleted) Delete(trans *Transaction) { 51 | 52 | } 53 | 54 | func (c *ContentDeleted) GC(store *StructStore) { 55 | 56 | } 57 | 58 | func (c *ContentDeleted) Write(encoder *UpdateEncoderV1, offset Number) error { 59 | if offset > c.Length { 60 | return errors.New("offset is larger than length") 61 | } 62 | 63 | encoder.WriteLen(c.Length - offset) 64 | return nil 65 | } 66 | 67 | func (c *ContentDeleted) GetRef() uint8 { 68 | return RefContentDeleted 69 | } 70 | 71 | func NewContentDeleted(length Number) *ContentDeleted { 72 | return &ContentDeleted{Length: length} 73 | } 74 | 75 | func ReadContentDeleted(decoder *UpdateDecoderV1) (IAbstractContent, error) { 76 | length, err := decoder.ReadLen() 77 | if err != nil { 78 | return nil, err 79 | } 80 | 81 | return NewContentDeleted(length), nil 82 | } 83 | -------------------------------------------------------------------------------- /y_string.go: -------------------------------------------------------------------------------- 1 | package y_crdt 2 | 3 | type YString struct { 4 | AbstractType 5 | Str string 6 | } 7 | 8 | func (str *YString) GetLength() Number { 9 | return len(str.Str) 10 | } 11 | 12 | func (str *YString) GetItem() *Item { 13 | return nil 14 | } 15 | 16 | func (str *YString) GetMap() map[string]*Item { 17 | return nil 18 | } 19 | 20 | func (str *YString) StartItem() *Item { 21 | return nil 22 | } 23 | 24 | func (str *YString) SetStartItem(item *Item) { 25 | 26 | } 27 | 28 | func (str *YString) GetDoc() *Doc { 29 | return nil 30 | } 31 | 32 | func (str *YString) UpdateLength(n Number) { 33 | 34 | } 35 | 36 | func (str *YString) SetSearchMarker(mark []*ArraySearchMarker) { 37 | 38 | } 39 | 40 | func (str *YString) Parent() IAbstractType { 41 | return nil 42 | } 43 | 44 | func (str *YString) Integrate(doc *Doc, item *Item) { 45 | 46 | } 47 | 48 | func (str *YString) Copy() IAbstractType { 49 | return nil 50 | } 51 | 52 | func (str *YString) Clone() IAbstractType { 53 | return nil 54 | } 55 | 56 | func (str *YString) Write(encoder *UpdateEncoderV1) { 57 | 58 | } 59 | 60 | func (str *YString) First() *Item { 61 | return nil 62 | } 63 | 64 | func (str *YString) CallObserver(trans *Transaction, parentSubs Set) { 65 | 66 | } 67 | 68 | func (str *YString) Observe(f func(interface{}, interface{})) { 69 | 70 | } 71 | 72 | func (str *YString) ObserveDeep(f func(interface{}, interface{})) { 73 | 74 | } 75 | 76 | func (str *YString) Unobserve(f func(interface{}, interface{})) { 77 | 78 | } 79 | 80 | func (str *YString) UnobserveDeep(f func(interface{}, interface{})) { 81 | 82 | } 83 | 84 | func (str *YString) ToJson() interface{} { 85 | return "" 86 | } 87 | 88 | func NewYString(str string) *YString { 89 | ystr := &YString{ 90 | Str: str, 91 | } 92 | 93 | ystr.EH = NewEventHandler() 94 | ystr.DEH = NewEventHandler() 95 | 96 | return ystr 97 | } 98 | 99 | func NewDefaultYString() *YString { 100 | return &YString{} 101 | } 102 | -------------------------------------------------------------------------------- /content_any.go: -------------------------------------------------------------------------------- 1 | package y_crdt 2 | 3 | import ( 4 | "errors" 5 | 6 | "github.com/mitchellh/copystructure" 7 | ) 8 | 9 | type ContentAny struct { 10 | Arr ArrayAny 11 | } 12 | 13 | func (c *ContentAny) GetLength() Number { 14 | return len(c.Arr) 15 | } 16 | 17 | func (c *ContentAny) GetContent() ArrayAny { 18 | return c.Arr 19 | } 20 | 21 | func (c *ContentAny) IsCountable() bool { 22 | return true 23 | } 24 | 25 | func (c *ContentAny) Copy() IAbstractContent { 26 | arr, err := copystructure.Copy(c.Arr) 27 | if err != nil { 28 | return nil 29 | } 30 | 31 | return NewContentAny(arr.(ArrayAny)) 32 | } 33 | 34 | func (c *ContentAny) Splice(offset Number) IAbstractContent { 35 | right := c.Copy().(*ContentAny) 36 | right.Arr = right.Arr[offset:] 37 | c.Arr = c.Arr[:offset] 38 | return right 39 | } 40 | 41 | func (c *ContentAny) MergeWith(right IAbstractContent) bool { 42 | r, ok := right.(*ContentAny) 43 | if !ok { 44 | return false 45 | } 46 | 47 | c.Arr = append(c.Arr, r.Arr...) 48 | return true 49 | } 50 | 51 | func (c *ContentAny) Integrate(trans *Transaction, item *Item) { 52 | 53 | } 54 | 55 | func (c *ContentAny) Delete(trans *Transaction) { 56 | 57 | } 58 | 59 | func (c *ContentAny) GC(store *StructStore) { 60 | 61 | } 62 | 63 | func (c *ContentAny) Write(encoder *UpdateEncoderV1, offset Number) error { 64 | length := len(c.Arr) 65 | if offset > length { 66 | return errors.New("offset is larger than length") 67 | } 68 | 69 | encoder.WriteLen(length - offset) 70 | for i := offset; i < length; i++ { 71 | c := c.Arr[i] 72 | encoder.WriteAny(c) 73 | } 74 | 75 | return nil 76 | } 77 | 78 | func (c *ContentAny) GetRef() uint8 { 79 | return RefContentAny 80 | } 81 | 82 | func NewContentAny(arr ArrayAny) *ContentAny { 83 | return &ContentAny{Arr: arr} 84 | } 85 | 86 | func ReadContentAny(decoder *UpdateDecoderV1) (IAbstractContent, error) { 87 | length, err := decoder.ReadLen() 88 | if err != nil { 89 | return nil, err 90 | } 91 | 92 | var cs ArrayAny 93 | for i := 0; i < length; i++ { 94 | c, err := decoder.ReadAny() 95 | if err != nil { 96 | return nil, err 97 | } 98 | 99 | cs = append(cs, c) 100 | } 101 | 102 | return NewContentAny(cs), nil 103 | } 104 | -------------------------------------------------------------------------------- /content_doc.go: -------------------------------------------------------------------------------- 1 | package y_crdt 2 | 3 | import ( 4 | "github.com/mitchellh/copystructure" 5 | ) 6 | 7 | const ( 8 | OptKeyGC = "gc" 9 | OptKeyAutoLoad = "autoLoad" 10 | OptKeyMeta = "meta" 11 | ) 12 | 13 | type ContentDoc struct { 14 | Doc *Doc 15 | Opts Object 16 | } 17 | 18 | func (c *ContentDoc) GetLength() Number { 19 | return 1 20 | } 21 | 22 | func (c *ContentDoc) GetContent() ArrayAny { 23 | return ArrayAny{c.Doc} 24 | } 25 | 26 | func (c *ContentDoc) IsCountable() bool { 27 | return true 28 | } 29 | 30 | func (c *ContentDoc) Copy() IAbstractContent { 31 | doc, err := copystructure.Copy(c.Doc) 32 | if err != nil { 33 | return nil 34 | } 35 | 36 | return NewContentDoc(doc.(*Doc)) 37 | } 38 | 39 | func (c *ContentDoc) Splice(offset Number) IAbstractContent { 40 | return nil 41 | } 42 | 43 | func (c *ContentDoc) MergeWith(right IAbstractContent) bool { 44 | return false 45 | } 46 | 47 | func (c *ContentDoc) Integrate(trans *Transaction, item *Item) { 48 | // TODO 49 | } 50 | 51 | func (c *ContentDoc) Delete(trans *Transaction) { 52 | // TODO 53 | } 54 | 55 | func (c *ContentDoc) GC(store *StructStore) { 56 | 57 | } 58 | 59 | func (c *ContentDoc) Write(encoder *UpdateEncoderV1, offset Number) error { 60 | err := encoder.WriteString(c.Doc.Guid) 61 | if err != nil { 62 | return err 63 | } 64 | 65 | encoder.WriteAny(c.Opts) 66 | return nil 67 | } 68 | 69 | func (c *ContentDoc) GetRef() uint8 { 70 | return RefContentDoc 71 | } 72 | 73 | func NewContentDoc(doc *Doc) *ContentDoc { 74 | c := &ContentDoc{ 75 | Doc: doc, 76 | Opts: NewObject(), 77 | } 78 | 79 | if !doc.GC { 80 | c.Opts[OptKeyGC] = false 81 | } 82 | 83 | if doc.AutoLoad { 84 | c.Opts[OptKeyAutoLoad] = true 85 | } 86 | 87 | if doc.Meta != nil { 88 | c.Opts[OptKeyMeta] = doc.Meta 89 | } 90 | 91 | return c 92 | } 93 | 94 | func ReadContentDoc(decoder *UpdateDecoderV1) (IAbstractContent, error) { 95 | guid, err := decoder.ReadString() 96 | if err != nil { 97 | return nil, err 98 | } 99 | 100 | any, err := decoder.ReadAny() 101 | if err != nil { 102 | return nil, err 103 | } 104 | 105 | opts := any.(Object) 106 | 107 | gc, ok := opts[OptKeyGC].(bool) 108 | if !ok { 109 | gc = true // 不存在gc参数,使用默认参数 110 | } 111 | 112 | autoLoad, ok := opts[OptKeyAutoLoad].(bool) 113 | if !ok { 114 | autoLoad = false // 不存在autoLoad参数,使用默认参数 115 | } 116 | 117 | doc := NewDoc(guid, gc, DefaultGCFilter, opts[OptKeyMeta], autoLoad) 118 | return NewContentDoc(doc), nil 119 | } 120 | -------------------------------------------------------------------------------- /content_json.go: -------------------------------------------------------------------------------- 1 | package y_crdt 2 | 3 | import ( 4 | "encoding/json" 5 | 6 | "github.com/mitchellh/copystructure" 7 | ) 8 | 9 | type ContentJson struct { 10 | Arr ArrayAny 11 | } 12 | 13 | func (c *ContentJson) GetLength() Number { 14 | return len(c.Arr) 15 | } 16 | 17 | func (c *ContentJson) GetContent() ArrayAny { 18 | return c.Arr 19 | } 20 | 21 | func (c *ContentJson) IsCountable() bool { 22 | return true 23 | } 24 | 25 | func (c *ContentJson) Copy() IAbstractContent { 26 | data, err := copystructure.Copy(c.Arr) 27 | if err != nil { 28 | return nil 29 | } 30 | 31 | return NewContentJson(data.(ArrayAny)) 32 | } 33 | 34 | func (c *ContentJson) Splice(offset Number) IAbstractContent { 35 | cp := c.Copy() 36 | if cp == nil { 37 | return nil 38 | } 39 | 40 | c.Arr = c.Arr[:offset] 41 | 42 | right := cp.(*ContentJson) 43 | right.Arr = right.Arr[offset:] 44 | 45 | return right 46 | } 47 | 48 | func (c *ContentJson) MergeWith(right IAbstractContent) bool { 49 | r, ok := right.(*ContentJson) 50 | if !ok { 51 | return false 52 | } 53 | 54 | c.Arr = append(c.Arr, r.Arr...) 55 | return true 56 | } 57 | 58 | func (c *ContentJson) Integrate(trans *Transaction, item *Item) { 59 | 60 | } 61 | 62 | func (c *ContentJson) Delete(trans *Transaction) { 63 | 64 | } 65 | 66 | func (c *ContentJson) GC(store *StructStore) { 67 | 68 | } 69 | 70 | func (c *ContentJson) Write(encoder *UpdateEncoderV1, offset Number) error { 71 | length := len(c.Arr) 72 | encoder.WriteLen(length - offset) 73 | for i := offset; i < length; i++ { 74 | e := c.Arr[i] 75 | 76 | if IsUndefined(e) { 77 | encoder.WriteString(KeywordUndefined) 78 | continue 79 | } 80 | 81 | data, err := json.Marshal(e) 82 | if err != nil { 83 | return err 84 | } 85 | 86 | encoder.WriteString(string(data)) 87 | } 88 | 89 | return nil 90 | } 91 | 92 | func (c *ContentJson) GetRef() uint8 { 93 | return RefContentJson 94 | } 95 | 96 | func NewContentJson(arr ArrayAny) *ContentJson { 97 | return &ContentJson{ 98 | Arr: arr, 99 | } 100 | } 101 | 102 | func ReadContentJson(decoder *UpdateDecoderV1) (IAbstractContent, error) { 103 | length, err := decoder.ReadLen() 104 | if err != nil { 105 | return nil, err 106 | } 107 | 108 | var cs ArrayAny 109 | for i := 0; i < length; i++ { 110 | c, err := decoder.ReadString() 111 | if err != nil { 112 | return nil, err 113 | } 114 | 115 | if c == KeywordUndefined { 116 | cs = append(cs, Undefined) 117 | } else { 118 | var obj interface{} 119 | err = json.Unmarshal([]byte(c), &obj) 120 | if err != nil { 121 | return nil, err 122 | } 123 | 124 | cs = append(cs, obj) 125 | } 126 | } 127 | 128 | return NewContentJson(cs), nil 129 | } 130 | -------------------------------------------------------------------------------- /content_string.go: -------------------------------------------------------------------------------- 1 | package y_crdt 2 | 3 | import "unicode/utf16" 4 | 5 | type ContentString struct { 6 | Str string 7 | } 8 | 9 | func (c *ContentString) GetLength() Number { 10 | return StringLength(c.Str) 11 | } 12 | 13 | func (c *ContentString) GetContent() ArrayAny { 14 | chars := utf16.Encode([]rune(c.Str)) 15 | 16 | content := make(ArrayAny, 0, len(chars)) 17 | for _, c := range chars { 18 | // content[i] = c 19 | content = append(content, c) 20 | } 21 | 22 | return content 23 | } 24 | 25 | func (c *ContentString) IsCountable() bool { 26 | return true 27 | } 28 | 29 | func (c *ContentString) Copy() IAbstractContent { 30 | return NewContentString(c.Str) 31 | } 32 | 33 | func (c *ContentString) Splice(offset Number) IAbstractContent { 34 | right := &ContentString{ 35 | Str: StringTail(c.Str, offset), 36 | } 37 | 38 | c.Str = StringHeader(c.Str, offset) 39 | 40 | // Prevent encoding invalid documents because of splitting of surrogate pairs: https://github.com/yjs/yjs/issues/248 41 | firstCharCode, err := CharCodeAt(c.Str, offset-1) 42 | if err == nil && firstCharCode >= 0xD800 && firstCharCode <= 0xDBFF { 43 | // Last character of the left split is the start of a surrogate utf16/ucs2 pair. 44 | // We don't support splitting of surrogate pairs because this may lead to invalid documents. 45 | // Replace the invalid character with a unicode replacement character (� / U+FFFD) 46 | c.Str = ReplaceChar(c.Str, len(c.Str)-1, '�') 47 | 48 | // replace right as well 49 | right.Str = ReplaceChar(right.Str, 0, '�') 50 | } 51 | 52 | return right 53 | } 54 | 55 | func (c *ContentString) MergeWith(right IAbstractContent) bool { 56 | r, ok := right.(*ContentString) 57 | if !ok { 58 | return false 59 | } 60 | 61 | c.Str = MergeString(c.Str, r.Str) 62 | return true 63 | } 64 | 65 | func (c *ContentString) Integrate(trans *Transaction, item *Item) { 66 | 67 | } 68 | 69 | func (c *ContentString) Delete(trans *Transaction) { 70 | 71 | } 72 | 73 | func (c *ContentString) GC(store *StructStore) { 74 | 75 | } 76 | 77 | func (c *ContentString) Write(encoder *UpdateEncoderV1, offset Number) error { 78 | if offset == 0 { 79 | encoder.WriteString(c.Str) 80 | } else { 81 | encoder.WriteString(StringTail(c.Str, offset)) 82 | } 83 | 84 | return nil 85 | } 86 | 87 | func (c *ContentString) GetRef() uint8 { 88 | return RefContentString 89 | } 90 | 91 | func NewContentString(str string) *ContentString { 92 | return &ContentString{ 93 | Str: str, 94 | } 95 | } 96 | 97 | func ReadContentString(decoder *UpdateDecoderV1) (IAbstractContent, error) { 98 | str, err := decoder.ReadString() 99 | if err != nil { 100 | return nil, err 101 | } 102 | 103 | return NewContentString(str), nil 104 | } 105 | -------------------------------------------------------------------------------- /y_xml_text.go: -------------------------------------------------------------------------------- 1 | package y_crdt 2 | 3 | import ( 4 | "fmt" 5 | "sort" 6 | "strings" 7 | ) 8 | 9 | // YXmlText Represents text in a Dom Element. In the future this type will also handle 10 | // simple formatting information like bold and italic. 11 | type YXmlText struct { 12 | YText 13 | } 14 | 15 | func (y *YXmlText) GetNextSibling() IAbstractType { 16 | var n *Item 17 | if y.Item != nil { 18 | n = y.Item.Next() 19 | } 20 | 21 | if n != nil { 22 | t, ok := n.Content.(*ContentType) 23 | if ok { 24 | return t.Type 25 | } 26 | } 27 | 28 | return nil 29 | } 30 | 31 | func (y *YXmlText) GetPreSibling() IAbstractType { 32 | var n *Item 33 | if y.Item != nil { 34 | n = y.Item.Prev() 35 | } 36 | 37 | if n != nil { 38 | t, ok := n.Content.(*ContentType) 39 | if ok { 40 | return t.Type 41 | } 42 | } 43 | 44 | return nil 45 | } 46 | 47 | func (y *YXmlText) Copy() IAbstractType { 48 | return NewYXmlText() 49 | } 50 | 51 | func (y *YXmlText) Clone() IAbstractType { 52 | text := NewYXmlText() 53 | text.ApplyDelta(y.ToDelta(nil, nil, nil), true) 54 | return text 55 | } 56 | 57 | // not supported yet. 58 | func (y *YXmlText) ToDOM() { 59 | 60 | } 61 | 62 | func (y *YXmlText) ToString() string { 63 | delta := y.ToDelta(nil, nil, nil) 64 | var list []string 65 | for _, data := range delta { 66 | var nestedNodes []Object 67 | for nodeName, _ := range data.Attributes { 68 | var attrs []Object 69 | 70 | nodeAttrs, ok := data.Attributes[nodeName].(Object) 71 | if ok { 72 | for key, _ := range nodeAttrs { 73 | attrs = append(attrs, Object{"key": key, "value": nodeAttrs[key]}) 74 | } 75 | 76 | // sort attributes to get a unique order 77 | sort.Slice(attrs, func(i, j int) bool { 78 | return attrs[i]["key"].(string) < attrs[j]["key"].(string) 79 | }) 80 | 81 | nestedNodes = append(nestedNodes, Object{"nodeName": nodeName, "attrs": attrs}) 82 | } 83 | } 84 | 85 | // sort node order to get a unique order 86 | sort.Slice(nestedNodes, func(i, j int) bool { 87 | return nestedNodes[i]["nodeName"].(string) < nestedNodes[j]["nodeName"].(string) 88 | }) 89 | 90 | // now convert to dom string 91 | var str string 92 | for i := 0; i < len(nestedNodes); i++ { 93 | node := nestedNodes[i] 94 | str = fmt.Sprintf(`%s<%s`, str, node["nodeName"]) 95 | 96 | attrs, ok := node["attrs"].([]Object) 97 | if ok { 98 | for j := 0; j < len(attrs); j++ { 99 | attr := attrs[j] 100 | value, _ := attr["value"].(string) 101 | str = fmt.Sprintf(`%s %s="%s"`, str, attr["key"], value) 102 | } 103 | 104 | str = fmt.Sprintf(`%s>`, str) 105 | } 106 | } 107 | 108 | str = fmt.Sprintf("%s%v", str, data.Insert) 109 | for i := len(nestedNodes) - 1; i >= 0; i-- { 110 | str = fmt.Sprintf(`%s`, str, nestedNodes[i]["nodeName"]) 111 | } 112 | 113 | list = append(list, str) 114 | } 115 | 116 | return strings.Join(list, "") 117 | } 118 | 119 | func (y *YXmlText) ToJSON() string { 120 | return y.ToString() 121 | } 122 | 123 | func (y *YXmlText) Write(encoder *UpdateEncoderV1) { 124 | encoder.WriteTypeRef(YXmlTextRefID) 125 | } 126 | 127 | func NewYXmlText() *YXmlText { 128 | return &YXmlText{} 129 | } 130 | -------------------------------------------------------------------------------- /update_encoder_v1.go: -------------------------------------------------------------------------------- 1 | package y_crdt 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | ) 7 | 8 | type DSEncoderV1 struct { 9 | RestEncoder *bytes.Buffer 10 | } 11 | 12 | type UpdateEncoderV1 struct { 13 | DSEncoderV1 14 | } 15 | 16 | // ToUint8Array returns the encoded bytes. 17 | func (v1 *DSEncoderV1) ToUint8Array() []uint8 { 18 | return v1.RestEncoder.Bytes() 19 | } 20 | 21 | // ResetDsCurVal resets the current value of DeleteSet. 22 | func (v1 *DSEncoderV1) ResetDsCurVal() { 23 | // nop 24 | } 25 | 26 | // WriteDsClock writes the clock value of DeleteSet. 27 | func (v1 *DSEncoderV1) WriteDsClock(clock Number) { 28 | WriteVarUint(v1.RestEncoder, uint64(clock)) 29 | } 30 | 31 | // WriteDsLen writes the length of DeleteSet. 32 | func (v1 *DSEncoderV1) WriteDsLen(length Number) { 33 | WriteVarUint(v1.RestEncoder, uint64(length)) 34 | } 35 | 36 | // WriteID writes the ID of Item. 37 | func (v1 *UpdateEncoderV1) WriteID(id *ID) { 38 | WriteVarUint(v1.RestEncoder, uint64(id.Client)) 39 | WriteVarUint(v1.RestEncoder, uint64(id.Clock)) 40 | } 41 | 42 | // WriteLeftID writes the left ID of Item. 43 | func (v1 *UpdateEncoderV1) WriteLeftID(id *ID) { 44 | v1.WriteID(id) 45 | } 46 | 47 | // WriteRightID writes the right ID of Item. 48 | func (v1 *UpdateEncoderV1) WriteRightID(id *ID) { 49 | v1.WriteID(id) 50 | } 51 | 52 | // WriteClient writes the client of Item. 53 | func (v1 *UpdateEncoderV1) WriteClient(client Number) { 54 | WriteVarUint(v1.RestEncoder, uint64(client)) 55 | } 56 | 57 | // WriteInfo writes the info of Item. 58 | func (v1 *UpdateEncoderV1) WriteInfo(info uint8) { 59 | WriteByte(v1.RestEncoder, info) 60 | } 61 | 62 | // WriteString writes the string of Item. 63 | func (v1 *UpdateEncoderV1) WriteString(str string) error { 64 | return WriteString(v1.RestEncoder, str) 65 | } 66 | 67 | // WriteParentInfo writes the parent info of Item. 68 | func (v1 *UpdateEncoderV1) WriteParentInfo(isYKey bool) { 69 | code := uint64(0) 70 | if isYKey { 71 | code = 1 72 | } 73 | 74 | WriteVarUint(v1.RestEncoder, code) 75 | } 76 | 77 | // WriteTypeRef writes the type ref of Item. 78 | func (v1 *UpdateEncoderV1) WriteTypeRef(info uint8) { 79 | WriteVarUint(v1.RestEncoder, uint64(info)) 80 | } 81 | 82 | // WriteLen write len of a struct - well suited for Opt RLE encoder. 83 | func (v1 *UpdateEncoderV1) WriteLen(length Number) { 84 | WriteVarUint(v1.RestEncoder, uint64(length)) 85 | } 86 | 87 | // WriteAny writes the any of Item. 88 | func (v1 *UpdateEncoderV1) WriteAny(any any) { 89 | WriteAny(v1.RestEncoder, any) 90 | } 91 | 92 | // WriteBuf writes the buf of Item. 93 | func (v1 *UpdateEncoderV1) WriteBuf(buf []uint8) { 94 | WriteVarUint8Array(v1.RestEncoder, buf) 95 | } 96 | 97 | // WriteJson writes the json of Item. 98 | func (v1 *UpdateEncoderV1) WriteJson(embed interface{}) error { 99 | data, err := json.Marshal(embed) 100 | if err != nil { 101 | return err 102 | } 103 | 104 | return WriteString(v1.RestEncoder, string(data)) 105 | } 106 | 107 | // WriteKey writes the key of Item. 108 | func (v1 *UpdateEncoderV1) WriteKey(key string) error { 109 | return WriteString(v1.RestEncoder, key) 110 | } 111 | 112 | // NewUpdateEncoderV1 creates a new UpdateEncoderV1 instance. 113 | func NewUpdateEncoderV1() *UpdateEncoderV1 { 114 | return &UpdateEncoderV1{ 115 | DSEncoderV1{ 116 | RestEncoder: new(bytes.Buffer), 117 | }, 118 | } 119 | } 120 | 121 | // NewEncoder creates a new UpdateEncoderV1 instance. 122 | func NewEncoder() *bytes.Buffer { 123 | return new(bytes.Buffer) 124 | } 125 | -------------------------------------------------------------------------------- /content_type.go: -------------------------------------------------------------------------------- 1 | package y_crdt 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/mitchellh/copystructure" 7 | ) 8 | 9 | var typeRefs = []func(decoder *UpdateDecoderV1) (IAbstractType, error){ 10 | readYArray, 11 | readYMap, 12 | readYText, 13 | readYXmlElement, 14 | readYXmlFragment, 15 | readYXmlHook, 16 | readYXmlText, 17 | } 18 | 19 | type ContentType struct { 20 | Type IAbstractType 21 | } 22 | 23 | func (c *ContentType) GetLength() Number { 24 | return 1 25 | } 26 | 27 | func (c *ContentType) GetContent() ArrayAny { 28 | return ArrayAny{c.Type} 29 | } 30 | 31 | func (c *ContentType) IsCountable() bool { 32 | return true 33 | } 34 | 35 | func (c *ContentType) Copy() IAbstractContent { 36 | cpType, err := copystructure.Copy(c.Type) 37 | if err != nil { 38 | return nil 39 | } 40 | 41 | return NewContentType(cpType.(IAbstractType)) 42 | } 43 | 44 | func (c *ContentType) Splice(offset Number) IAbstractContent { 45 | return nil 46 | } 47 | 48 | func (c *ContentType) MergeWith(right IAbstractContent) bool { 49 | return false 50 | } 51 | 52 | func (c *ContentType) Integrate(trans *Transaction, item *Item) { 53 | c.Type.Integrate(trans.Doc, item) 54 | } 55 | 56 | func (c *ContentType) Delete(trans *Transaction) { 57 | // TODO 58 | 59 | // item := c.Type.StartItem() 60 | // for item != nil { 61 | // if !item.Deleted() { 62 | // item.Delete(trans) 63 | // } else { 64 | // // Whis will be gc'd later and we want to merge it if possible 65 | // // We try to merge all deleted items after each transaction, 66 | // // but we have no knowledge about that this needs to be merged 67 | // // since it is not in transaction.ds. Hence we add it to transaction._mergeStructs 68 | // //trans._mergeStructs.push(item) 69 | // } 70 | // item = item.Right 71 | // } 72 | 73 | } 74 | 75 | func (c *ContentType) GC(store *StructStore) { 76 | // TODO 77 | } 78 | 79 | func (c *ContentType) Write(encoder *UpdateEncoderV1, offset Number) error { 80 | c.Type.Write(encoder) 81 | return nil 82 | } 83 | 84 | func (c *ContentType) GetRef() uint8 { 85 | return RefContentType 86 | } 87 | 88 | func NewContentType(t IAbstractType) *ContentType { 89 | return &ContentType{Type: t} 90 | } 91 | 92 | func ReadContentType(decoder *UpdateDecoderV1) (IAbstractContent, error) { 93 | refID, err := decoder.ReadTypeRef() 94 | if err != nil { 95 | return nil, err 96 | } 97 | 98 | if int(refID) >= len(typeRefs) { 99 | return nil, fmt.Errorf("index out of range. refID:%d len:%d", refID, len(typeRefs)) 100 | } 101 | 102 | refType, err := typeRefs[refID](decoder) 103 | if err != nil { 104 | return nil, err 105 | } 106 | 107 | return NewContentType(refType), nil 108 | } 109 | 110 | func readYArray(decoder *UpdateDecoderV1) (IAbstractType, error) { 111 | return NewYArray(), nil 112 | } 113 | 114 | func readYMap(decoder *UpdateDecoderV1) (IAbstractType, error) { 115 | return NewYMap(nil), nil 116 | } 117 | 118 | func readYText(decoder *UpdateDecoderV1) (IAbstractType, error) { 119 | return NewYText(""), nil 120 | } 121 | 122 | func readYXmlElement(decoder *UpdateDecoderV1) (IAbstractType, error) { 123 | key, err := decoder.ReadKey() 124 | if err != nil { 125 | return nil, err 126 | } 127 | 128 | return NewYXmlElement(key), nil 129 | } 130 | 131 | func readYXmlFragment(decoder *UpdateDecoderV1) (IAbstractType, error) { 132 | return NewYXmlFragment(), nil 133 | } 134 | 135 | func readYXmlHook(decoder *UpdateDecoderV1) (IAbstractType, error) { 136 | key, err := decoder.ReadKey() 137 | if err != nil { 138 | return nil, err 139 | } 140 | 141 | return NewYXmlHook(key), nil 142 | } 143 | 144 | func readYXmlText(decoder *UpdateDecoderV1) (IAbstractType, error) { 145 | return NewYXmlText(), nil 146 | } 147 | -------------------------------------------------------------------------------- /update_decoder_v1.go: -------------------------------------------------------------------------------- 1 | package y_crdt 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "encoding/json" 7 | "io" 8 | ) 9 | 10 | type UpdateDecoderV1 struct { 11 | RestDecoder *bytes.Buffer 12 | } 13 | 14 | // ResetDsCurVal resets the current value of DeleteSet. 15 | func (v1 *UpdateDecoderV1) ResetDsCurVal() { 16 | // nop 17 | } 18 | 19 | // ReadDsClock reads the clock value of DeleteSet. 20 | func (v1 *UpdateDecoderV1) ReadDsClock() (Number, error) { 21 | number, err := binary.ReadUvarint(v1.RestDecoder) 22 | if err != nil { 23 | return 0, err 24 | } 25 | 26 | return Number(number), nil 27 | } 28 | 29 | // ReadDsLen reads the length of DeleteSet. 30 | func (v1 *UpdateDecoderV1) ReadDsLen() (Number, error) { 31 | number, err := binary.ReadUvarint(v1.RestDecoder) 32 | if err != nil { 33 | return 0, err 34 | } 35 | 36 | return Number(number), nil 37 | } 38 | 39 | // ReadID reads the ID of Item. 40 | func (v1 *UpdateDecoderV1) ReadID() (*ID, error) { 41 | client, err := binary.ReadUvarint(v1.RestDecoder) 42 | if err != nil { 43 | return nil, err 44 | } 45 | 46 | clock, err := binary.ReadUvarint(v1.RestDecoder) 47 | if err != nil { 48 | return nil, err 49 | } 50 | 51 | return &ID{Client: Number(client), Clock: Number(clock)}, nil 52 | } 53 | 54 | // ReadLeftID reads the left ID of Item. 55 | func (v1 *UpdateDecoderV1) ReadLeftID() (*ID, error) { 56 | return v1.ReadID() 57 | } 58 | 59 | // ReadRightID reads the right ID of Item. 60 | func (v1 *UpdateDecoderV1) ReadRightID() (*ID, error) { 61 | return v1.ReadID() 62 | } 63 | 64 | // ReadClient reads the client of Item. 65 | func (v1 *UpdateDecoderV1) ReadClient() (Number, error) { 66 | number, err := binary.ReadUvarint(v1.RestDecoder) 67 | if err != nil { 68 | return 0, err 69 | } 70 | 71 | return Number(number), err 72 | } 73 | 74 | // ReadInfo reads the info of Item. 75 | func (v1 *UpdateDecoderV1) ReadInfo() (uint8, error) { 76 | buf := make([]byte, 1) 77 | _, err := io.ReadFull(v1.RestDecoder, buf) 78 | if err != nil { 79 | return 0, err 80 | } 81 | 82 | return buf[0], nil 83 | } 84 | 85 | // ReadString reads the string of Item. 86 | func (v1 *UpdateDecoderV1) ReadString() (string, error) { 87 | return ReadString(v1.RestDecoder) 88 | } 89 | 90 | // ReadParentInfo reads the parent info of Item. 91 | func (v1 *UpdateDecoderV1) ReadParentInfo() (bool, error) { 92 | info, err := binary.ReadUvarint(v1.RestDecoder) 93 | if err != nil { 94 | return false, err 95 | } 96 | 97 | return info == 1, nil 98 | } 99 | 100 | // ReadTypeRef reads the type ref of Item. 101 | func (v1 *UpdateDecoderV1) ReadTypeRef() (uint8, error) { 102 | ref, err := binary.ReadUvarint(v1.RestDecoder) 103 | if err != nil { 104 | return 0, err 105 | } 106 | 107 | return uint8(ref), nil 108 | } 109 | 110 | // ReadLen reads the length of Item. 111 | func (v1 *UpdateDecoderV1) ReadLen() (Number, error) { 112 | length, err := binary.ReadUvarint(v1.RestDecoder) 113 | if err != nil { 114 | return 0, err 115 | } 116 | 117 | return Number(length), nil 118 | } 119 | 120 | // ReadAny reads the any of Item. 121 | func (v1 *UpdateDecoderV1) ReadAny() (any, error) { 122 | return ReadAny(v1.RestDecoder) 123 | } 124 | 125 | // ReadBuf reads the buf of Item. 126 | func (v1 *UpdateDecoderV1) ReadBuf() ([]uint8, error) { 127 | data, err := ReadVarUint8Array(v1.RestDecoder) 128 | if err != nil { 129 | return nil, err 130 | } 131 | 132 | return data.([]uint8), nil 133 | } 134 | 135 | // ReadJson reads the json of Item. 136 | func (v1 *UpdateDecoderV1) ReadJson() (interface{}, error) { 137 | data, err := v1.ReadString() 138 | if err != nil { 139 | return nil, err 140 | } 141 | 142 | var obj any 143 | err = json.Unmarshal([]byte(data), &obj) 144 | if err != nil { 145 | return nil, err 146 | } 147 | 148 | return obj, nil 149 | } 150 | 151 | // ReadKey reads the key of Item. 152 | func (v1 *UpdateDecoderV1) ReadKey() (string, error) { 153 | return v1.ReadString() 154 | } 155 | 156 | // NewUpdateDecoderV1 creates a new UpdateDecoderV1. 157 | func NewUpdateDecoderV1(buf []byte) *UpdateDecoderV1 { 158 | return &UpdateDecoderV1{ 159 | RestDecoder: bytes.NewBuffer(buf), 160 | } 161 | } 162 | 163 | // NewDecoder creates a new decoder. 164 | func NewDecoder(buf []byte) *bytes.Buffer { 165 | return bytes.NewBuffer(buf) 166 | } 167 | -------------------------------------------------------------------------------- /type_define.go: -------------------------------------------------------------------------------- 1 | package y_crdt 2 | 3 | import "reflect" 4 | 5 | var ( 6 | Null = NullType{} 7 | Undefined = UndefinedType{} 8 | ) 9 | 10 | // js Number 11 | type Number = int 12 | 13 | // js Object 14 | type Object = map[string]any 15 | 16 | // js Array 17 | type ArrayAny = []any 18 | 19 | // js undefined 20 | type UndefinedType struct { 21 | } 22 | 23 | // js null 24 | type NullType struct { 25 | } 26 | 27 | // js Set 28 | type Set map[any]bool 29 | 30 | // Add adds the given element to the set. 31 | func (s Set) Add(e any) { 32 | s[e] = true 33 | } 34 | 35 | // Has returns true if the given element is in the set. 36 | func (s Set) Has(e any) bool { 37 | _, exist := s[e] 38 | return exist 39 | } 40 | 41 | // Delete deletes the given element from the set. 42 | func (s Set) Delete(e any) { 43 | delete(s, e) 44 | } 45 | 46 | // Range calls the given function for each element in the set. 47 | func (s Set) Range(f func(element any)) { 48 | for el := range s { 49 | f(el) 50 | } 51 | } 52 | 53 | // js NumberSlice 54 | type NumberSlice []Number 55 | 56 | // Len returns the length of the slice. 57 | func (ns NumberSlice) Len() int { 58 | return len(ns) 59 | } 60 | 61 | // Less returns true if the element at index i is less than the element at index j. 62 | func (ns NumberSlice) Less(i, j int) bool { 63 | return ns[i] < ns[j] 64 | } 65 | 66 | // Swap swaps the elements at index i and j. 67 | func (ns NumberSlice) Swap(i, j int) { 68 | ns[i], ns[j] = ns[j], ns[i] 69 | } 70 | 71 | // Filter returns a new slice containing all elements for which the given function returns true. 72 | func (ns NumberSlice) Filter(cond func(number Number) bool) NumberSlice { 73 | var r NumberSlice 74 | for _, n := range ns { 75 | if cond(n) { 76 | r = append(r, n) 77 | } 78 | } 79 | 80 | return r 81 | } 82 | 83 | // NewObject returns a new object. 84 | func NewObject() Object { 85 | return make(Object) 86 | } 87 | 88 | // NewSet returns a new set. 89 | func NewSet() Set { 90 | return make(Set) 91 | } 92 | 93 | // IsUndefined returns true if the given object is undefined. 94 | // In javascript, undefined indicate that the variable has not been initialized. 95 | // In golang, a nil any(=interface{}) value indicates that the variable has not been initialized. 96 | // So, we define an object is undefined if its value is nil or its type is Undefined. 97 | func IsUndefined(obj any) bool { 98 | return obj == nil || reflect.TypeOf(obj) == reflect.TypeOf(Undefined) 99 | } 100 | 101 | // IsNull returns true if the given object is null. 102 | // In javascript, null indicate that the variable has been initialized and the value is null. 103 | // In golang, we define an object is null if the object is a pointer kind and the value is nil or its type is Null. 104 | func IsNull(obj any) bool { 105 | return reflect.TypeOf(obj) == reflect.TypeOf(Null) || (IsPtr(obj) && reflect.TypeOf(obj) != nil && reflect.ValueOf(obj).IsNil()) 106 | } 107 | 108 | // IsPtr returns true if the given object is a pointer. 109 | func IsPtr(obj any) bool { 110 | return reflect.ValueOf(obj).Kind() == reflect.Ptr 111 | } 112 | 113 | // IsGCPtr returns true if the given object is a pointer to a GC. 114 | func IsGCPtr(obj interface{}) bool { 115 | return reflect.TypeOf(obj) == reflect.TypeOf(&GC{}) 116 | } 117 | 118 | // IsItemPtr returns true if the given object is a pointer to an Item. 119 | func IsItemPtr(obj interface{}) bool { 120 | return reflect.TypeOf(obj) == reflect.TypeOf(&Item{}) 121 | } 122 | 123 | // IsIDPtr returns true if the given object is a pointer to an ID. 124 | func IsIDPtr(obj interface{}) bool { 125 | return reflect.TypeOf(obj) == reflect.TypeOf(&ID{}) 126 | } 127 | 128 | // IsSameType returns true if the given two objects are the same type. 129 | func IsSameType(a interface{}, b interface{}) bool { 130 | return reflect.TypeOf(a) == reflect.TypeOf(b) 131 | } 132 | 133 | // IsString returns true if the given object is a string. 134 | func IsString(obj interface{}) bool { 135 | return reflect.ValueOf(obj).Kind() == reflect.String 136 | } 137 | 138 | // IsYString returns true if the given object is a YString. 139 | func IsYString(obj interface{}) bool { 140 | return reflect.TypeOf(obj) == reflect.TypeOf(&YString{}) 141 | } 142 | 143 | // IsIAbstractType returns true if the given object is an IAbstractType. 144 | func IsIAbstractType(a interface{}) bool { 145 | if a == nil { 146 | return false 147 | } 148 | 149 | _, ok := a.(IAbstractType) 150 | return ok 151 | } 152 | -------------------------------------------------------------------------------- /snapshot.go: -------------------------------------------------------------------------------- 1 | package y_crdt 2 | 3 | import "errors" 4 | 5 | type Snapshot struct { 6 | Ds *DeleteSet 7 | Sv map[Number]Number // state map 8 | } 9 | 10 | func NewSnapshot(ds *DeleteSet, sv map[Number]Number) *Snapshot { 11 | return &Snapshot{ 12 | Ds: ds, 13 | Sv: sv, 14 | } 15 | } 16 | 17 | func EqualSnapshots(snap1, snap2 *Snapshot) bool { 18 | ds1 := snap1.Ds.Clients 19 | ds2 := snap2.Ds.Clients 20 | sv1 := snap1.Sv 21 | sv2 := snap2.Sv 22 | 23 | if len(sv1) != len(sv2) || len(ds1) != len(ds2) { 24 | return false 25 | } 26 | 27 | for key, value := range sv1 { 28 | if sv2[key] != value { 29 | return false 30 | } 31 | } 32 | 33 | for client, delItems1 := range ds1 { 34 | delItems2 := ds2[client] 35 | if len(delItems1) != len(delItems2) { 36 | return false 37 | } 38 | 39 | for i := 0; i < len(delItems1); i++ { 40 | delItem1 := delItems1[i] 41 | delItem2 := delItems2[i] 42 | 43 | if delItem1.Clock != delItem2.Clock || delItem1.Length != delItem2.Length { 44 | return false 45 | } 46 | } 47 | } 48 | 49 | return true 50 | } 51 | 52 | func EncodeSnapshotV2(snapshot *Snapshot, encoder *UpdateEncoderV1) []uint8 { 53 | WriteDeleteSet(encoder, snapshot.Ds) 54 | WriteStateVector(encoder, snapshot.Sv) 55 | return encoder.ToUint8Array() 56 | } 57 | 58 | func EncodeSnapshot(snapshot *Snapshot) []uint8 { 59 | return EncodeSnapshotV2(snapshot, NewUpdateEncoderV1()) 60 | } 61 | 62 | func DecodeSnapshotV2(buf []uint8) *Snapshot { 63 | decoder := NewUpdateDecoderV1(buf) 64 | ds := ReadDeleteSet(decoder) 65 | sv := ReadStateVector(decoder) 66 | 67 | return NewSnapshot(ds, sv) 68 | } 69 | 70 | func DecodeSnapshot(buf []uint8) *Snapshot { 71 | return DecodeSnapshotV2(buf) 72 | } 73 | 74 | func EmptySnapshot() *Snapshot { 75 | return NewSnapshot(NewDeleteSet(), make(map[Number]Number)) 76 | } 77 | 78 | // snapshot(doc) 79 | func NewSnapshotByDoc(doc *Doc) *Snapshot { 80 | return NewSnapshot(NewDeleteSetFromStructStore(doc.Store), GetStateVector(doc.Store)) 81 | } 82 | 83 | func IsVisible(item *Item, snapshot *Snapshot) bool { 84 | if snapshot == nil { 85 | return !item.Deleted() 86 | } 87 | 88 | state := snapshot.Sv[item.ID.Client] 89 | return state > item.ID.Clock && !IsDeleted(snapshot.Ds, &item.ID) 90 | } 91 | 92 | func SplitSnapshotAffectedStructs(trans *Transaction, snapshot *Snapshot) { 93 | _, exist := trans.Meta[SplitSnapshotAffectedStructs] 94 | if !exist { 95 | trans.Meta[SplitSnapshotAffectedStructs] = NewSet() 96 | } 97 | 98 | meta := trans.Meta[SplitSnapshotAffectedStructs] 99 | store := trans.Doc.Store 100 | 101 | // check if we already split for this snapshot 102 | if _, exist := meta[snapshot]; !exist { 103 | for client, clock := range snapshot.Sv { 104 | if clock < GetState(store, client) { 105 | GetItemCleanStart(trans, GenID(client, clock)) 106 | } 107 | } 108 | 109 | IterateDeletedStructs(trans, snapshot.Ds, func(s IAbstractStruct) {}) 110 | meta.Add(snapshot) 111 | } 112 | } 113 | 114 | func CreateDocFromSnapshot(originDoc *Doc, snapshot *Snapshot, newDoc *Doc) (*Doc, error) { 115 | if originDoc.GC { 116 | // we should not try to restore a GC-ed document, because some of the restored items might have their content deleted 117 | return nil, errors.New("originDoc must not be garbage collected") 118 | } 119 | 120 | ds, sv := snapshot.Ds, snapshot.Sv 121 | encoder := NewUpdateEncoderV1() 122 | originDoc.Transact(func(trans *Transaction) { 123 | size := uint64(0) 124 | for _, clock := range sv { 125 | if clock > 0 { 126 | size++ 127 | } 128 | } 129 | 130 | WriteVarUint(encoder.RestEncoder, size) 131 | // splitting the structs before writing them to the encoder 132 | for client, clock := range sv { 133 | if clock == 0 { 134 | continue 135 | } 136 | 137 | if clock < GetState(originDoc.Store, client) { 138 | GetItemCleanStart(trans, GenID(client, clock)) 139 | } 140 | 141 | structs := originDoc.Store.Clients[client] 142 | lastStructIndex, _ := FindIndexSS(*structs, clock-1) 143 | 144 | // write # encoded structs 145 | WriteVarUint(encoder.RestEncoder, uint64(lastStructIndex+1)) 146 | encoder.WriteClient(client) 147 | 148 | // first clock written is 0 149 | WriteVarUint(encoder.RestEncoder, 0) 150 | for i := 0; i <= lastStructIndex; i++ { 151 | (*structs)[i].Write(encoder, 0) 152 | } 153 | } 154 | WriteDeleteSet(encoder, ds) 155 | }, nil) 156 | 157 | ApplyUpdate(newDoc, encoder.ToUint8Array(), "snapshot") 158 | return newDoc, nil 159 | } 160 | -------------------------------------------------------------------------------- /encoding_test.go: -------------------------------------------------------------------------------- 1 | package y_crdt 2 | 3 | import ( 4 | "bytes" 5 | "math" 6 | "testing" 7 | ) 8 | 9 | func TestWriteByte(t *testing.T) { 10 | encoder := bytes.NewBuffer(nil) 11 | WriteByte(encoder, 128) 12 | 13 | if encoder.Len() != 1 { 14 | t.Errorf("Expected buffer length to be 1, got %d", encoder.Len()) 15 | } 16 | 17 | expected := []byte{128} 18 | if !bytes.Equal(encoder.Bytes(), expected) { 19 | t.Errorf("Expected buffer to be %v, got %v", expected, encoder.Bytes()) 20 | } 21 | } 22 | 23 | func TestWriteVarUnit8Array(t *testing.T) { 24 | encoder := bytes.NewBuffer(nil) 25 | buf := []uint8{1, 2, 3, 4, 5} 26 | WriteVarUint8Array(encoder, buf) 27 | 28 | if encoder.Len() != 6 { 29 | t.Errorf("Expected buffer length to be 5, got %d", encoder.Len()) 30 | } 31 | 32 | expected := []byte{5, 1, 2, 3, 4, 5} // this first byte is the length of the array 33 | if !bytes.Equal(encoder.Bytes(), expected) { 34 | t.Errorf("Expected buffer to be %v, got %v", expected, encoder.Bytes()) 35 | } 36 | } 37 | 38 | func TestWriteUint8Array(t *testing.T) { 39 | encoder := bytes.NewBuffer(nil) 40 | buf := []uint8{1, 2, 3, 4} 41 | 42 | WriteUint8Array(encoder, buf) 43 | if encoder.Len() != 4 { 44 | t.Errorf("Expected buffer length to be 4, got %d", encoder.Len()) 45 | } 46 | 47 | expected := []byte{1, 2, 3, 4} 48 | if !bytes.Equal(encoder.Bytes(), expected) { 49 | t.Errorf("Expected buffer to be %v, got %v", expected, encoder.Bytes()) 50 | } 51 | } 52 | 53 | func TestWriteVarUint(t *testing.T) { 54 | encoder := bytes.NewBuffer(nil) 55 | WriteVarUint(encoder, 128) 56 | if encoder.Len() != 2 { 57 | t.Errorf("Expected buffer length to be 2, got %d", encoder.Len()) 58 | } 59 | 60 | expected := []byte{128, 1} 61 | if !bytes.Equal(encoder.Bytes(), expected) { 62 | t.Errorf("Expected buffer to be %v, got %v", expected, encoder.Bytes()) 63 | } 64 | 65 | encoder.Reset() 66 | WriteVarUint(encoder, math.MaxUint64) 67 | if encoder.Len() != 10 { 68 | t.Errorf("Expected buffer length to be 8, got %d", encoder.Len()) 69 | } 70 | 71 | expected = []byte{255, 255, 255, 255, 255, 255, 255, 255, 255, 1} 72 | if !bytes.Equal(encoder.Bytes(), expected) { 73 | t.Errorf("Expected buffer to be %v, got %v", expected, encoder.Bytes()) 74 | } 75 | } 76 | 77 | func TestWriteVarInt(t *testing.T) { 78 | encoder := bytes.NewBuffer(nil) 79 | WriteVarInt(encoder, 128) 80 | if encoder.Len() != 2 { 81 | t.Errorf("Expected buffer length to be 2, got %d", encoder.Len()) 82 | } 83 | expected := []byte{128, 2} 84 | if !bytes.Equal(encoder.Bytes(), expected) { 85 | t.Errorf("Expected buffer to be %v, got %v", expected, encoder.Bytes()) 86 | } 87 | encoder.Reset() 88 | WriteVarInt(encoder, -128) 89 | if encoder.Len() != 2 { 90 | t.Errorf("Expected buffer length to be 2, got %d", encoder.Len()) 91 | } 92 | 93 | expected = []byte{192, 2} 94 | if !bytes.Equal(encoder.Bytes(), expected) { 95 | t.Errorf("Expected buffer to be %v, got %v", expected, encoder.Bytes()) 96 | } 97 | } 98 | 99 | func TestWriteFloat32(t *testing.T) { 100 | encoder := bytes.NewBuffer(nil) 101 | WriteFloat32(encoder, 1.0) 102 | if encoder.Len() != 4 { 103 | t.Errorf("Expected buffer length to be 4, got %d", encoder.Len()) 104 | } 105 | expected := []byte{63, 128, 0, 0} 106 | if !bytes.Equal(encoder.Bytes(), expected) { 107 | t.Errorf("Expected buffer to be %v, got %v", expected, encoder.Bytes()) 108 | } 109 | } 110 | 111 | func TestWriteFloat64(t *testing.T) { 112 | encoder := bytes.NewBuffer(nil) 113 | WriteFloat64(encoder, 1.0) 114 | if encoder.Len() != 8 { 115 | t.Errorf("Expected buffer length to be 8, got %d", encoder.Len()) 116 | } 117 | expected := []byte{63, 240, 0, 0, 0, 0, 0, 0} 118 | if !bytes.Equal(encoder.Bytes(), expected) { 119 | t.Errorf("Expected buffer to be %v, got %v", expected, encoder.Bytes()) 120 | } 121 | } 122 | 123 | func TestWriteInt64(t *testing.T) { 124 | encoder := bytes.NewBuffer(nil) 125 | WriteInt64(encoder, 1) 126 | if encoder.Len() != 8 { 127 | t.Errorf("Expected buffer length to be 8, got %d", encoder.Len()) 128 | } 129 | expected := []byte{0, 0, 0, 0, 0, 0, 0, 1} 130 | if !bytes.Equal(encoder.Bytes(), expected) { 131 | t.Errorf("Expected buffer to be %v, got %v", expected, encoder.Bytes()) 132 | } 133 | } 134 | 135 | func TestWriteString(t *testing.T) { 136 | encoder := bytes.NewBuffer(nil) 137 | WriteString(encoder, "hello") 138 | if encoder.Len() != 6 { 139 | t.Errorf("Expected buffer length to be 6, got %d", encoder.Len()) 140 | } 141 | expected := []byte{5, 104, 101, 108, 108, 111} 142 | if !bytes.Equal(encoder.Bytes(), expected) { 143 | t.Errorf("Expected buffer to be %v, got %v", expected, encoder.Bytes()) 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /y_xml_element.go: -------------------------------------------------------------------------------- 1 | package y_crdt 2 | 3 | import ( 4 | "fmt" 5 | "sort" 6 | "strings" 7 | ) 8 | 9 | const DefaultNodeName = "UNDEFINED" 10 | 11 | type YXmlElement struct { 12 | YXmlFragment 13 | 14 | PrelimAttrs map[string]interface{} 15 | NodeName string 16 | } 17 | 18 | // GetNextSibling return {YXmlElement|YXmlText|nil} 19 | func (y *YXmlElement) GetNextSibling() IAbstractType { 20 | var n *Item 21 | if y.Item != nil { 22 | n = y.Item.Next() 23 | } 24 | 25 | if n != nil { 26 | t, ok := n.Content.(*ContentType) 27 | if ok { 28 | return t.Type 29 | } 30 | } 31 | 32 | return nil 33 | } 34 | 35 | // GetPrevSibling return {YXmlElement|YXmlText|nil} 36 | func (y *YXmlElement) GetPrevSibling() IAbstractType { 37 | var n *Item 38 | if y.Item != nil { 39 | n = y.Item.Prev() 40 | } 41 | 42 | if n != nil { 43 | t, ok := n.Content.(*ContentType) 44 | if ok { 45 | return t.Type 46 | } 47 | } 48 | 49 | return nil 50 | } 51 | 52 | // Integrate this type into the Yjs instance. 53 | // 54 | // Save this struct in the os 55 | // This type is sent to other client 56 | // Observer functions are fired 57 | func (y *YXmlElement) Integrate(doc *Doc, item *Item) { 58 | y.AbstractType.Integrate(doc, item) 59 | 60 | for key, value := range y.PrelimAttrs { 61 | y.SetAttribute(key, value) 62 | } 63 | 64 | y.PrelimAttrs = nil 65 | } 66 | 67 | // Copy Creates an Item with the same effect as this Item (without position effect) 68 | func (y *YXmlElement) Copy() IAbstractType { 69 | return NewYXmlElement(y.NodeName) 70 | } 71 | 72 | func (y *YXmlElement) Clone() IAbstractType { 73 | el := NewYXmlElement(y.NodeName) 74 | attrs := y.GetAttributes() 75 | 76 | for key, value := range attrs { 77 | el.SetAttribute(key, value) 78 | } 79 | 80 | var data []interface{} 81 | for _, element := range y.ToArray() { 82 | item, ok := element.(IAbstractType) 83 | if ok { 84 | data = append(data, item.Clone()) 85 | } else { 86 | data = append(data, element) 87 | } 88 | } 89 | 90 | el.Insert(0, data) 91 | return el 92 | } 93 | 94 | // Returns the XML serialization of this YXmlElement. 95 | // The attributes are ordered by attribute-name, so you can easily use this 96 | // method to compare YXmlElements 97 | // 98 | // @return {string} The string representation of this type. 99 | func (y *YXmlElement) ToString() string { 100 | attrs := y.GetAttributes() 101 | var stringBuilder []string 102 | 103 | keys := make([]string, 0, len(attrs)) 104 | for key, _ := range attrs { 105 | keys = append(keys, key) 106 | } 107 | sort.Strings(keys) 108 | 109 | for _, key := range keys { 110 | str := fmt.Sprintf("%s=\"%v\"", key, attrs[key]) 111 | stringBuilder = append(stringBuilder, str) 112 | } 113 | 114 | nodeName := strings.ToLower(y.NodeName) 115 | 116 | var attrsString string 117 | if len(stringBuilder) > 0 { 118 | attrsString = fmt.Sprintf(" %s", strings.Join(stringBuilder, " ")) 119 | } 120 | 121 | return fmt.Sprintf(`<%s%s>%s`, nodeName, attrsString, y.YXmlFragment.ToString(), nodeName) 122 | } 123 | 124 | // RemoveAttribute Removes an attribute from this YXmlElement. 125 | func (y *YXmlElement) RemoveAttribute(attributeName string) { 126 | if y.Doc != nil { 127 | Transact(y.Doc, func(trans *Transaction) { 128 | TypeMapDelete(trans, y, attributeName) 129 | }, nil, true) 130 | } else { 131 | delete(y.PrelimAttrs, attributeName) 132 | } 133 | } 134 | 135 | // SetAttribute Sets or updates an attribute. 136 | func (y *YXmlElement) SetAttribute(attributeName string, attributeValue interface{}) { 137 | if y.Doc != nil { 138 | Transact(y.Doc, func(trans *Transaction) { 139 | TypeMapSet(trans, y, attributeName, attributeValue) 140 | }, nil, true) 141 | } else { 142 | y.PrelimAttrs[attributeName] = attributeValue 143 | } 144 | } 145 | 146 | // GetAttribute Returns an attribute value that belongs to the attribute name. 147 | func (y *YXmlElement) GetAttribute(attributeName string) interface{} { 148 | return TypeMapGet(y, attributeName) 149 | } 150 | 151 | // HasAttribute Returns whether an attribute exists 152 | func (y *YXmlElement) HasAttribute(attributeName string) bool { 153 | return TypeMapHas(y, attributeName) 154 | } 155 | 156 | // GetAttributes Returns an attribute value that belongs to the attribute name. 157 | func (y *YXmlElement) GetAttributes() Object { 158 | return TypeMapGetAll(y) 159 | } 160 | 161 | // ToDOM Creates a Dom Element that mirrors this YXmlElement. 162 | func (y *YXmlElement) ToDOM() { 163 | 164 | } 165 | 166 | func (y *YXmlElement) Write(encoder *UpdateEncoderV1) { 167 | encoder.WriteTypeRef(YXmlElementRefID) 168 | err := encoder.WriteKey(y.NodeName) 169 | if err != nil { 170 | Logf("[crdt] %s.", err.Error()) 171 | } 172 | } 173 | 174 | func NewYXmlElement(nodeName string) *YXmlElement { 175 | el := &YXmlElement{NodeName: nodeName} 176 | el.PrelimAttrs = make(map[string]interface{}) 177 | return el 178 | } 179 | -------------------------------------------------------------------------------- /y_array.go: -------------------------------------------------------------------------------- 1 | package y_crdt 2 | 3 | // Event that describes the changes on a YArray 4 | type YArrayEvent struct { 5 | YEvent 6 | YTrans *Transaction 7 | } 8 | 9 | func NewYArrayEvent(yarray *YArray, trans *Transaction) *YArrayEvent { 10 | y := &YArrayEvent{ 11 | YEvent: *NewYEvent(yarray, trans), 12 | YTrans: trans, 13 | } 14 | 15 | return y 16 | } 17 | 18 | // A shared Array implementation. 19 | type YArray struct { 20 | AbstractType 21 | PrelimContent ArrayAny 22 | SearchMaker []*ArraySearchMarker 23 | } 24 | 25 | // Construct a new YArray containing the specified items. 26 | func (y *YArray) From(items ArrayAny) *YArray { 27 | a := NewYArray() 28 | a.Push(items) 29 | return a 30 | } 31 | 32 | // Integrate this type into the Yjs instance. 33 | // 34 | // Save this struct in the os 35 | // This type is sent to other client 36 | // Observer functions are fired 37 | func (y *YArray) Integrate(doc *Doc, item *Item) { 38 | y.AbstractType.Integrate(doc, item) 39 | y.Insert(0, y.PrelimContent) 40 | y.PrelimContent = nil 41 | } 42 | 43 | func (y *YArray) Copy() IAbstractType { 44 | return NewYArray() 45 | } 46 | 47 | func (y *YArray) Clone() IAbstractType { 48 | arr := NewYArray() 49 | 50 | var content []interface{} 51 | for _, el := range y.ToArray() { 52 | a, ok := el.(IAbstractType) 53 | if ok { 54 | content = append(content, a.Clone()) 55 | } else { 56 | content = append(content, el) 57 | } 58 | } 59 | 60 | arr.Insert(0, content) 61 | return arr 62 | } 63 | 64 | func (y *YArray) GetLength() Number { 65 | if y.PrelimContent == nil { 66 | return y.Length 67 | } 68 | 69 | return len(y.PrelimContent) 70 | } 71 | 72 | // Creates YArrayEvent and calls observers. 73 | func (y *YArray) CallObserver(trans *Transaction, parentSubs Set) { 74 | y.AbstractType.CallObserver(trans, parentSubs) 75 | CallTypeObservers(y, trans, NewYArrayEvent(y, trans)) 76 | } 77 | 78 | // Inserts new content at an index. 79 | // 80 | // Important: This function expects an array of content. Not just a content 81 | // object. The reason for this "weirdness" is that inserting several elements 82 | // is very efficient when it is done as a single operation. 83 | // 84 | // @example 85 | // // Insert character 'a' at position 0 86 | // yarray.insert(0, ['a']) 87 | // // Insert numbers 1, 2 at position 1 88 | // yarray.insert(1, [1, 2]) 89 | func (y *YArray) Insert(index Number, content ArrayAny) { 90 | if y.Doc != nil { 91 | Transact(y.Doc, func(trans *Transaction) { 92 | TypeListInsertGenerics(trans, y, index, content) 93 | }, nil, true) 94 | } else { 95 | SpliceArray(&y.PrelimContent, index, 0, content) 96 | } 97 | } 98 | 99 | // Appends content to this YArray. 100 | func (y *YArray) Push(content ArrayAny) { 101 | y.Insert(y.Length, content) 102 | } 103 | 104 | // Preppends content to this YArray. 105 | func (y *YArray) Unshift(content ArrayAny) { 106 | y.Insert(0, content) 107 | } 108 | 109 | // Deletes elements starting from an index. 110 | func (y *YArray) Delete(index, length Number) { 111 | if y.Doc != nil { 112 | Transact(y.Doc, func(trans *Transaction) { 113 | TypeListDelete(trans, y, index, length) 114 | }, nil, true) 115 | } else { 116 | SpliceArray(&y.PrelimContent, index, length, nil) 117 | } 118 | } 119 | 120 | // Returns the i-th element from a YArray. 121 | func (y *YArray) Get(index Number) interface{} { 122 | return TypeListGet(y, index) 123 | } 124 | 125 | // Transforms this YArray to a JavaScript Array. 126 | func (y *YArray) ToArray() ArrayAny { 127 | return TypeListToArray(y) 128 | } 129 | 130 | // Transforms this YArray to a JavaScript Array. 131 | func (y *YArray) Splice(start, end Number) ArrayAny { 132 | return TypeListSlice(y, start, end) 133 | } 134 | 135 | // Transforms this Shared Type to a JSON object. 136 | func (y *YArray) ToJson() interface{} { 137 | v := y.Map(func(c interface{}, _ Number, _ IAbstractType) interface{} { 138 | a, ok := c.(IAbstractType) 139 | if ok { 140 | return a.ToJson() 141 | } else { 142 | return c 143 | } 144 | }) 145 | 146 | if v == nil { 147 | return ArrayAny{} 148 | } 149 | 150 | return v 151 | } 152 | 153 | // Returns an Array with the result of calling a provided function on every 154 | // element of this YArray. 155 | func (y *YArray) Map(f func(interface{}, Number, IAbstractType) interface{}) ArrayAny { 156 | return TypeListMap(y, f) 157 | } 158 | 159 | // Executes a provided function on once on overy element of this YArray. 160 | func (y *YArray) ForEach(f func(interface{}, Number, IAbstractType)) { 161 | TypeListForEach(y, f) 162 | } 163 | 164 | func (y *YArray) Range(f func(item *Item)) { 165 | n := y.Start 166 | for ; n != nil; n = n.Right { 167 | if !n.Deleted() { 168 | f(n) 169 | } 170 | } 171 | } 172 | 173 | func (y *YArray) Write(encoder *UpdateEncoderV1) { 174 | encoder.WriteTypeRef(YArrayRefID) 175 | } 176 | 177 | func NewYArray() *YArray { 178 | a := &YArray{} 179 | a.EH = NewEventHandler() 180 | a.DEH = NewEventHandler() 181 | a.AbstractType.Map = make(map[string]*Item) 182 | return a 183 | } 184 | 185 | func NewYArrayType() IAbstractType { 186 | return NewYArray() 187 | } 188 | -------------------------------------------------------------------------------- /permanent_user_data.go: -------------------------------------------------------------------------------- 1 | package y_crdt 2 | 3 | type PermanentUserData struct { 4 | YUsers IAbstractType 5 | Doc *Doc 6 | Clients map[Number]string 7 | Dss map[string]*DeleteSet 8 | } 9 | 10 | func (p *PermanentUserData) SetUserMapping(doc *Doc, clientID Number, userDescription string, filer func(trans *Transaction, set *DeleteSet) bool) { 11 | users := p.YUsers.(*YMap) 12 | user, ok := users.Get(userDescription).(*YMap) 13 | if !ok { 14 | user = NewYMap(nil) 15 | user.Set("ids", NewYArray()) 16 | user.Set("ds", NewYArray()) 17 | users.Set(userDescription, user) 18 | } 19 | 20 | a := user.Get("ids").(*YArray) 21 | a.Push(ArrayAny{clientID}) 22 | users.Observe(func(e interface{}, t interface{}) { 23 | userOverWrite := users.Get(userDescription).(*YMap) 24 | if userOverWrite != user { 25 | // user was overwritten, port all data over to the next user object 26 | // @todo Experiment with Y.Sets here 27 | user = userOverWrite 28 | 29 | // @todo iterate over old type 30 | for clientID, _userDescription := range p.Clients { 31 | if userDescription == _userDescription { 32 | a := user.Get("ids").(*YArray) 33 | a.Push(ArrayAny{clientID}) 34 | } 35 | } 36 | 37 | encoder := NewUpdateEncoderV1() 38 | ds := p.Dss[userDescription] 39 | if ds != nil { 40 | WriteDeleteSet(encoder, ds) 41 | a := user.Get("ds").(*YArray) 42 | a.Push(ArrayAny{encoder.ToUint8Array()}) 43 | } 44 | } 45 | }) 46 | 47 | doc.On("afterTransaction", NewObserverHandler(func(v ...interface{}) { 48 | trans := v[0].(*Transaction) 49 | yds := user.Get("ds").(*YArray) 50 | ds := trans.DeleteSet 51 | if trans.Local && len(ds.Clients) > 0 && filer(trans, ds) { 52 | encoder := NewUpdateEncoderV1() 53 | WriteDeleteSet(encoder, ds) 54 | yds.Push(ArrayAny{encoder.ToUint8Array()}) 55 | } 56 | })) 57 | } 58 | 59 | func (p *PermanentUserData) GetUserByClientID(clientID Number) string { 60 | return p.Clients[clientID] 61 | } 62 | 63 | func (p *PermanentUserData) GetUserByDeletedID(id *ID) string { 64 | for userDescription, ds := range p.Dss { 65 | if IsDeleted(ds, id) { 66 | return userDescription 67 | } 68 | } 69 | 70 | return "" 71 | } 72 | 73 | func NewPermanentUserData(doc *Doc, storeType IAbstractType) *PermanentUserData { 74 | if storeType == nil { 75 | storeType = doc.GetMap("users") 76 | } 77 | 78 | dss := make(map[string]*DeleteSet) 79 | 80 | p := &PermanentUserData{ 81 | YUsers: storeType, 82 | Doc: doc, 83 | Clients: make(map[Number]string), 84 | Dss: dss, 85 | } 86 | 87 | initUser := func(user *YMap, userDescription string) { 88 | ds, _ := user.Get("ds").(*YArray) 89 | ids, _ := user.Get("ids").(*YArray) 90 | 91 | if ds == nil || ids == nil { 92 | return 93 | } 94 | 95 | addClientId := func(clientID Number) { 96 | p.Clients[clientID] = userDescription 97 | } 98 | 99 | ds.ObserveDeep(func(e interface{}, t interface{}) { 100 | event := e.(*YArrayEvent) 101 | a := event.Changes["added"].(Set) 102 | a.Range(func(element interface{}) { 103 | item, ok := element.(*Item) 104 | if ok { 105 | for _, encodeDs := range item.Content.GetContent() { 106 | data, ok := encodeDs.([]uint8) 107 | if ok { 108 | delSet, exist := p.Dss[userDescription] 109 | if !exist { 110 | delSet = NewDeleteSet() 111 | } 112 | p.Dss[userDescription] = MergeDeleteSets([]*DeleteSet{delSet, ReadDeleteSet(NewUpdateDecoderV1(data))}) 113 | } 114 | } 115 | } 116 | }) 117 | }) 118 | 119 | var delSet []*DeleteSet 120 | ds.Map(func(encodeDs interface{}, number Number, abstractType IAbstractType) interface{} { 121 | data, ok := encodeDs.([]uint8) 122 | if ok { 123 | delSet = append(delSet, ReadDeleteSet(NewUpdateDecoderV1(data))) 124 | } 125 | return nil 126 | }) 127 | 128 | p.Dss[userDescription] = MergeDeleteSets(delSet) 129 | ids.Observe(func(e interface{}, t interface{}) { 130 | event, ok := e.(*YArrayEvent) 131 | if !ok { 132 | return 133 | } 134 | a := event.GetChanges()["added"].(Set) 135 | a.Range(func(element interface{}) { 136 | item, ok := element.(*Item) 137 | if ok { 138 | arr := item.Content.GetContent() 139 | for _, j := range arr { 140 | n, _ := j.(Number) 141 | addClientId(n) 142 | } 143 | } 144 | }) 145 | }) 146 | 147 | ids.ForEach(func(i interface{}, number Number, abstractType IAbstractType) { 148 | clientID := i.(Number) 149 | addClientId(clientID) 150 | }) 151 | } 152 | 153 | storeType.Observe(func(e interface{}, t interface{}) { 154 | event := e.(*YMapEvent) 155 | a := event.KeysChanged 156 | a.Range(func(element interface{}) { 157 | userDescription := element.(string) 158 | if m, ok := storeType.(*YMap); ok { 159 | if user, ok := m.Get(userDescription).(*YMap); ok { 160 | initUser(user, userDescription) 161 | } else { 162 | Logf("cannot get user. storeType:%+v userDescription:%s", storeType, userDescription) 163 | } 164 | } else { 165 | Logf("storeType is not *YMap. storeType:%+v", storeType) 166 | } 167 | }) 168 | }) 169 | 170 | storeType.(*YMap).ForEach(func(s string, i interface{}, yMap *YMap) { 171 | initUser(yMap, s) 172 | }) 173 | 174 | return p 175 | } 176 | -------------------------------------------------------------------------------- /utils_test.go: -------------------------------------------------------------------------------- 1 | package y_crdt 2 | 3 | import ( 4 | "fmt" 5 | "math/rand/v2" 6 | "strings" 7 | "testing" 8 | ) 9 | 10 | // BenchmarkMergeString benchmark the performance of MergeStringV1, MergeStringV2, MergeStringV3. 11 | // The benchmark is run 100000 times, and the result is as follows: 12 | // 13 | // str length: 100 14 | // BenchmarkMergeString/MergeStringV1 59.16 ns/op 208 B/op 1 allocs/op 15 | // BenchmarkMergeString/MergeStringV2 81.39 ns/op 208 B/op 1 allocs/op 16 | // BenchmarkMergeString/MergeStringV3 170.2 ns/op 240 B/op 3 allocs/op 17 | // 18 | // str length: 1000 19 | // BenchmarkMergeString/MergeStringV1 279.7 ns/op 2048 B/op 1 allocs/op 20 | // BenchmarkMergeString/MergeStringV2 287.2 ns/op 2048 B/op 1 allocs/op 21 | // BenchmarkMergeString/MergeStringV3 360.7 ns/op 2080 B/op 3 allocs/op 22 | // 23 | // str length: 7000 24 | // BenchmarkMergeString/MergeStringV1 1285 ns/op 14336 B/op 1 allocs/op 25 | // BenchmarkMergeString/MergeStringV2 1270 ns/op 14336 B/op 1 allocs/op 26 | // BenchmarkMergeString/MergeStringV3 1597 ns/op 14368 B/op 3 allocs/op 27 | 28 | func BenchmarkMergeString(b *testing.B) { 29 | contents := make([]string, 100000) 30 | for i := 0; i < 100000; i++ { 31 | contents[i] = randString(5000 + rand.IntN(100)) 32 | } 33 | 34 | b.Run("MergeStringV1", func(b *testing.B) { 35 | for i := 0; i < b.N; i++ { 36 | MergeStringV1(contents[i%100000], contents[(i+1)%100000]) 37 | } 38 | }) 39 | 40 | b.Run("MergeStringV2", func(b *testing.B) { 41 | for i := 0; i < b.N; i++ { 42 | MergeStringV2(contents[i%100000], contents[(i+1)%100000]) 43 | } 44 | }) 45 | 46 | b.Run("MergeStringV3", func(b *testing.B) { 47 | for i := 0; i < b.N; i++ { 48 | MergeStringV3(contents[i%100000], contents[(i+1)%100000]) 49 | } 50 | }) 51 | } 52 | 53 | // user str1 + str2 to merge two strings. 54 | func MergeStringV1(str1, str2 string) string { 55 | return str1 + str2 56 | } 57 | 58 | // use strings.Builder to merge two strings. 59 | func MergeStringV2(str1, str2 string) string { 60 | builder := strings.Builder{} 61 | builder.Grow(len(str1) + len(str2)) 62 | builder.WriteString(str1) 63 | builder.WriteString(str2) 64 | return builder.String() 65 | } 66 | 67 | func MergeStringV3(str1, str2 string) string { 68 | return fmt.Sprintf("%s%s", str1, str2) 69 | } 70 | 71 | // randString generate a random string with length n. 72 | func randString(n int) string { 73 | b := make([]byte, n) 74 | for i := range b { 75 | // generate a random character between 'a' (97) and 'z' (122) 76 | b[i] = byte(rand.IntN(26) + 97) 77 | } 78 | 79 | return string(b) 80 | } 81 | 82 | func TestSpliceStruc(t *testing.T) { 83 | var ss []IAbstractStruct 84 | for i := 0; i < 10; i++ { 85 | ss = append(ss, &AbstractStruct{Length: Number(i)}) 86 | } 87 | 88 | elements := []IAbstractStruct{&AbstractStruct{Length: 100}, &AbstractStruct{Length: 200}, &AbstractStruct{Length: 300}} 89 | SpliceStruct(&ss, 3, 5, elements) 90 | 91 | for i := 0; i < 3; i++ { 92 | if ss[i].GetLength() != Number(i) { 93 | t.Errorf("SpliceStruc(ss, 3, 5)[%d] = %d, want %d", i, ss[i].GetLength(), i) 94 | } 95 | } 96 | 97 | for i := 3; i < 6; i++ { 98 | if ss[i].GetLength() != Number(i-2)*100 { 99 | t.Errorf("SpliceStruc(ss, 3, 5)[%d] = %d, want %d", i, ss[i].GetLength(), (i-2)*100) 100 | } 101 | } 102 | 103 | for i := 6; i < 8; i++ { 104 | if ss[i].GetLength() != Number(i+2) { 105 | t.Errorf("SpliceStruc(ss, 3, 5)[%d] = %d, want %d", i, ss[i].GetLength(), i+2) 106 | } 107 | } 108 | 109 | ss = make([]IAbstractStruct, 0, 10) 110 | for i := 0; i < 10; i++ { 111 | ss = append(ss, &AbstractStruct{Length: Number(i)}) 112 | } 113 | 114 | SpliceStruct(&ss, 3, 1, []IAbstractStruct{&AbstractStruct{Length: 100}, &AbstractStruct{Length: 200}}) 115 | for i := 0; i < 3; i++ { 116 | if ss[i].GetLength() != Number(i) { 117 | t.Errorf("SpliceStruc(ss, 3, 1)[%d] = %d, want %d", i, ss[i].GetLength(), i) 118 | } 119 | } 120 | 121 | for i := 3; i < 5; i++ { 122 | if ss[i].GetLength() != Number(i-2)*100 { 123 | t.Errorf("SpliceStruc(ss, 3, 1)[%d] = %d, want %d", i, ss[i].GetLength(), (i-2)*100) 124 | } 125 | } 126 | 127 | for i := 5; i < 10; i++ { 128 | if ss[i].GetLength() != Number(i-1) { 129 | t.Errorf("SpliceStruc(ss, 3, 1)[%d] = %d, want %d", i, ss[i].GetLength(), i-1) 130 | } 131 | } 132 | 133 | ss = make([]IAbstractStruct, 0, 10) 134 | for i := 0; i < 8; i++ { 135 | ss = append(ss, &AbstractStruct{Length: Number(i)}) 136 | } 137 | SpliceStruct(&ss, 3, 1, []IAbstractStruct{&AbstractStruct{Length: 100}, &AbstractStruct{Length: 200}, &AbstractStruct{Length: 300}}) 138 | for i := 0; i < 3; i++ { 139 | if ss[i].GetLength() != Number(i) { 140 | t.Errorf("SpliceStruc(ss, 3, 1)[%d] = %d, want %d", i, ss[i].GetLength(), i) 141 | } 142 | } 143 | for i := 3; i < 6; i++ { 144 | if ss[i].GetLength() != Number(i-2)*100 { 145 | t.Errorf("SpliceStruc(ss, 3, 1)[%d] = %d, want %d", i, ss[i].GetLength(), (i-2)*100) 146 | } 147 | } 148 | for i := 6; i < 10; i++ { 149 | if ss[i].GetLength() != Number(i-2) { 150 | t.Errorf("SpliceStruc(ss, 3, 1)[%d] = %d, want %d", i, ss[i].GetLength(), i-2) 151 | } 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /encoding.go: -------------------------------------------------------------------------------- 1 | package y_crdt 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "math" 7 | ) 8 | 9 | // WriteByte writes a single uint8 number to the encoder buffer. 10 | func WriteByte(encoder *bytes.Buffer, number uint8) { 11 | buf := make([]byte, 1) 12 | buf[0] = number 13 | encoder.Write(buf) 14 | } 15 | 16 | // WriteUint8Array writes a byte array to the encoder buffer. 17 | // The first byte is the length of the array, and the following bytes are the array elements. 18 | func WriteVarUint8Array(encoder *bytes.Buffer, buf []uint8) { 19 | WriteVarUint(encoder, uint64(len(buf))) 20 | encoder.Write(buf) 21 | } 22 | 23 | // WriteUint8Array writes a byte array to the encoder buffer. 24 | // The first byte is the length of the array, and the following bytes are the array elements. 25 | func WriteUint8Array(encoder *bytes.Buffer, buf []uint8) { 26 | encoder.Write(buf) 27 | } 28 | 29 | // WriteVarUint writes a variable-length uint64 number to the encoder buffer. 30 | func WriteVarUint(encoder *bytes.Buffer, number uint64) { 31 | buf := make([]byte, binary.MaxVarintLen64) 32 | size := binary.PutUvarint(buf, number) 33 | encoder.Write(buf[:size]) 34 | } 35 | 36 | // WriteVarInt writes a variable-length int64 number to the encoder buffer. 37 | func WriteVarInt(encoder *bytes.Buffer, number int) { 38 | bitSign := 0 // sign bit 39 | if number < 0 { 40 | number = -number 41 | bitSign = BIT7 42 | } 43 | 44 | // bitNext indicates whether there are more bytes to read after this one. 45 | // If the highest bit is 1, there are more bytes to read. 46 | bitNext := 0 47 | if number > BITS6 { 48 | bitNext = BIT8 49 | } 50 | 51 | buf := make([]byte, 1) 52 | buf[0] = uint8(bitNext|bitSign) | uint8(BITS6&number) // [next_flag sign_flag low_6_bits_data] 53 | encoder.Write(buf) 54 | 55 | number >>= 6 56 | 57 | for number > 0 { 58 | bitNext := uint8(0) 59 | if number > BITS7 { 60 | bitNext = BIT8 61 | } 62 | 63 | buf := make([]byte, 1) 64 | buf[0] = bitNext | uint8(BITS7&number) // [next_flag low_7_bits_data] 65 | encoder.Write(buf) 66 | number >>= 7 67 | } 68 | } 69 | 70 | // WriteFloat32 writes a 4-byte float32 to the encoder buffer using big-endian encoding. 71 | func WriteFloat32(encoder *bytes.Buffer, f float32) { 72 | buf := make([]byte, 4) 73 | binary.BigEndian.PutUint32(buf, math.Float32bits(f)) 74 | encoder.Write(buf) 75 | } 76 | 77 | // WriteFloat64 writes an 8-byte float64 to the encoder buffer using big-endian encoding. 78 | func WriteFloat64(encoder *bytes.Buffer, f float64) { 79 | bs := make([]byte, 8) 80 | binary.BigEndian.PutUint64(bs, math.Float64bits(f)) 81 | encoder.Write(bs) 82 | } 83 | 84 | // WriteInt64 writes an 8-byte int64 to the encoder buffer using big-endian encoding. 85 | func WriteInt64(encoder *bytes.Buffer, n int64) { 86 | bs := make([]byte, 8) 87 | binary.BigEndian.PutUint64(bs, uint64(n)) 88 | encoder.Write(bs) 89 | } 90 | 91 | // WriteString writes a variable-length string to the encoder buffer. 92 | func WriteString(encoder *bytes.Buffer, str string) error { 93 | data := []byte(str) 94 | size := uint64(len(data)) 95 | 96 | WriteVarUint(encoder, size) 97 | return binary.Write(encoder, binary.LittleEndian, data) 98 | } 99 | 100 | // WriteObject writes an object to the encoder buffer. 101 | func WriteObject(encoder *bytes.Buffer, obj Object) error { 102 | // wirte the object size. 103 | WriteVarUint(encoder, uint64(len(obj))) 104 | 105 | // write the object key-value pairs. 106 | for key, value := range obj { 107 | err := WriteString(encoder, key) 108 | if err != nil { 109 | return err 110 | } 111 | 112 | WriteAny(encoder, value) 113 | } 114 | 115 | return nil 116 | } 117 | 118 | // WriteArray writes an array(any) to the encoder buffer. 119 | func WriteArray(encoder *bytes.Buffer, array []any) error { 120 | // write the array size. 121 | WriteVarUint(encoder, uint64(len(array))) 122 | 123 | // write the array elements. 124 | for _, value := range array { 125 | if err := WriteAny(encoder, value); err != nil { 126 | return err 127 | } 128 | } 129 | 130 | return nil 131 | } 132 | 133 | // WriteAny writes any type to the encoder buffer. 134 | func WriteAny(encoder *bytes.Buffer, any any) error { 135 | if IsUndefined(any) { 136 | WriteByte(encoder, 127) 137 | return nil 138 | } 139 | 140 | if IsNull(any) { 141 | WriteByte(encoder, 126) 142 | return nil 143 | } 144 | 145 | switch v := any.(type) { 146 | case string: 147 | WriteByte(encoder, 119) 148 | if err := WriteString(encoder, v); err != nil { 149 | return err 150 | } 151 | case int8: 152 | WriteByte(encoder, 125) 153 | WriteVarInt(encoder, Number(v)) 154 | case int16: 155 | WriteByte(encoder, 125) 156 | WriteVarInt(encoder, Number(v)) 157 | case Number: 158 | WriteByte(encoder, 125) 159 | WriteVarInt(encoder, v) 160 | case int64: 161 | WriteByte(encoder, 122) 162 | WriteInt64(encoder, v) 163 | case float32: 164 | WriteByte(encoder, 124) 165 | WriteFloat32(encoder, v) 166 | case float64: 167 | WriteByte(encoder, 123) 168 | WriteFloat64(encoder, v) 169 | case bool: 170 | if v { 171 | WriteByte(encoder, 120) 172 | } else { 173 | WriteByte(encoder, 121) 174 | } 175 | case []uint8: 176 | WriteByte(encoder, 116) 177 | WriteVarUint8Array(encoder, v) 178 | case ArrayAny: 179 | WriteVarUint(encoder, 117) 180 | WriteArray(encoder, v) 181 | case Object: 182 | WriteByte(encoder, 118) 183 | if err := WriteObject(encoder, v); err != nil { 184 | return err 185 | } 186 | default: 187 | WriteByte(encoder, 126) 188 | } 189 | 190 | return nil 191 | } 192 | -------------------------------------------------------------------------------- /y_map.go: -------------------------------------------------------------------------------- 1 | package y_crdt 2 | 3 | import ( 4 | "reflect" 5 | ) 6 | 7 | type YMapIter struct { 8 | Key string 9 | Item *Item 10 | } 11 | 12 | // Event that describes the changes on a YMap. 13 | type YMapEvent struct { 14 | YEvent 15 | KeysChanged Set 16 | } 17 | 18 | func NewYMapEvent(ymap *YMap, trans *Transaction, subs Set) *YMapEvent { 19 | return &YMapEvent{ 20 | YEvent: *NewYEvent(ymap, trans), 21 | KeysChanged: subs, 22 | } 23 | } 24 | 25 | // A shared Map implementation. 26 | type YMap struct { 27 | AbstractType 28 | PrelimContent map[string]interface{} 29 | } 30 | 31 | // Integrate this type into the Yjs instance. 32 | // 33 | // Save this struct in the os 34 | // This type is sent to other client 35 | // Observer functions are fired 36 | func (y *YMap) Integrate(doc *Doc, item *Item) { 37 | y.AbstractType.Integrate(doc, item) 38 | for key, value := range y.PrelimContent { 39 | y.Set(key, value) 40 | } 41 | 42 | y.PrelimContent = make(map[string]interface{}) 43 | } 44 | 45 | func (y *YMap) Copy() IAbstractType { 46 | return NewYMap(nil) 47 | } 48 | 49 | func (y *YMap) Clone() IAbstractType { 50 | m := NewYMap(nil) 51 | 52 | y.ForEach(func(key string, value interface{}, yMap *YMap) { 53 | v, ok := value.(IAbstractType) 54 | if ok { 55 | m.Set(key, v.Clone()) 56 | } else { 57 | m.Set(key, value) 58 | } 59 | }) 60 | 61 | return m 62 | } 63 | 64 | // Creates YMapEvent and calls observers. 65 | func (y *YMap) CallObserver(trans *Transaction, parentSubs Set) { 66 | CallTypeObservers(y, trans, NewYMapEvent(y, trans, parentSubs)) 67 | } 68 | 69 | // Transforms this Shared Type to a JSON object. 70 | func (y *YMap) ToJson() interface{} { 71 | m := NewObject() 72 | for key, item := range y.Map { 73 | if !item.Deleted() { 74 | v := item.Content.GetContent()[item.Length-1] 75 | t, ok := v.(IAbstractType) 76 | if ok { 77 | m[key] = t.ToJson() 78 | } else { 79 | if reflect.TypeOf(v) != reflect.TypeOf(UndefinedType{}) { 80 | m[key] = v 81 | } 82 | } 83 | } 84 | } 85 | return m 86 | } 87 | 88 | // Returns the size of the YMap (count of key/value pairs) 89 | func (y *YMap) GetSize() Number { 90 | return len(createMapIterator(y.Map)) 91 | } 92 | 93 | // Returns the keys for each element in the YMap Type. 94 | func (y *YMap) Keys() []string { 95 | its := createMapIterator(y.Map) 96 | 97 | keys := make([]string, 0, len(its)) 98 | for _, it := range its { 99 | keys = append(keys, it.Key) 100 | } 101 | 102 | return keys 103 | } 104 | 105 | // Returns the values for each element in the YMap Type. 106 | func (y *YMap) Values() []interface{} { 107 | its := createMapIterator(y.Map) 108 | 109 | values := make([]interface{}, 0, len(its)) 110 | for _, it := range its { 111 | values = append(values, it.Item.Content.GetContent()[it.Item.Length-1]) 112 | } 113 | return values 114 | } 115 | 116 | // Returns an Iterator of [key, value] pairs 117 | func (y *YMap) Entries() map[string]interface{} { 118 | m := make(map[string]interface{}) 119 | its := createMapIterator(y.Map) 120 | 121 | for _, it := range its { 122 | m[it.Key] = it.Item.Content.GetContent()[it.Item.Length-1] 123 | } 124 | 125 | return m 126 | } 127 | 128 | // Executes a provided function on once on every key-value pair. 129 | func (y *YMap) ForEach(f func(string, interface{}, *YMap)) Object { 130 | m := NewObject() 131 | for key, item := range y.Map { 132 | if !item.Deleted() { 133 | f(key, item.Content.GetContent()[item.Length-1], y) 134 | } 135 | } 136 | return m 137 | } 138 | 139 | func (y *YMap) Range(f func(key string, val interface{})) { 140 | entries := y.Entries() 141 | for key, value := range entries { 142 | f(key, value) 143 | } 144 | } 145 | 146 | // Remove a specified element from this YMap. 147 | func (y *YMap) Delete(key string) { 148 | if y.Doc != nil { 149 | Transact(y.Doc, func(trans *Transaction) { 150 | TypeMapDelete(trans, y, key) 151 | }, nil, true) 152 | } else { 153 | delete(y.PrelimContent, key) 154 | } 155 | } 156 | 157 | // Adds or updates an element with a specified key and value. 158 | func (y *YMap) Set(key string, value interface{}) interface{} { 159 | if y.Doc != nil { 160 | Transact(y.Doc, func(trans *Transaction) { 161 | TypeMapSet(trans, y, key, value) 162 | }, nil, true) 163 | } else { 164 | y.PrelimContent[key] = value 165 | } 166 | 167 | return value 168 | } 169 | 170 | // Returns a specified element from this YMap. 171 | func (y *YMap) Get(key string) interface{} { 172 | return TypeMapGet(y, key) 173 | } 174 | 175 | func (y *YMap) Has(key string) bool { 176 | return TypeMapHas(y, key) 177 | } 178 | 179 | // Removes all elements from this YMap. 180 | func (y *YMap) Clear() { 181 | if y.Doc != nil { 182 | Transact(y.Doc, func(trans *Transaction) { 183 | y.Range(func(key string, val interface{}) { 184 | TypeMapDelete(trans, y, key) 185 | }) 186 | 187 | }, nil, true) 188 | } else { 189 | y.PrelimContent = make(map[string]interface{}) 190 | } 191 | } 192 | 193 | func (y *YMap) Write(encoder *UpdateEncoderV1) { 194 | encoder.WriteTypeRef(YMapRefID) 195 | } 196 | 197 | func NewYMap(entries map[string]interface{}) *YMap { 198 | ymap := &YMap{ 199 | AbstractType: AbstractType{ 200 | Map: make(map[string]*Item), 201 | EH: NewEventHandler(), 202 | DEH: NewEventHandler(), 203 | }, 204 | } 205 | 206 | if entries == nil { 207 | ymap.PrelimContent = make(map[string]interface{}) 208 | } else { 209 | ymap.PrelimContent = entries 210 | } 211 | 212 | return ymap 213 | } 214 | 215 | func NewYMapType() IAbstractType { 216 | return NewYMap(nil) 217 | } 218 | 219 | func createMapIterator(m map[string]*Item) []YMapIter { 220 | var its []YMapIter 221 | 222 | for key, item := range m { 223 | if !item.Deleted() { 224 | its = append(its, YMapIter{ 225 | Key: key, 226 | Item: item, 227 | }) 228 | } 229 | } 230 | 231 | return its 232 | } 233 | -------------------------------------------------------------------------------- /struct_store.go: -------------------------------------------------------------------------------- 1 | package y_crdt 2 | 3 | import ( 4 | "errors" 5 | ) 6 | 7 | type StructStore struct { 8 | Clients map[Number]*[]IAbstractStruct 9 | PendingStructs *RestStructs 10 | PendingDs []uint8 11 | } 12 | 13 | func (ss *StructStore) GetStructs(client Number) []IAbstractStruct { 14 | return *ss.Clients[client] 15 | } 16 | 17 | func NewStructStore() *StructStore { 18 | return &StructStore{ 19 | Clients: make(map[Number]*[]IAbstractStruct), 20 | } 21 | } 22 | 23 | // Return the states as a Map. 24 | // Note that clock refers to the next expected clock id. 25 | func GetStateVector(store *StructStore) map[Number]Number { 26 | sm := make(map[Number]Number) 27 | 28 | for client, structs := range store.Clients { 29 | s := (*structs)[len(*structs)-1] 30 | sm[client] = s.GetID().Clock + s.GetLength() 31 | } 32 | 33 | return sm 34 | } 35 | 36 | func GetState(store *StructStore, client Number) Number { 37 | structs, exist := store.Clients[client] 38 | if !exist { 39 | return 0 40 | } 41 | 42 | lastStruct := (*structs)[len(*structs)-1] 43 | return lastStruct.GetID().Clock + lastStruct.GetLength() 44 | } 45 | 46 | func IntegretyCheck(store *StructStore) error { 47 | for _, structs := range store.Clients { 48 | for i := 1; i < len(*structs); i++ { 49 | l := (*structs)[i-1] 50 | r := (*structs)[i] 51 | 52 | if l.GetID().Clock+l.GetLength() != r.GetID().Clock { 53 | return errors.New("StructStore failed integrety check") 54 | } 55 | } 56 | } 57 | 58 | return nil 59 | } 60 | 61 | func AddStruct(store *StructStore, st IAbstractStruct) error { 62 | client := st.GetID().Client 63 | ss, exist := store.Clients[client] 64 | 65 | if !exist { 66 | store.Clients[client] = &[]IAbstractStruct{st} 67 | } else { 68 | lastStruct := (*ss)[len(*ss)-1] 69 | if lastStruct.GetID().Clock+lastStruct.GetLength() != st.GetID().Clock { 70 | return errors.New("unexpected case") 71 | } 72 | 73 | *(store.Clients[client]) = append(*(store.Clients[client]), st) 74 | } 75 | 76 | return nil 77 | } 78 | 79 | func FindIndexSS(ss []IAbstractStruct, clock Number) (Number, error) { 80 | index, err := BinarySearch(ss, clock, 0, len(ss)-1) 81 | if err != nil { 82 | return 0, err 83 | } 84 | 85 | return index, nil 86 | } 87 | 88 | func BinarySearch(ss []IAbstractStruct, clock Number, begin, end Number) (Number, error) { 89 | if begin > end { 90 | return 0, errors.New("not found") 91 | } 92 | 93 | mid := (begin + end) / 2 94 | if ss[mid].GetID().Clock <= clock && clock < ss[mid].GetID().Clock+ss[mid].GetLength() { 95 | return mid, nil 96 | } 97 | 98 | if ss[mid].GetID().Clock <= clock { 99 | begin = mid + 1 100 | } else { 101 | end = mid - 1 102 | } 103 | 104 | return BinarySearch(ss, clock, begin, end) 105 | } 106 | 107 | func Find(store *StructStore, id ID) (IAbstractStruct, error) { 108 | ss := store.Clients[id.Client] 109 | index, err := FindIndexSS(*ss, id.Clock) 110 | if err != nil { 111 | return nil, err 112 | } 113 | 114 | return (*ss)[index], nil 115 | } 116 | 117 | func GetItem(store *StructStore, id ID) IAbstractStruct { 118 | item, err := Find(store, id) 119 | if err != nil { 120 | Logf("[crdt] %s.", err.Error()) 121 | } 122 | return item 123 | } 124 | 125 | // ss可能会被切割,所以需要按指针传递 126 | func FindIndexCleanStart(trans *Transaction, ss *[]IAbstractStruct, clock Number) (Number, error) { 127 | index, err := FindIndexSS(*ss, clock) 128 | if err != nil { 129 | return index, err 130 | } 131 | 132 | s, ok := (*ss)[index].(*Item) 133 | if ok && s.GetID().Clock < clock { 134 | items := []IAbstractStruct{SplitItem(trans, s, clock-s.GetID().Clock)} 135 | SpliceStruct(ss, index+1, 0, items) 136 | return index + 1, nil 137 | } 138 | 139 | return index, nil 140 | } 141 | 142 | // Expects that id is actually in store. This function throws or is an infinite loop otherwise. 143 | func GetItemCleanStart(trans *Transaction, id ID) *Item { 144 | ss, exist := trans.Doc.Store.Clients[id.Client] 145 | if !exist { 146 | return nil 147 | } 148 | 149 | index, err := FindIndexCleanStart(trans, ss, id.Clock) 150 | if err != nil { 151 | return nil 152 | } 153 | 154 | item, _ := (*ss)[index].(*Item) 155 | return item 156 | } 157 | 158 | // Expects that id is actually in store. This function throws or is an infinite loop otherwise. 159 | func GetItemCleanEnd(trans *Transaction, store *StructStore, id ID) *Item { 160 | ss, exist := store.Clients[id.Client] 161 | if !exist { 162 | return nil 163 | } 164 | 165 | index, err := FindIndexSS(*ss, id.Clock) 166 | if err != nil { 167 | return nil 168 | } 169 | 170 | s, ok := (*ss)[index].(*Item) 171 | if !ok { 172 | return nil 173 | } 174 | 175 | if id.Clock != s.GetID().Clock+s.GetLength()-1 { 176 | rightItem := SplitItem(trans, s, id.Clock-s.GetID().Clock+1) 177 | SpliceStruct(ss, index+1, 0, []IAbstractStruct{rightItem}) 178 | } 179 | 180 | return s 181 | } 182 | 183 | // Replace item(*GC|*Item) with newItem(*GC|*Item) in store 184 | func ReplaceStruct(store *StructStore, item IAbstractStruct, newItem IAbstractStruct) error { 185 | if item.GetID().Client != newItem.GetID().Client { 186 | return errors.New("cannot replace struct when tow items' client are different") 187 | } 188 | 189 | ss, exist := store.Clients[item.GetID().Client] 190 | if !exist { 191 | return errors.New("not exist client") 192 | } 193 | 194 | index, err := FindIndexSS(*ss, item.GetID().Clock) 195 | if err != nil { 196 | return err 197 | } 198 | 199 | (*ss)[index] = newItem 200 | return nil 201 | } 202 | 203 | // Iterate over a range of structs 204 | func IterateStructs(trans *Transaction, ss *[]IAbstractStruct, clockStart Number, length Number, f func(s IAbstractStruct)) { 205 | if length == 0 { 206 | return 207 | } 208 | 209 | clockEnd := clockStart + length 210 | index, err := FindIndexCleanStart(trans, ss, clockStart) 211 | if err != nil { 212 | return 213 | } 214 | 215 | for { 216 | s := (*ss)[index] 217 | index++ 218 | 219 | if clockEnd < s.GetID().Clock+s.GetLength() { 220 | _, err := FindIndexCleanStart(trans, ss, clockEnd) 221 | if err != nil { 222 | Logf("[crdt] %s.", err.Error()) 223 | } 224 | } 225 | 226 | f(s) 227 | 228 | if index >= len(*ss) || (*ss)[index].GetID().Clock >= clockEnd { 229 | break 230 | } 231 | } 232 | } 233 | -------------------------------------------------------------------------------- /doc.go: -------------------------------------------------------------------------------- 1 | package y_crdt 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | type Doc struct { 8 | *Observable 9 | Guid string 10 | ClientID Number 11 | 12 | GC bool 13 | GCFilter func(item *Item) bool 14 | Share map[string]IAbstractType 15 | Store *StructStore 16 | Trans *Transaction 17 | TransCleanup []*Transaction 18 | SubDocs Set 19 | Item *Item // If this document is a subdocument - a document integrated into another document - then _item is defined. 20 | ShouldLoad bool 21 | AutoLoad bool 22 | Meta interface{} 23 | } 24 | 25 | // Notify the parent document that you request to load data into this subdocument (if it is a subdocument). 26 | // 27 | // `load()` might be used in the future to request any provider to load the most current data. 28 | // It is safe to call `load()` multiple times. 29 | func (doc *Doc) Load() { 30 | item := doc.Item 31 | if item != nil && !doc.ShouldLoad { 32 | parent := item.Parent.(IAbstractType) 33 | Transact(parent.GetDoc(), func(trans *Transaction) { 34 | trans.SubdocsLoaded.Add(doc) 35 | }, nil, true) 36 | } 37 | doc.ShouldLoad = true 38 | } 39 | 40 | func (doc *Doc) GetSubdocs() Set { 41 | return doc.SubDocs 42 | } 43 | 44 | func (doc *Doc) GetSubdocGuids() Set { 45 | s := NewSet() 46 | for k := range doc.SubDocs { 47 | guid := k.(string) 48 | s.Add(guid) 49 | } 50 | return s 51 | } 52 | 53 | // Changes that happen inside of a transaction are bundled. This means that 54 | // the observer fires _after_ the transaction is finished and that all changes 55 | // that happened inside of the transaction are sent as one message to the 56 | // other peers. 57 | func (doc *Doc) Transact(f func(trans *Transaction), origin interface{}) { 58 | Transact(doc, f, origin, true) 59 | } 60 | 61 | // Define a shared data type. 62 | // 63 | // Multiple calls of `y.get(name, TypeConstructor)` yield the same result 64 | // and do not overwrite each other. I.e. 65 | // `y.define(name, Y.Array) === y.define(name, Y.Array)` 66 | // 67 | // After this method is called, the type is also available on `y.share.get(name)`. 68 | // 69 | // Best Practices: 70 | // Define all types right after the Yjs instance is created and store them in a separate object. 71 | // Also use the typed methods `getText(name)`, `getArray(name)`, .. 72 | // 73 | // example 74 | // 75 | // const y = new Y(..) 76 | // const appState = { 77 | // document: y.getText('document') 78 | // comments: y.getArray('comments') 79 | // } 80 | func (doc *Doc) Get(name string, typeConstructor TypeConstructor) (IAbstractType, error) { 81 | _, exist := doc.Share[name] 82 | if !exist { 83 | t := typeConstructor() 84 | t.Integrate(doc, nil) 85 | doc.Share[name] = t 86 | } 87 | 88 | constr := doc.Share[name] 89 | if !IsSameType(typeConstructor(), NewAbstractType()) && !IsSameType(constr, typeConstructor()) { 90 | if IsSameType(constr, NewAbstractType()) { 91 | t := typeConstructor() 92 | t.SetMap(constr.GetMap()) 93 | for _, n := range t.GetMap() { 94 | for ; n != nil; n = n.Left { 95 | n.Parent = t 96 | } 97 | } 98 | 99 | t.SetStartItem(constr.StartItem()) 100 | for n := t.StartItem(); n != nil; n = n.Right { 101 | n.Parent = t 102 | } 103 | 104 | t.SetLength(constr.GetLength()) 105 | t.Integrate(doc, nil) 106 | doc.Share[name] = t 107 | return t, nil 108 | } else { 109 | return nil, fmt.Errorf("Type with the name %s has already been defined with a different constructor ", name) 110 | } 111 | } 112 | 113 | return constr, nil 114 | } 115 | 116 | func (doc *Doc) GetArray(name string) *YArray { 117 | arr, err := doc.Get(name, NewYArrayType) 118 | if err != nil { 119 | return nil 120 | } 121 | 122 | a, ok := arr.(*YArray) 123 | if ok { 124 | return a 125 | } 126 | 127 | return nil 128 | } 129 | 130 | func (doc *Doc) GetText(name string) *YText { 131 | text, err := doc.Get(name, NewYTextType) 132 | if err != nil { 133 | return nil 134 | } 135 | 136 | a, ok := text.(*YText) 137 | if ok { 138 | return a 139 | } 140 | 141 | return nil 142 | } 143 | 144 | func (doc *Doc) GetMap(name string) IAbstractType { 145 | m, err := doc.Get(name, NewYMapType) 146 | if err != nil { 147 | return nil 148 | } 149 | return m 150 | } 151 | 152 | func (doc *Doc) GetXmlFragment(name string) IAbstractType { 153 | xml, err := doc.Get(name, NewYXmlFragmentType) 154 | if err != nil { 155 | return nil 156 | } 157 | return xml 158 | } 159 | 160 | // Converts the entire document into a js object, recursively traversing each yjs type 161 | // Doesn't log types that have not been defined (using ydoc.getType(..)). 162 | // 163 | // Do not use this method and rather call toJSON directly on the shared types. 164 | func (doc *Doc) ToJson() Object { 165 | object := NewObject() 166 | for key, value := range doc.Share { 167 | object[key] = value.ToJson() 168 | } 169 | return object 170 | } 171 | 172 | // Emit `destroy` event and unregister all event handlers. 173 | func (doc *Doc) Destroy() { 174 | for k := range doc.SubDocs { 175 | subDoc := k.(*Doc) 176 | subDoc.Destroy() 177 | } 178 | 179 | item := doc.Item 180 | if item != nil { 181 | doc.Item = nil 182 | content := item.Content.(*ContentDoc) 183 | if item.Deleted() { 184 | content.Doc = nil 185 | } else { 186 | content.Doc = NewDoc(doc.Guid, content.Opts[OptKeyGC].(bool), DefaultGCFilter, content.Opts[OptKeyMeta], content.Opts[OptKeyAutoLoad].(bool)) 187 | content.Doc.Item = item 188 | } 189 | 190 | Transact(item.Parent.(IAbstractType).GetDoc(), func(trans *Transaction) { 191 | if !item.Deleted() { 192 | trans.SubdocsAdded.Add(content.Doc) 193 | } 194 | }, nil, true) 195 | } 196 | 197 | doc.Emit("destroyed", true) 198 | doc.Emit("destroy", doc) 199 | doc.Observable.Destroy() 200 | } 201 | 202 | func (doc *Doc) On(eventName string, handler *ObserverHandler) { 203 | doc.Observable.On(eventName, handler) 204 | } 205 | 206 | func (doc *Doc) Off(eventName string, handler *ObserverHandler) { 207 | doc.Observable.Off(eventName, handler) 208 | } 209 | 210 | func NewDoc(guid string, gc bool, gcFilter func(item *Item) bool, meta interface{}, autoLoad bool) *Doc { 211 | doc := &Doc{ 212 | Observable: NewObservable(), 213 | ClientID: GenerateNewClientID(), 214 | Guid: guid, 215 | GC: gc, 216 | GCFilter: gcFilter, 217 | Meta: meta, 218 | AutoLoad: autoLoad, 219 | Store: NewStructStore(), 220 | Share: make(map[string]IAbstractType), 221 | } 222 | 223 | return doc 224 | } 225 | -------------------------------------------------------------------------------- /y_xml_fragment.go: -------------------------------------------------------------------------------- 1 | package y_crdt 2 | 3 | import ( 4 | "strings" 5 | ) 6 | 7 | /** 8 | * Define the elements to which a set of CSS queries apply. 9 | * {@link https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors|CSS_Selectors} 10 | * 11 | * @example 12 | * query = '.classSelector' 13 | * query = 'nodeSelector' 14 | * query = '#idSelector' 15 | * 16 | * @typedef {string} CSS_Selector 17 | */ 18 | 19 | /** 20 | * Dom filter function. 21 | * 22 | * @callback domFilter 23 | * @param {string} nodeName The nodeName of the element 24 | * @param {Map} attributes The map of attributes. 25 | * @return {boolean} Whether to include the Dom node in the YXmlElement. 26 | */ 27 | 28 | /** 29 | * Represents a subset of the nodes of a YXmlElement / YXmlFragment and a 30 | * position within them. 31 | * 32 | * Can be created with {@link YXmlFragment#createTreeWalker} 33 | * 34 | * @public 35 | * @implements {Iterable} 36 | */ 37 | 38 | type YXmlTreeWalker struct { 39 | Filter func() bool 40 | Root interface{} 41 | CurrentNode *Item 42 | FirstCall bool 43 | } 44 | 45 | type IXmlType interface { 46 | ToString() string 47 | } 48 | 49 | type YXmlFragment struct { 50 | AbstractType 51 | PrelimContent ArrayAny 52 | } 53 | 54 | func (y *YXmlFragment) GetFirstChild() interface{} { 55 | first := y.First() 56 | if first != nil && len(first.Content.GetContent()) > 0 { 57 | return first.Content.GetContent()[0] 58 | } 59 | 60 | return nil 61 | } 62 | 63 | // Integrate this type into the Yjs instance. 64 | // 65 | // Save this struct in the os 66 | // This type is sent to other client 67 | // Observer functions are fired 68 | func (y *YXmlFragment) Integrate(doc *Doc, item *Item) { 69 | y.AbstractType.Integrate(doc, item) 70 | y.Insert(0, y.PrelimContent) 71 | y.PrelimContent = nil 72 | } 73 | 74 | func (y *YXmlFragment) Copy() IAbstractType { 75 | return NewYXmlFragment() 76 | } 77 | 78 | func (y *YXmlFragment) Clone() IAbstractType { 79 | el := NewYXmlFragment() 80 | 81 | var data []interface{} 82 | for _, element := range y.ToArray() { 83 | item, ok := element.(IAbstractType) 84 | if ok { 85 | data = append(data, item.Clone()) 86 | } else { 87 | data = append(data, element) 88 | } 89 | } 90 | 91 | el.Insert(0, data) 92 | return el 93 | } 94 | 95 | func (y *YXmlFragment) GetLength() Number { 96 | if y.PrelimContent == nil { 97 | return y.Length 98 | } 99 | 100 | return len(y.PrelimContent) 101 | } 102 | 103 | func (y *YXmlFragment) CreateTreeWalker(filter func(abstractType IAbstractType) bool) *YXmlTreeWalker { 104 | return NewYXmlTreeWalker(y, filter) 105 | } 106 | 107 | // not supported yet. 108 | func (y *YXmlFragment) QuerySelector(query interface{}) { 109 | 110 | } 111 | 112 | // not supported yet. 113 | func (y *YXmlFragment) QuerySelectorAll(query interface{}) { 114 | 115 | } 116 | 117 | // Creates YXmlEvent and calls observers. 118 | func (y *YXmlFragment) CallObserver(trans *Transaction, parentSubs Set) { 119 | CallTypeObservers(y, trans, NewYXmlEvent(y, parentSubs, trans)) 120 | } 121 | 122 | // Get the string representation of all the children of this YXmlFragment. 123 | func (y *YXmlFragment) ToString() string { 124 | elements := TypeListMap(y, func(c interface{}, i Number, _ IAbstractType) interface{} { 125 | xml, ok := c.(IXmlType) 126 | if ok { 127 | return xml.ToString() 128 | } 129 | 130 | return "" 131 | }) 132 | 133 | var data []string 134 | for _, element := range elements { 135 | str, ok := element.(string) 136 | if ok && str != "" { 137 | data = append(data, str) 138 | } 139 | } 140 | 141 | return strings.Join(data, "") 142 | } 143 | 144 | func (y *YXmlFragment) ToJson() interface{} { 145 | return y.ToString() 146 | } 147 | 148 | // not supported yet. 149 | func (y *YXmlFragment) ToDOM() { 150 | 151 | } 152 | 153 | // Insert Inserts new content at an index. 154 | // 155 | // @example 156 | // 157 | // // Insert character 'a' at position 0 158 | // 159 | // xml.insert(0, [new Y.XmlText('text')]) 160 | func (y *YXmlFragment) Insert(index Number, content ArrayAny) { 161 | if y.Doc != nil { 162 | Transact(y.Doc, func(trans *Transaction) { 163 | TypeListInsertGenerics(trans, y, index, content) 164 | }, nil, true) 165 | } else { 166 | SpliceArray(&y.PrelimContent, index, 0, content) 167 | } 168 | } 169 | 170 | // Inserts new content at an index. 171 | // 172 | // @example 173 | // 174 | // // Insert character 'a' at position 0 175 | // xml.insert(0, [new Y.XmlText('text')]) 176 | func (y *YXmlFragment) InsertAfter(ref interface{}, content ArrayAny) { 177 | if y.Doc != nil { 178 | Transact(y.Doc, func(trans *Transaction) { 179 | var refItem *Item 180 | 181 | a, ok := ref.(IAbstractType) 182 | if ok { 183 | refItem = a.GetItem() 184 | } else { 185 | refItem, _ = ref.(*Item) 186 | } 187 | 188 | TypeListInsertGenericsAfter(trans, y, refItem, content) 189 | }, nil, true) 190 | } else { 191 | pc := y.PrelimContent 192 | index := 0 193 | 194 | if ref != nil { 195 | index = FindIndex(pc, func(e interface{}) bool { 196 | return e == ref 197 | }) + 1 198 | } 199 | 200 | if index == 0 && ref != nil { 201 | Logf("reference item not found") 202 | return 203 | } 204 | 205 | SpliceArray(&pc, index, 0, content) 206 | } 207 | } 208 | 209 | // Deletes elements starting from an index. 210 | // Default: length = 1 211 | func (y *YXmlFragment) Delete(index, length Number) { 212 | if y.Doc != nil { 213 | Transact(y.Doc, func(trans *Transaction) { 214 | TypeListDelete(trans, y, index, length) 215 | }, nil, true) 216 | } else { 217 | // @ts-ignore _prelimContent is defined because this is not yet integrated 218 | SpliceArray(&y.PrelimContent, index, length, nil) 219 | } 220 | } 221 | 222 | // Transforms this YArray to a JavaScript Array. 223 | func (y *YXmlFragment) ToArray() ArrayAny { 224 | return TypeListToArray(y) 225 | } 226 | 227 | // Appends content to this YArray. 228 | func (y *YXmlFragment) Push(content ArrayAny) { 229 | y.Insert(y.Length, content) 230 | } 231 | 232 | // Preppends content to this YArray. 233 | func (y *YXmlFragment) Unshift(content ArrayAny) { 234 | y.Insert(0, content) 235 | } 236 | 237 | // Returns the i-th element from a YArray. 238 | func (y *YXmlFragment) Get(index Number) interface{} { 239 | return TypeListGet(y, index) 240 | } 241 | 242 | // Transforms this YArray to a JavaScript Array. 243 | // Default: start = 0 244 | func (y *YXmlFragment) Slice(start, end Number) ArrayAny { 245 | return TypeListSlice(y, start, end) 246 | } 247 | 248 | // Transform the properties of this type to binary and write it to an 249 | // BinaryEncoder. 250 | // 251 | // This is called when this Item is sent to a remote peer. 252 | // 253 | // @param {UpdateEncoderV1 | UpdateEncoderV2} encoder The encoder to write data to. 254 | func (y *YXmlFragment) Write(encoder *UpdateEncoderV1) { 255 | encoder.WriteTypeRef(YXmlFragmentRefID) 256 | } 257 | 258 | func NewYXmlFragment() *YXmlFragment { 259 | return &YXmlFragment{ 260 | AbstractType: AbstractType{ 261 | Map: make(map[string]*Item), 262 | EH: NewEventHandler(), 263 | DEH: NewEventHandler(), 264 | }, 265 | } 266 | } 267 | 268 | func NewYXmlFragmentType() IAbstractType { 269 | return NewYXmlFragment() 270 | } 271 | 272 | // not supported yet. 273 | func NewYXmlTreeWalker(root interface{}, f func(abstractType IAbstractType) bool) *YXmlTreeWalker { 274 | 275 | return nil 276 | } 277 | -------------------------------------------------------------------------------- /y_event.go: -------------------------------------------------------------------------------- 1 | package y_crdt 2 | 3 | const ( 4 | ActionAdd = "add" 5 | ActionDelete = "delete" 6 | ActionUpdate = "update" 7 | ) 8 | 9 | type EventAction struct { 10 | Action string 11 | OldValue interface{} 12 | NewValue interface{} 13 | } 14 | 15 | type EventOperator struct { 16 | Insert interface{} // string | Array 17 | Retain Number 18 | Delete Number 19 | Attributes Object 20 | 21 | IsInsertDefined bool 22 | IsRetainDefined bool 23 | IsDeleteDefined bool 24 | IsAttributesDefined bool 25 | } 26 | 27 | type IEventType interface { 28 | GetTarget() IAbstractType 29 | GetCurrentTarget() IAbstractType 30 | SetCurrentTarget(t IAbstractType) 31 | Path() []interface{} 32 | } 33 | 34 | // YEvent describes the changes on a YType. 35 | type YEvent struct { 36 | Target IAbstractType // The type on which this event was created on. 37 | CurrentTarget IAbstractType // The current target on which the observe callback is called. 38 | Trans *Transaction // The transaction that triggered this event. 39 | Changes Object 40 | Keys map[string]EventAction // Map} 41 | delta []EventOperator 42 | } 43 | 44 | func (y *YEvent) GetTarget() IAbstractType { 45 | return y.Target 46 | } 47 | 48 | func (y *YEvent) GetCurrentTarget() IAbstractType { 49 | return y.CurrentTarget 50 | } 51 | 52 | func (y *YEvent) SetCurrentTarget(t IAbstractType) { 53 | y.CurrentTarget = t 54 | } 55 | 56 | // Computes the path from `y` to the changed type. 57 | // 58 | // @todo v14 should standardize on path: Array<{parent, index}> because that is easier to work with. 59 | // 60 | // The following property holds: 61 | // @example 62 | // ---------------------------------------------------------------------------- 63 | // 64 | // let type = y 65 | // event.path.forEach(dir => { 66 | // type = type.get(dir) 67 | // }) 68 | // type === event.target // => true 69 | // 70 | // ---------------------------------------------------------------------------- 71 | func (y *YEvent) Path() []interface{} { 72 | return GetPathTo(y.CurrentTarget, y.Target) 73 | } 74 | 75 | // Check if a struct is deleted by this event. 76 | // In contrast to change.deleted, this method also returns true if the struct was added and then deleted. 77 | func (y *YEvent) Deletes(s IAbstractStruct) bool { 78 | return IsDeleted(y.Trans.DeleteSet, s.GetID()) 79 | } 80 | 81 | func (y *YEvent) GetKeys() map[string]EventAction { 82 | if y.Keys == nil { 83 | keys := make(map[string]EventAction) 84 | target := y.Target 85 | changed := y.Trans.Changed[target] 86 | for key := range changed { 87 | if key != nil { 88 | strKey, ok := key.(string) 89 | if !ok { 90 | Log("[crdt] key is not string.") 91 | continue 92 | } 93 | 94 | item := target.GetMap()[strKey] 95 | 96 | var action string 97 | var oldValue interface{} 98 | var err error 99 | 100 | if y.Adds(item) { 101 | prev := item.Left 102 | for prev != nil && y.Adds(prev) { 103 | prev = prev.Left 104 | } 105 | 106 | if y.Deletes(item) { 107 | if prev != nil && y.Deletes(prev) { 108 | action = ActionDelete 109 | oldValue, err = ArrayLast(prev.Content.GetContent()) 110 | if err != nil { 111 | Log("[crdt] %s.", err.Error()) 112 | return nil 113 | } 114 | } else { 115 | return nil 116 | } 117 | } else { 118 | if prev != nil && y.Deletes(prev) { 119 | action = ActionUpdate 120 | oldValue, err = ArrayLast(prev.Content.GetContent()) 121 | if err != nil { 122 | Log("[crdt] %s.", err.Error()) 123 | return nil 124 | } 125 | } else { 126 | action = ActionAdd 127 | oldValue = nil 128 | } 129 | } 130 | } else { 131 | if y.Deletes(item) { 132 | action = ActionDelete 133 | oldValue, err = ArrayLast(item.Content.GetContent()) 134 | if err != nil { 135 | Log("[crdt] %s.", err.Error()) 136 | return nil 137 | } 138 | } else { 139 | return nil 140 | } 141 | } 142 | 143 | keys[strKey] = EventAction{ 144 | Action: action, 145 | OldValue: oldValue, 146 | } 147 | } 148 | } 149 | 150 | y.Keys = keys 151 | } 152 | 153 | return y.Keys 154 | } 155 | 156 | func (y *YEvent) GetDelta() []EventOperator { 157 | return y.GetChanges()["delta"].([]EventOperator) 158 | } 159 | 160 | // Check if a struct is added by this event. 161 | // In contrast to change.deleted, this method also returns true if the struct was added and then deleted. 162 | func (y *YEvent) Adds(s IAbstractStruct) bool { 163 | return s.GetID().Clock >= y.Trans.BeforeState[s.GetID().Client] 164 | } 165 | 166 | func (y *YEvent) GetChanges() Object { 167 | changes := y.Changes 168 | if changes == nil || len(changes) == 0 { 169 | target := y.Target 170 | added := NewSet() 171 | deleted := NewSet() 172 | var delta []EventOperator 173 | 174 | changes = NewObject() 175 | changes["added"] = added 176 | changes["deleted"] = deleted 177 | changes["keys"] = y.Keys 178 | 179 | changed := y.Trans.Changed[target] 180 | 181 | _, existNil := changed[nil] 182 | _, existEmpty := changed[""] 183 | if existNil || existEmpty { 184 | var lastOp *EventOperator 185 | packOp := func() { 186 | if lastOp != nil { 187 | delta = append(delta, *lastOp) 188 | } 189 | } 190 | 191 | for item := target.StartItem(); item != nil; item = item.Right { 192 | if item.Deleted() { 193 | if y.Deletes(item) && !y.Adds(item) { 194 | if lastOp == nil || !lastOp.IsDeleteDefined { 195 | packOp() 196 | lastOp = &EventOperator{} 197 | } 198 | lastOp.Delete += item.Length 199 | lastOp.IsDeleteDefined = true 200 | deleted.Add(item) 201 | } // else nop 202 | } else { 203 | if y.Adds(item) { 204 | if lastOp == nil || !lastOp.IsInsertDefined { 205 | packOp() 206 | 207 | lastOp = &EventOperator{ 208 | Insert: ArrayAny{}, 209 | IsInsertDefined: true, 210 | } 211 | } 212 | 213 | lastOp.Insert = append(lastOp.Insert.(ArrayAny), item.Content.GetContent()) 214 | lastOp.IsInsertDefined = true 215 | added.Add(item) 216 | } else { 217 | if lastOp == nil || !lastOp.IsRetainDefined { 218 | packOp() 219 | 220 | lastOp = &EventOperator{} 221 | } 222 | lastOp.Retain += item.Length 223 | lastOp.IsRetainDefined = true 224 | } 225 | } 226 | } 227 | 228 | if lastOp != nil && !lastOp.IsRetainDefined { 229 | packOp() 230 | } 231 | } 232 | 233 | changes["delta"] = delta 234 | y.Changes = changes 235 | } 236 | 237 | return changes 238 | } 239 | 240 | func NewYEvent(target IAbstractType, trans *Transaction) *YEvent { 241 | return &YEvent{ 242 | Target: target, 243 | CurrentTarget: target, 244 | Trans: trans, 245 | Changes: NewObject(), 246 | Keys: make(map[string]EventAction), 247 | } 248 | } 249 | 250 | func NewDefaultYEvent() *YEvent { 251 | return &YEvent{} 252 | } 253 | 254 | // Compute the path from this type to the specified target. 255 | // 256 | // @example 257 | // ---------------------------------------------------------------------------- 258 | // 259 | // // `child` should be accessible via `type.get(path[0]).get(path[1])..` 260 | // const path = type.getPathTo(child) 261 | // // assuming `type instanceof YArray` 262 | // console.Log(path) // might look like => [2, 'key1'] 263 | // child === type.get(path[0]).get(path[1]) 264 | // 265 | // ---------------------------------------------------------------------------- 266 | func GetPathTo(parent IAbstractType, child IAbstractType) []interface{} { 267 | var path []interface{} 268 | 269 | for child.GetItem() != nil && child != parent { 270 | if child.GetItem().ParentSub != "" { 271 | // parent is map-ish 272 | path = Unshift(path, child.GetItem().ParentSub) 273 | } else { 274 | // parent is array-ish 275 | i := 0 276 | c := child.GetItem().Parent.(IAbstractType).StartItem() 277 | for c != child.GetItem() && c != nil { 278 | if !c.Deleted() { 279 | i++ 280 | } 281 | c = c.Right 282 | } 283 | path = Unshift(path, i) 284 | } 285 | 286 | child = child.GetItem().Parent.(IAbstractType) 287 | } 288 | 289 | return path 290 | } 291 | -------------------------------------------------------------------------------- /relative_position.go: -------------------------------------------------------------------------------- 1 | package y_crdt 2 | 3 | import ( 4 | "errors" 5 | ) 6 | 7 | // A relative position is based on the Yjs model and is not affected by document changes. 8 | // E.g. If you place a relative position before a certain character, it will always point to this character. 9 | // If you place a relative position at the end of a type, it will always point to the end of the type. 10 | // 11 | // A numeric position is often unsuited for user selections, because it does not change when content is inserted 12 | // before or after. 13 | // 14 | // ```Insert(0, 'x')('a|bc') = 'xa|bc'``` Where | is the relative position. 15 | // 16 | // One of the properties must be defined. 17 | // 18 | // @example 19 | // // Current cursor position is at position 10 20 | // const relativePosition = createRelativePositionFromIndex(yText, 10) 21 | // // modify yText 22 | // yText.insert(0, 'abc') 23 | // yText.delete(3, 10) 24 | // // Compute the cursor position 25 | // const absolutePosition = createAbsolutePositionFromRelativePosition(y, relativePosition) 26 | // absolutePosition.type === yText // => true 27 | // console.log('cursor location is ' + absolutePosition.index) // => cursor location is 3 28 | 29 | type RelativePosition struct { 30 | Type *ID 31 | Tname string 32 | Item *ID 33 | 34 | // A relative position is associated to a specific character. By default 35 | // assoc >= 0, the relative position is associated to the character 36 | // after the meant position. 37 | // I.e. position 1 in 'ab' is associated to character 'b'. 38 | // 39 | // If assoc < 0, then the relative position is associated to the caharacter 40 | // before the meant position. 41 | Assoc Number 42 | } 43 | 44 | func RelativePositionToJSON(rpos *RelativePosition) Object { 45 | json := NewObject() 46 | if rpos.Type != nil { 47 | json["type"] = rpos.Type 48 | } 49 | 50 | if rpos.Tname != "" { 51 | json["tname"] = rpos.Tname 52 | } 53 | 54 | if rpos.Item != nil { 55 | json["item"] = rpos.Item 56 | } 57 | 58 | json["assoc"] = rpos.Assoc 59 | return json 60 | } 61 | 62 | func CreateRelativePositionFromJSON(json Object) *RelativePosition { 63 | r := &RelativePosition{} 64 | if v, exist := json["type"]; exist { 65 | id := v.(ID) 66 | // r.Type = GenID(id.Client, id.Clock) 67 | r.Type = &id 68 | } 69 | 70 | if v, exist := json["tname"]; exist { 71 | r.Tname = v.(string) 72 | } 73 | 74 | if v, exist := json["item"]; exist { 75 | id := v.(ID) 76 | // r.Item = GenID(id.Client, id.Clock) 77 | r.Item = &id 78 | } 79 | 80 | if v, exist := json["assoc"]; exist { 81 | assoc := v.(Number) 82 | r.Assoc = assoc 83 | } 84 | return r 85 | } 86 | 87 | type AbsolutePosition struct { 88 | Type IAbstractType 89 | Index Number 90 | Assoc Number 91 | } 92 | 93 | func NewAbsolutePosition(t IAbstractType, index, assoc Number) *AbsolutePosition { 94 | return &AbsolutePosition{ 95 | Type: t, 96 | Index: index, 97 | Assoc: assoc, 98 | } 99 | } 100 | 101 | func NewRelativePosition(t IAbstractType, item *ID, assoc Number) *RelativePosition { 102 | var typeid ID 103 | var tname string 104 | 105 | if t.GetItem() == nil { 106 | tname = FindRootTypeKey(t) 107 | } else { 108 | typeid = GenID(t.GetItem().ID.Client, t.GetItem().ID.Clock) 109 | } 110 | 111 | return &RelativePosition{ 112 | Type: &typeid, 113 | Tname: tname, 114 | Item: item, 115 | Assoc: assoc, 116 | } 117 | } 118 | 119 | // Create a relativePosition based on a absolute position. 120 | func NewRelativePositionFromTypeIndex(tp IAbstractType, index, assoc Number) *RelativePosition { 121 | t := tp.StartItem() 122 | if assoc < 0 { 123 | // associated to the left character or the beginning of a type, increment index if possible. 124 | if index == 0 { 125 | return NewRelativePosition(tp, nil, assoc) 126 | } 127 | 128 | index-- 129 | } 130 | 131 | for t != nil { 132 | if !t.Deleted() && t.Countable() { 133 | if t.Length > index { 134 | // case 1: found position somewhere in the linked list 135 | item := GenID(t.ID.Client, t.ID.Clock+index) 136 | return NewRelativePosition(tp, &item, assoc) 137 | } 138 | index -= t.Length 139 | } 140 | 141 | if t.Right == nil && assoc < 0 { 142 | // left-associated position, return last available id 143 | return NewRelativePosition(tp, t.LastID(), assoc) 144 | } 145 | 146 | t = t.Right 147 | } 148 | 149 | return NewRelativePosition(tp, nil, assoc) 150 | } 151 | 152 | func WriteRelativePosition(encoder *UpdateEncoderV1, rpos *RelativePosition) error { 153 | t, tname, item, assoc := rpos.Type, rpos.Tname, rpos.Item, rpos.Assoc 154 | if item != nil { 155 | WriteVarUint(encoder.RestEncoder, 0) 156 | encoder.WriteID(item) 157 | } else if tname != "" { 158 | // case 2: found position at the end of the list and type is stored in y.share 159 | WriteByte(encoder.RestEncoder, 1) 160 | encoder.WriteString(tname) 161 | } else if t != nil { 162 | // case 3: found position at the end of the list and type is attached to an item 163 | WriteByte(encoder.RestEncoder, 2) 164 | encoder.WriteID(t) 165 | } else { 166 | return errors.New("unexpected case") 167 | } 168 | 169 | WriteVarInt(encoder.RestEncoder, assoc) 170 | return nil 171 | } 172 | 173 | func EncodeRelativePosition(rpos *RelativePosition) []uint8 { 174 | encoder := NewUpdateEncoderV1() 175 | WriteRelativePosition(encoder, rpos) 176 | return encoder.ToUint8Array() 177 | } 178 | 179 | func ReadRelativePosition(decoder *UpdateDecoderV1) *RelativePosition { 180 | var t *ID 181 | var tname string 182 | var itemID *ID 183 | var assoc Number 184 | 185 | n, _ := readVarUint(decoder.RestDecoder) 186 | switch n { 187 | case 0: 188 | // case 1: found position somewhere in the linked list 189 | itemID, _ = decoder.ReadID() 190 | 191 | case 1: 192 | // case 2: found position at the end of the list and type is stored in y.share 193 | tname, _ = decoder.ReadString() 194 | 195 | case 2: 196 | // case 3: found position at the end of the list and type is attached to an item 197 | t, _ = decoder.ReadID() 198 | } 199 | 200 | if hasContent(decoder.RestDecoder) { 201 | v, _ := ReadVarInt(decoder.RestDecoder) 202 | assoc = v.(Number) 203 | } 204 | 205 | return &RelativePosition{ 206 | Type: t, 207 | Tname: tname, 208 | Item: itemID, 209 | Assoc: assoc, 210 | } 211 | } 212 | 213 | func DecodeRelativePosition(uint8Array []uint8) *RelativePosition { 214 | return ReadRelativePosition(NewUpdateDecoderV1(uint8Array)) 215 | } 216 | 217 | func CreateAbsolutePositionFromRelativePosition(rpos *RelativePosition, doc *Doc) *AbsolutePosition { 218 | store := doc.Store 219 | rightID := rpos.Item 220 | typeID := rpos.Type 221 | tname := rpos.Tname 222 | assoc := rpos.Assoc 223 | 224 | var t IAbstractType 225 | var index Number 226 | 227 | if rightID != nil { 228 | if GetState(store, rightID.Client) <= rightID.Clock { 229 | return nil 230 | } 231 | 232 | item, diff := FollowRedone(store, *rightID) 233 | right := item 234 | if right == nil { 235 | return nil 236 | } 237 | 238 | t = right.Parent.(IAbstractType) 239 | if t.GetItem() == nil || !t.GetItem().Deleted() { 240 | // adjust position based on left association if necessary 241 | if right.Deleted() || !right.Countable() { 242 | index = 0 243 | } else { 244 | if assoc >= 0 { 245 | index = diff 246 | } else { 247 | index = diff + 1 248 | } 249 | } 250 | 251 | n := right.Left 252 | for n != nil { 253 | if !n.Deleted() && n.Countable() { 254 | index += n.Length 255 | } 256 | n = n.Left 257 | } 258 | } 259 | } else { 260 | if tname != "" { 261 | t = doc.GetMap(tname) 262 | } else if typeID != nil { 263 | if GetState(store, typeID.Client) <= typeID.Clock { 264 | // type does not exist yet 265 | return nil 266 | } 267 | 268 | item, _ := FollowRedone(store, *typeID) 269 | if item != nil && IsSameType(item.Content, &ContentType{}) { 270 | t = item.Content.(*ContentType).Type 271 | } else { 272 | // struct is garbage collected 273 | return nil 274 | } 275 | } else { 276 | Logf("[crdt] unexpected case.") 277 | return nil 278 | } 279 | 280 | if assoc >= 0 { 281 | index = t.GetLength() 282 | } else { 283 | index = 0 284 | } 285 | } 286 | 287 | return NewAbsolutePosition(t, index, rpos.Assoc) 288 | } 289 | 290 | func CompareRelativePositions(a, b *RelativePosition) bool { 291 | return a == b || a != nil && b != nil && a.Tname == b.Tname && CompareIDs(a.Item, b.Item) && CompareIDs(a.Type, b.Type) && a.Assoc == b.Assoc 292 | } 293 | -------------------------------------------------------------------------------- /update_decoder_test.go: -------------------------------------------------------------------------------- 1 | package y_crdt 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "fmt" 7 | "testing" 8 | ) 9 | 10 | // TestWriteReadDsClock verifies encoding/decoding of DeleteSet clock values 11 | func TestWriteReadDsClock(t *testing.T) { 12 | encoder := NewUpdateEncoderV1() 13 | originalClock := Number(12345) 14 | encoder.WriteDsClock(originalClock) 15 | data := encoder.ToUint8Array() 16 | 17 | decoder := NewUpdateDecoderV1(data) 18 | decodedClock, err := decoder.ReadDsClock() 19 | if err != nil { 20 | t.Fatalf("ReadDsClock failed: %v", err) 21 | } 22 | 23 | if decodedClock != originalClock { 24 | t.Errorf("DsClock mismatch: got %d, want %d", decodedClock, originalClock) 25 | } 26 | } 27 | 28 | // TestWriteReadDsLen verifies encoding/decoding of DeleteSet length values 29 | func TestWriteReadDsLen(t *testing.T) { 30 | encoder := NewUpdateEncoderV1() 31 | originalLen := Number(67890) 32 | encoder.WriteDsLen(originalLen) 33 | data := encoder.ToUint8Array() 34 | 35 | decoder := NewUpdateDecoderV1(data) 36 | decodedLen, err := decoder.ReadDsLen() 37 | if err != nil { 38 | t.Fatalf("ReadDsLen failed: %v", err) 39 | } 40 | 41 | if decodedLen != originalLen { 42 | t.Errorf("DsLen mismatch: got %d, want %d", decodedLen, originalLen) 43 | } 44 | } 45 | 46 | // TestWriteReadID verifies encoding/decoding of ID structs 47 | func TestWriteReadID(t *testing.T) { 48 | encoder := NewUpdateEncoderV1() 49 | originalID := &ID{Client: 42, Clock: 100} 50 | encoder.WriteID(originalID) 51 | data := encoder.ToUint8Array() 52 | 53 | decoder := NewUpdateDecoderV1(data) 54 | decodedID, err := decoder.ReadID() 55 | if err != nil { 56 | t.Fatalf("ReadID failed: %v", err) 57 | } 58 | 59 | if decodedID.Client != originalID.Client || decodedID.Clock != originalID.Clock { 60 | t.Errorf("ID mismatch: got %+v, want %+v", decodedID, originalID) 61 | } 62 | } 63 | 64 | // TestWriteReadClient verifies encoding/decoding of client numbers 65 | func TestWriteReadClient(t *testing.T) { 66 | encoder := NewUpdateEncoderV1() 67 | originalClient := Number(99) 68 | encoder.WriteClient(originalClient) 69 | data := encoder.ToUint8Array() 70 | 71 | decoder := NewUpdateDecoderV1(data) 72 | decodedClient, err := decoder.ReadClient() 73 | if err != nil { 74 | t.Fatalf("ReadClient failed: %v", err) 75 | } 76 | 77 | if decodedClient != originalClient { 78 | t.Errorf("Client mismatch: got %d, want %d", decodedClient, originalClient) 79 | } 80 | } 81 | 82 | // TestWriteReadInfo verifies encoding/decoding of info bytes 83 | func TestWriteReadInfo(t *testing.T) { 84 | encoder := NewUpdateEncoderV1() 85 | originalInfo := uint8(0xAB) 86 | encoder.WriteInfo(originalInfo) 87 | data := encoder.ToUint8Array() 88 | 89 | decoder := NewUpdateDecoderV1(data) 90 | decodedInfo, err := decoder.ReadInfo() 91 | if err != nil { 92 | t.Fatalf("ReadInfo failed: %v", err) 93 | } 94 | 95 | if decodedInfo != originalInfo { 96 | t.Errorf("Info mismatch: got 0x%X, want 0x%X", decodedInfo, originalInfo) 97 | } 98 | } 99 | 100 | // TestWriteReadString verifies encoding/decoding of strings 101 | func TestWriteReadString(t *testing.T) { 102 | encoder := NewUpdateEncoderV1() 103 | originalStr := "test string content" 104 | if err := encoder.WriteString(originalStr); err != nil { 105 | t.Fatalf("WriteString failed: %v", err) 106 | } 107 | data := encoder.ToUint8Array() 108 | 109 | decoder := NewUpdateDecoderV1(data) 110 | decodedStr, err := decoder.ReadString() 111 | if err != nil { 112 | t.Fatalf("ReadString failed: %v", err) 113 | } 114 | 115 | if decodedStr != originalStr { 116 | t.Errorf("String mismatch: got '%s', want '%s'", decodedStr, originalStr) 117 | } 118 | } 119 | 120 | // TestWriteReadParentInfo verifies encoding/decoding of parent info flags 121 | func TestWriteReadParentInfo(t *testing.T) { 122 | tests := []struct { 123 | name string 124 | input bool 125 | expected bool 126 | }{ 127 | {"true value", true, true}, 128 | {"false value", false, false}, 129 | } 130 | 131 | for _, tt := range tests { 132 | t.Run(tt.name, func(t *testing.T) { 133 | encoder := NewUpdateEncoderV1() 134 | encoder.WriteParentInfo(tt.input) 135 | data := encoder.ToUint8Array() 136 | 137 | decoder := NewUpdateDecoderV1(data) 138 | decoded, err := decoder.ReadParentInfo() 139 | if err != nil { 140 | t.Fatalf("ReadParentInfo failed: %v", err) 141 | } 142 | 143 | if decoded != tt.expected { 144 | t.Errorf("ParentInfo mismatch: got %v, want %v", decoded, tt.expected) 145 | } 146 | }) 147 | } 148 | } 149 | 150 | // TestWriteReadTypeRef verifies encoding/decoding of type references 151 | func TestWriteReadTypeRef(t *testing.T) { 152 | encoder := NewUpdateEncoderV1() 153 | originalRef := uint8(0x12) 154 | encoder.WriteTypeRef(originalRef) 155 | data := encoder.ToUint8Array() 156 | 157 | decoder := NewUpdateDecoderV1(data) 158 | decodedRef, err := decoder.ReadTypeRef() 159 | if err != nil { 160 | t.Fatalf("ReadTypeRef failed: %v", err) 161 | } 162 | 163 | if decodedRef != originalRef { 164 | t.Errorf("TypeRef mismatch: got 0x%X, want 0x%X", decodedRef, originalRef) 165 | } 166 | } 167 | 168 | // TestWriteReadLen verifies encoding/decoding of length values 169 | func TestWriteReadLen(t *testing.T) { 170 | encoder := NewUpdateEncoderV1() 171 | originalLen := Number(1024) 172 | encoder.WriteLen(originalLen) 173 | data := encoder.ToUint8Array() 174 | 175 | decoder := NewUpdateDecoderV1(data) 176 | decodedLen, err := decoder.ReadLen() 177 | if err != nil { 178 | t.Fatalf("ReadLen failed: %v", err) 179 | } 180 | 181 | if decodedLen != originalLen { 182 | t.Errorf("Len mismatch: got %d, want %d", decodedLen, originalLen) 183 | } 184 | } 185 | 186 | // TestWriteReadAny verifies encoding/decoding of arbitrary data types 187 | func TestWriteReadAny(t *testing.T) { 188 | tests := []struct { 189 | name string 190 | input any 191 | expected any 192 | }{ 193 | {"string type", "test any string", "test any string"}, 194 | {"integer type", Number(42), Number(42)}, 195 | {"boolean true", true, true}, 196 | {"boolean false", false, false}, 197 | {"byte array", []uint8{0x01, 0x02, 0x03}, []uint8{0x01, 0x02, 0x03}}, 198 | } 199 | 200 | for _, tt := range tests { 201 | t.Run(tt.name, func(t *testing.T) { 202 | encoder := NewUpdateEncoderV1() 203 | encoder.WriteAny(tt.input) 204 | data := encoder.ToUint8Array() 205 | 206 | decoder := NewUpdateDecoderV1(data) 207 | decoded, err := decoder.ReadAny() 208 | if err != nil { 209 | t.Fatalf("ReadAny failed: %v", err) 210 | } 211 | 212 | if fmt.Sprintf("%v", decoded) != fmt.Sprintf("%v", tt.expected) { 213 | t.Errorf("Any mismatch: got %v, want %v", decoded, tt.expected) 214 | } 215 | }) 216 | } 217 | } 218 | 219 | // TestWriteReadBuf verifies encoding/decoding of byte buffers 220 | func TestWriteReadBuf(t *testing.T) { 221 | encoder := NewUpdateEncoderV1() 222 | originalBuf := []uint8{0x01, 0x02, 0x03, 0x04} 223 | encoder.WriteBuf(originalBuf) 224 | data := encoder.ToUint8Array() 225 | 226 | decoder := NewUpdateDecoderV1(data) 227 | decodedBuf, err := decoder.ReadBuf() 228 | if err != nil { 229 | t.Fatalf("ReadBuf failed: %v", err) 230 | } 231 | 232 | if !bytes.Equal(decodedBuf, originalBuf) { 233 | t.Errorf("Buf mismatch: got %v, want %v", decodedBuf, originalBuf) 234 | } 235 | } 236 | 237 | // TestWriteReadJson verifies encoding/decoding of JSON objects 238 | func TestWriteReadJson(t *testing.T) { 239 | type TestStruct struct { 240 | Key string 241 | Value int 242 | } 243 | originalObj := TestStruct{Key: "test", Value: 123} 244 | 245 | encoder := NewUpdateEncoderV1() 246 | if err := encoder.WriteJson(originalObj); err != nil { 247 | t.Fatalf("WriteJson failed: %v", err) 248 | } 249 | data := encoder.ToUint8Array() 250 | 251 | decoder := NewUpdateDecoderV1(data) 252 | decodedObj, err := decoder.ReadJson() 253 | if err != nil { 254 | t.Fatalf("ReadJson failed: %v", err) 255 | } 256 | 257 | // Convert both to JSON for comparison 258 | originalJSON, _ := json.Marshal(originalObj) 259 | decodedJSON, _ := json.Marshal(decodedObj) 260 | if string(originalJSON) != string(decodedJSON) { 261 | t.Errorf("Json mismatch: got %s, want %s", decodedJSON, originalJSON) 262 | } 263 | } 264 | 265 | // TestWriteReadKey verifies encoding/decoding of key strings 266 | func TestWriteReadKey(t *testing.T) { 267 | encoder := NewUpdateEncoderV1() 268 | originalKey := "test_key_123" 269 | if err := encoder.WriteKey(originalKey); err != nil { 270 | t.Fatalf("WriteKey failed: %v", err) 271 | } 272 | data := encoder.ToUint8Array() 273 | 274 | decoder := NewUpdateDecoderV1(data) 275 | decodedKey, err := decoder.ReadKey() 276 | if err != nil { 277 | t.Fatalf("ReadKey failed: %v", err) 278 | } 279 | 280 | if decodedKey != originalKey { 281 | t.Errorf("Key mismatch: got '%s', want '%s'", decodedKey, originalKey) 282 | } 283 | } 284 | -------------------------------------------------------------------------------- /compatibility_test.go: -------------------------------------------------------------------------------- 1 | package y_crdt 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | "testing" 7 | 8 | "github.com/bytedance/mockey" 9 | ) 10 | 11 | func TestTextInsertDelete(t *testing.T) { 12 | // Generated via: 13 | // ```js 14 | // const doc = new Y.Doc() 15 | // const ytext = doc.getText('type') 16 | // doc..transact_mut()(function () { 17 | // ytext.insert(0, 'def') 18 | // ytext.insert(0, 'abc') 19 | // ytext.insert(6, 'ghi') 20 | // ytext.delete(2, 5) 21 | // }) 22 | // const update = Y.encodeStateAsUpdate(doc) 23 | // ytext.toString() // => 'abhi' 24 | // ``` 25 | // 26 | // This way we confirm that we can decode and apply: 27 | // 1. blocks without left/right origin consisting of multiple characters 28 | // 2. blocks with left/right origin consisting of multiple characters 29 | // 3. delete sets 30 | 31 | // construct doc by golang and check to see if the result is the same as the expected. 32 | doc := NewDoc("guid", false, nil, nil, false) 33 | ytext := doc.GetText("type") 34 | doc.Transact(func(trans *Transaction) { 35 | ytext.Insert(0, "def", nil) 36 | ytext.Insert(0, "abc", nil) 37 | ytext.Insert(6, "ghi", nil) 38 | ytext.Delete(2, 5) 39 | }, nil) 40 | 41 | if ytext.ToString() != "abhi" { 42 | t.Error("expected abhi, got ", ytext.ToString()) 43 | } 44 | t.Logf("construct by golang, ytext is %s", ytext.ToString()) 45 | 46 | // the payload was generated by javascript. 47 | var payload = []byte{ 48 | 1, 5, 152, 234, 173, 126, 0, 1, 1, 4, 116, 121, 112, 101, 3, 68, 152, 234, 173, 126, 0, 2, 49 | 97, 98, 193, 152, 234, 173, 126, 4, 152, 234, 173, 126, 0, 1, 129, 152, 234, 173, 126, 2, 50 | 1, 132, 152, 234, 173, 126, 6, 2, 104, 105, 1, 152, 234, 173, 126, 2, 0, 3, 5, 2, 51 | } 52 | 53 | // apply the update and check to see if the result is the same as the expected. 54 | doc = NewDoc("guid", false, nil, nil, false) 55 | doc.Transact(func(trans *Transaction) { 56 | ApplyUpdate(doc, payload, nil) 57 | }, nil) 58 | 59 | ytext = doc.GetText("type") 60 | if ytext.ToString() != "abhi" { 61 | t.Errorf("expected abhi, got %s", ytext.ToString()) 62 | } 63 | t.Logf("after apply update, ytext is %s", ytext.ToString()) 64 | } 65 | 66 | func TestMapSet(t *testing.T) { 67 | // Generated via: 68 | // ```js 69 | // const doc = new Y.Doc() 70 | // const x = doc.getMap('test') 71 | // x.set('k1', 'v1') 72 | // x.set('k2', 'v2') 73 | // const payload_v1 = Y.encodeStateAsUpdate(doc) 74 | // console.log(payload_v1); 75 | // const payload_v2 = Y.encodeStateAsUpdateV2(doc) 76 | // console.log(payload_v2); 77 | // ``` 78 | 79 | mocker := mockey.Mock(GenerateNewClientID).Return(440166001).Build() 80 | 81 | // construct doc by golang and check to see if the result is the same as the expected. 82 | doc := NewDoc("guid", false, nil, nil, false) 83 | x := doc.GetMap("test").(*YMap) 84 | doc.Transact(func(trans *Transaction) { 85 | x.Set("k1", "v1") 86 | x.Set("k2", "v2") 87 | }, nil) 88 | 89 | content, err := json.Marshal(x.ToJson()) 90 | if err != nil { 91 | t.Errorf("marshal x to json, err is %v", err) 92 | } 93 | 94 | t.Logf("construct by golang, x is %s", content) 95 | 96 | m := make(map[string]string) 97 | err = json.Unmarshal(content, &m) 98 | if err != nil || len(m) != 2 || m["k1"] != "v1" || m["k2"] != "v2" { 99 | t.Errorf("expected {\"k1\":\"v1\",\"k2\":\"v2\"}, got %s", content) 100 | } 101 | 102 | // the payload was generated by javascript. 103 | var payload = []byte{ 104 | 1, 2, 241, 204, 241, 209, 1, 0, 40, 1, 4, 116, 101, 115, 116, 2, 107, 49, 1, 119, 2, 118, 105 | 49, 40, 1, 4, 116, 101, 115, 116, 2, 107, 50, 1, 119, 2, 118, 50, 0, 106 | } 107 | 108 | // encode doc(geneareted by golang) and compare with payload(generated by javascript). 109 | update := EncodeStateAsUpdate(doc, nil) 110 | t.Logf("update is %v", update) 111 | if !bytes.Equal(update, payload) { 112 | t.Errorf("expect update:%v got update:%v", payload, update) 113 | } 114 | 115 | // apply the update(v1) and check to see if the result is the same as the expected. 116 | mocker.UnPatch() 117 | doc = NewDoc("guid", false, nil, nil, false) 118 | doc.Transact(func(trans *Transaction) { 119 | ApplyUpdate(doc, payload, nil) 120 | }, nil) 121 | 122 | content, err = json.Marshal(doc.GetMap("test").ToJson()) 123 | if err != nil { 124 | t.Errorf("marshal doc.GetMap(\"test\") to json, err is %v", err) 125 | } 126 | 127 | t.Logf("after apply update, x is %s", content) 128 | 129 | m = make(map[string]string) 130 | err = json.Unmarshal(content, &m) 131 | if err != nil || len(m) != 2 || m["k1"] != "v1" || m["k2"] != "v2" { 132 | t.Errorf("expected {\"k1\":\"v1\",\"k2\":\"v2\"}, got %s", content) 133 | } 134 | 135 | // decoder v2 not support yet. 136 | } 137 | 138 | func TestArrayInsert(t *testing.T) { 139 | // Generated via: 140 | // ```js 141 | // const doc = new Y.Doc() 142 | // const x = doc.getArray('test') 143 | // x.push(['a']); 144 | // x.push(['b']); 145 | // const payload_v1 = Y.encodeStateAsUpdate(doc) 146 | // console.log(payload_v1); 147 | // const payload_v2 = Y.encodeStateAsUpdateV2(doc) 148 | // console.log(payload_v2); 149 | // ``` 150 | 151 | mocker := mockey.Mock(GenerateNewClientID).Return(2525665872).Build() 152 | 153 | // construct doc by golang and check to see if the result is the same as the expected. 154 | doc := NewDoc("guid", false, nil, nil, false) 155 | x := doc.GetArray("test") 156 | x.Push([]any{"a"}) 157 | x.Push([]any{"b"}) 158 | 159 | content, err := json.Marshal(x.ToJson()) 160 | t.Logf("construct by golang, x is %s, err is %v", content, err) 161 | 162 | if !bytes.Equal(content, []byte("[\"a\",\"b\"]")) { 163 | t.Errorf("expected [\"a\",\"b\"], got %s", content) 164 | } 165 | 166 | // the payload was generated by javascript. 167 | var payload = []byte{ 168 | 1, 1, 208, 180, 170, 180, 9, 0, 8, 1, 4, 116, 101, 115, 116, 2, 119, 1, 97, 119, 1, 98, 0, 169 | } 170 | 171 | // encode doc(geneareted by golang) and compare with payload(generated by javascript). 172 | update := EncodeStateAsUpdate(doc, nil) 173 | t.Logf("update is %v", update) 174 | if !bytes.Equal(update, payload) { 175 | t.Errorf("expect update:%v got update:%v", payload, update) 176 | } 177 | 178 | // apply the update(v1) and check to see if the result is the same as the expected. 179 | mocker.UnPatch() 180 | doc = NewDoc("new doc", false, nil, nil, false) 181 | ApplyUpdate(doc, payload, nil) 182 | 183 | content, err = json.Marshal(doc.GetArray("test").ToJson()) 184 | t.Logf("after apply update, x is %s, err is %v", content, err) 185 | 186 | if !bytes.Equal(content, []byte("[\"a\",\"b\"]")) { 187 | t.Errorf("expected [\"a\",\"b\"], got %s", content) 188 | } 189 | } 190 | 191 | func TestXmlFragmentInsert(t *testing.T) { 192 | // Generated via: 193 | // ```js 194 | // const ydoc = new Y.Doc() 195 | // const yxmlFragment = ydoc.getXmlFragment('fragment-name') 196 | // const yxmlNested = new Y.XmlFragment('fragment-name') 197 | // const yxmlText = new Y.XmlText() 198 | // yxmlFragment.insert(0, [yxmlText]) 199 | // yxmlFragment.firstChild === yxmlText 200 | // yxmlFragment.insertAfter(yxmlText, [new Y.XmlElement('node-name')]) 201 | // const payload_v1 = Y.encodeStateAsUpdate(ydoc) 202 | // console.log(payload_v1); 203 | // const payload_v2 = Y.encodeStateAsUpdateV2(ydoc) 204 | // console.log(payload_v2); 205 | // ``` 206 | 207 | mockey.Mock(GenerateNewClientID).Return(2459881872).Build() 208 | 209 | // construct doc by golang and check to see if the result is the same as the expected. 210 | doc := NewDoc("guid", false, nil, nil, false) 211 | yxmlFragment := doc.GetXmlFragment("fragment-name").(*YXmlFragment) 212 | yxmlText := NewYXmlText() 213 | yxmlFragment.Insert(0, ArrayAny{yxmlText}) 214 | 215 | if yxmlFragment.GetFirstChild().(*YXmlText) != yxmlText { 216 | t.Errorf("expected yxmlFragment.GetFirstChild() is yxmlText, got %v", yxmlFragment.GetFirstChild()) 217 | } 218 | 219 | yxmlFragment.InsertAfter(yxmlText, ArrayAny{NewYXmlElement("node-name")}) 220 | update := EncodeStateAsUpdate(doc, nil) 221 | 222 | var payload = []byte{ 223 | 1, 2, 144, 163, 251, 148, 9, 0, 7, 1, 13, 102, 114, 97, 103, 109, 101, 110, 116, 45, 110, 224 | 97, 109, 101, 6, 135, 144, 163, 251, 148, 9, 0, 3, 9, 110, 111, 100, 101, 45, 110, 97, 109, 225 | 101, 0, 226 | } 227 | 228 | if !bytes.Equal(update, payload) { 229 | t.Errorf("expected update:%v got update:%v", payload, update) 230 | } 231 | } 232 | 233 | func TestStateVector(t *testing.T) { 234 | // Generated via: 235 | // ```js 236 | // const a = new Y.Doc() 237 | // const ta = a.getText('test') 238 | // ta.insert(0, 'abc') 239 | 240 | // const b = new Y.Doc() 241 | // const tb = b.getText('test') 242 | // tb.insert(0, 'de') 243 | 244 | // Y.applyUpdate(a, Y.encodeStateAsUpdate(b)) 245 | // console.log(Y.encodeStateVector(a)) 246 | // ``` 247 | 248 | var payload = []byte{2, 178, 219, 218, 44, 3, 190, 212, 225, 6, 2} 249 | sv := DecodeStateVector(payload) 250 | 251 | expected := map[Number]Number{ 252 | 14182974: 2, 253 | 93760946: 3, 254 | } 255 | 256 | for k, v := range expected { 257 | if sv[k] != v { 258 | t.Errorf("expected %v, got %v", v, sv[k]) 259 | } 260 | } 261 | 262 | serialized := EncodeStateVector(nil, sv, NewUpdateEncoderV1()) 263 | if !bytes.Equal(serialized, payload) { 264 | t.Errorf("expected: %v, got: %v", payload, serialized) 265 | } 266 | } 267 | -------------------------------------------------------------------------------- /delete_set.go: -------------------------------------------------------------------------------- 1 | package y_crdt 2 | 3 | import ( 4 | "errors" 5 | "sort" 6 | 7 | "github.com/mitchellh/copystructure" 8 | ) 9 | 10 | /* 11 | * We no longer maintain a DeleteStore. DeleteSet is a temporary object that is created when needed. 12 | * - When created in a transaction, it must only be accessed after sorting, and merging 13 | * - This DeleteSet is send to other clients 14 | * - We do not create a DeleteSet when we send a sync message. The DeleteSet message is created directly from StructStore 15 | * - We read a DeleteSet as part of a sync/update message. In this case the DeleteSet is already sorted and merged. 16 | */ 17 | 18 | type DeleteItem struct { 19 | Clock Number 20 | Length Number 21 | } 22 | 23 | type DeleteSet struct { 24 | Clients map[Number][]*DeleteItem 25 | } 26 | 27 | func NewDeleteItem(clock Number, length Number) *DeleteItem { 28 | return &DeleteItem{Clock: clock, Length: length} 29 | } 30 | 31 | func NewDeleteSet() *DeleteSet { 32 | return &DeleteSet{ 33 | Clients: make(map[Number][]*DeleteItem), 34 | } 35 | } 36 | 37 | // Iterate over all structs that the DeleteSet gc's. 38 | // f func(*GC|*Item) 39 | func IterateDeletedStructs(trans *Transaction, ds *DeleteSet, f func(s IAbstractStruct)) { 40 | for clientId, deletes := range ds.Clients { 41 | ss := trans.Doc.Store.Clients[clientId] 42 | if len(*ss) == 0 { 43 | continue 44 | } 45 | 46 | for i := 0; i < len(deletes); i++ { 47 | del := deletes[i] 48 | IterateStructs(trans, ss, del.Clock, del.Length, f) 49 | } 50 | } 51 | } 52 | 53 | func FindIndexDS(dis []*DeleteItem, clock Number) (Number, error) { 54 | left := 0 55 | right := len(dis) - 1 56 | 57 | for left <= right { 58 | midIndex := (left + right) / 2 59 | mid := dis[midIndex] 60 | midClock := mid.Clock 61 | 62 | if midClock <= clock { 63 | if clock < midClock+mid.Length { 64 | return midIndex, nil 65 | } 66 | left = midIndex + 1 67 | } else { 68 | right = midIndex - 1 69 | } 70 | } 71 | 72 | return 0, errors.New("not found") 73 | } 74 | 75 | func IsDeleted(ds *DeleteSet, id *ID) bool { 76 | dis := ds.Clients[id.Client] 77 | _, err := FindIndexDS(dis, id.Clock) 78 | return dis != nil && err == nil 79 | } 80 | 81 | func SortAndMergeDeleteSet(ds *DeleteSet) { 82 | for client, dels := range ds.Clients { 83 | sort.Slice(dels, func(i, j int) bool { 84 | return dels[i].Clock < dels[j].Clock 85 | }) 86 | 87 | // merge items without filtering or splicing the array 88 | // i is the current pointer 89 | // j refers to the current insert position for the pointed item 90 | // try to merge dels[i] into dels[j-1] or set dels[j]=dels[i] 91 | i, j := 1, 1 92 | for ; i < len(dels); i++ { 93 | left := dels[j-1] 94 | right := dels[i] 95 | 96 | if left.Clock+left.Length >= right.Clock { 97 | left.Length = Max(left.Length, right.Clock+right.Length-left.Clock) 98 | } else { 99 | if j < i { 100 | dels[j] = right 101 | } 102 | j++ 103 | } 104 | } 105 | 106 | ds.Clients[client] = dels[:j] 107 | } 108 | } 109 | 110 | func MergeDeleteSets(dss []*DeleteSet) *DeleteSet { 111 | merged := NewDeleteSet() 112 | 113 | for dssI := 0; dssI < len(dss); dssI++ { 114 | for client, delsLeft := range dss[dssI].Clients { 115 | _, exist := merged.Clients[client] 116 | if !exist { 117 | // Write all missing keys from current ds and all following. 118 | // If merged already contains `client` current ds has already been added. 119 | 120 | cp, err := copystructure.Copy(delsLeft) 121 | if err != nil { 122 | continue 123 | } 124 | 125 | dels := cp.([]*DeleteItem) 126 | for i := dssI + 1; i < len(dss); i++ { 127 | dels = append(dels, dss[i].Clients[client]...) 128 | } 129 | 130 | merged.Clients[client] = dels 131 | } 132 | } 133 | } 134 | 135 | SortAndMergeDeleteSet(merged) 136 | return merged 137 | } 138 | 139 | func AddToDeleteSet(ds *DeleteSet, client Number, clock Number, length Number) { 140 | ds.Clients[client] = append(ds.Clients[client], NewDeleteItem(clock, length)) 141 | } 142 | 143 | func NewDeleteSetFromStructStore(ss *StructStore) *DeleteSet { 144 | ds := NewDeleteSet() 145 | for client, structs := range ss.Clients { 146 | var disItems []*DeleteItem 147 | for i := 0; i < len(*structs); i++ { 148 | s := (*structs)[i] 149 | if s.Deleted() { 150 | clock := s.GetID().Clock 151 | length := s.GetLength() 152 | 153 | for i+1 < len(*structs) { 154 | next := (*structs)[i+1] 155 | if next.Deleted() { 156 | length += next.GetLength() 157 | i++ 158 | } else { 159 | break 160 | } 161 | } 162 | 163 | disItems = append(disItems, NewDeleteItem(clock, length)) 164 | } 165 | } 166 | 167 | if len(disItems) > 0 { 168 | ds.Clients[client] = disItems 169 | } 170 | } 171 | 172 | return ds 173 | } 174 | 175 | func WriteDeleteSet(encoder *UpdateEncoderV1, ds *DeleteSet) { 176 | WriteVarUint(encoder.RestEncoder, uint64(len(ds.Clients))) 177 | 178 | for client, dsItems := range ds.Clients { 179 | encoder.ResetDsCurVal() 180 | WriteVarUint(encoder.RestEncoder, uint64(client)) 181 | 182 | length := len(dsItems) 183 | WriteVarUint(encoder.RestEncoder, uint64(length)) 184 | 185 | for i := 0; i < length; i++ { 186 | item := dsItems[i] 187 | encoder.WriteDsClock(item.Clock) 188 | encoder.WriteDsLen(item.Length) 189 | } 190 | } 191 | } 192 | 193 | func WriteDeleteSetV2(encoder *UpdateEncoderV2, ds *DeleteSet) { 194 | WriteVarUint(encoder.RestEncoder, uint64(len(ds.Clients))) 195 | 196 | for client, dsItems := range ds.Clients { 197 | encoder.ResetDsCurVal() 198 | WriteVarUint(encoder.RestEncoder, uint64(client)) 199 | 200 | length := len(dsItems) 201 | WriteVarUint(encoder.RestEncoder, uint64(length)) 202 | 203 | for i := 0; i < length; i++ { 204 | item := dsItems[i] 205 | encoder.WriteDsClock(item.Clock) 206 | encoder.WriteDsLen(item.Length) 207 | } 208 | } 209 | } 210 | 211 | func ReadDeleteSet(decoder *UpdateDecoderV1) *DeleteSet { 212 | ds := NewDeleteSet() 213 | 214 | n, err := readVarUint(decoder.RestDecoder) 215 | if err != nil { 216 | return nil 217 | } 218 | 219 | numClients := n.(uint64) 220 | for i := uint64(0); i < numClients; i++ { 221 | decoder.ResetDsCurVal() 222 | 223 | n, err = readVarUint(decoder.RestDecoder) 224 | if err != nil { 225 | return nil 226 | } 227 | 228 | client := Number(n.(uint64)) 229 | 230 | n, err = readVarUint(decoder.RestDecoder) 231 | if err != nil { 232 | return nil 233 | } 234 | 235 | numberOfDeletes := n.(uint64) 236 | 237 | for j := uint64(0); j < numberOfDeletes; j++ { 238 | dsClock, err := decoder.ReadDsClock() 239 | if err != nil { 240 | return nil 241 | } 242 | 243 | dsLength, err := decoder.ReadDsLen() 244 | if err != nil { 245 | return nil 246 | } 247 | 248 | ds.Clients[client] = append(ds.Clients[client], NewDeleteItem(dsClock, dsLength)) 249 | } 250 | } 251 | 252 | return ds 253 | } 254 | 255 | func ReadAndApplyDeleteSet(decoder *UpdateDecoderV1, trans *Transaction, store *StructStore) []uint8 { 256 | n, err := readVarUint(decoder.RestDecoder) 257 | if err != nil { 258 | return nil 259 | } 260 | 261 | unappliedDS := NewDeleteSet() 262 | numClients := n.(uint64) 263 | 264 | for i := uint64(0); i < numClients; i++ { 265 | decoder.ResetDsCurVal() 266 | 267 | n, err = readVarUint(decoder.RestDecoder) 268 | if err != nil { 269 | return nil 270 | } 271 | client := Number(n.(uint64)) 272 | 273 | n, err = readVarUint(decoder.RestDecoder) 274 | if err != nil { 275 | return nil 276 | } 277 | numberOfDeletes := n.(uint64) 278 | 279 | structs := store.Clients[client] 280 | state := GetState(store, client) 281 | for j := uint64(0); j < numberOfDeletes; j++ { 282 | clock, err := decoder.ReadDsClock() 283 | if err != nil { 284 | return nil 285 | } 286 | 287 | length, err := decoder.ReadLen() 288 | if err != nil { 289 | return nil 290 | } 291 | 292 | clockEnd := clock + length 293 | 294 | if clock < state { 295 | if state < clockEnd { 296 | AddToDeleteSet(unappliedDS, client, state, clockEnd-state) 297 | } 298 | 299 | index, err := FindIndexSS(*structs, clock) 300 | if err != nil { 301 | return nil 302 | } 303 | 304 | // We can ignore the case of GC and Delete structs, because we are going to skip them 305 | s := (*structs)[index] 306 | 307 | // split the first item if necessary 308 | if !s.Deleted() && s.GetID().Clock < clock { 309 | items := []IAbstractStruct{SplitItem(trans, s.(*Item), clock-s.GetID().Clock)} 310 | SpliceStruct(structs, index+1, 0, items) 311 | index++ // increase we now want to use the next struct 312 | } 313 | 314 | for index < len(*structs) { 315 | st := (*structs)[index].(IAbstractStruct) 316 | index++ 317 | 318 | if st.GetID().Clock < clockEnd { 319 | if !st.Deleted() { 320 | item, _ := st.(*Item) 321 | if item != nil { 322 | if clockEnd < item.GetID().Clock+item.GetLength() { 323 | items := []IAbstractStruct{SplitItem(trans, item, clockEnd-st.GetID().Clock)} 324 | SpliceStruct(structs, index, 0, items) 325 | } 326 | item.Delete(trans) 327 | } 328 | } 329 | } else { 330 | break 331 | } 332 | } 333 | } else { 334 | AddToDeleteSet(unappliedDS, client, clock, clockEnd-clock) 335 | } 336 | } 337 | } 338 | 339 | if len(unappliedDS.Clients) > 0 { 340 | ds := NewUpdateEncoderV2() 341 | WriteVarUint(ds.RestEncoder, 0) 342 | WriteDeleteSetV2(ds, unappliedDS) 343 | return ds.ToUint8Array() 344 | } 345 | 346 | return nil 347 | } 348 | -------------------------------------------------------------------------------- /undo_manager.go: -------------------------------------------------------------------------------- 1 | package y_crdt 2 | 3 | import ( 4 | "reflect" 5 | ) 6 | 7 | type StackItem struct { 8 | Insertions *DeleteSet 9 | Deletions *DeleteSet 10 | Meta map[interface{}]interface{} // Use this to save and restore metadata like selection range 11 | } 12 | 13 | // @typedef {Object} UndoManagerOptions 14 | // @property {number} [UndoManagerOptions.captureTimeout=500] 15 | // @property {function(Item):boolean} [UndoManagerOptions.deleteFilter=()=>true] Sometimes 16 | // it is necessary to filter whan an Undo/Redo operation can delete. If this 17 | // filter returns false, the type/item won't be deleted even it is in the 18 | // undo/redo scope. 19 | // @property {Set} [UndoManagerOptions.trackedOrigins=new Set([null])] 20 | // 21 | // Fires 'stack-item-added' event when a stack item was added to either the undo- or 22 | // the redo-stack. You may store additional stack information via the 23 | // metadata property on `event.stackItem.meta` (it is a `Map` of metadata properties). 24 | // Fires 'stack-item-popped' event when a stack item was popped from either the 25 | // undo- or the redo-stack. You may restore the saved stack information from `event.stackItem.meta`. 26 | // 27 | // @extends {Observable<'stack-item-added'|'stack-item-popped'>} 28 | type UndoManager struct { 29 | *Observable 30 | Scopes []IAbstractType 31 | DeleteFilter func(item *Item) bool 32 | TrackedOrigins Set 33 | UndoStack []*StackItem 34 | RedoStack []*StackItem 35 | 36 | // Whether the client is currently undoing (calling UndoManager.undo) 37 | Undoing bool 38 | Redoing bool 39 | LastChange Number 40 | } 41 | 42 | func (u *UndoManager) GetDoc() *Doc { 43 | return u.Scopes[0].GetDoc() 44 | } 45 | 46 | func (u *UndoManager) Clear() { 47 | u.GetDoc().Transact(func(trans *Transaction) { 48 | clearItem := func(stackItem *StackItem) { 49 | IterateDeletedStructs(trans, stackItem.Deletions, func(s IAbstractStruct) { 50 | item, ok := s.(*Item) 51 | isParent := false 52 | for _, scope := range u.Scopes { 53 | if IsParentOf(scope, item) { 54 | isParent = true 55 | break 56 | } 57 | } 58 | 59 | if ok && isParent { 60 | KeepItem(item, false) 61 | } 62 | }) 63 | } 64 | 65 | for _, stack := range u.UndoStack { 66 | clearItem(stack) 67 | } 68 | 69 | for _, stack := range u.RedoStack { 70 | clearItem(stack) 71 | } 72 | }, nil) 73 | 74 | u.UndoStack = nil 75 | u.RedoStack = nil 76 | } 77 | 78 | // UndoManager merges Undo-StackItem if they are created within time-gap 79 | // smaller than `options.captureTimeout`. Call `um.stopCapturing()` so that the next 80 | // StackItem won't be merged. 81 | // 82 | // @example 83 | // 84 | // // without stopCapturing 85 | // ytext.insert(0, 'a') 86 | // ytext.insert(1, 'b') 87 | // um.undo() 88 | // ytext.toString() // => '' (note that 'ab' was removed) 89 | // // with stopCapturing 90 | // ytext.insert(0, 'a') 91 | // um.stopCapturing() 92 | // ytext.insert(0, 'b') 93 | // um.undo() 94 | // ytext.toString() // => 'a' (note that only 'b' was removed) 95 | func (u *UndoManager) StopCapturing() { 96 | u.LastChange = 0 97 | } 98 | 99 | // Undo last changes on type. 100 | func (u *UndoManager) Undo() *StackItem { 101 | u.Undoing = true 102 | res := PopStackItem(u, u.UndoStack, "undo") 103 | u.Undoing = false 104 | return res 105 | } 106 | 107 | // Redo last undo operation. 108 | func (u *UndoManager) Redo() *StackItem { 109 | u.Redoing = true 110 | res := PopStackItem(u, u.RedoStack, "redo") 111 | u.Redoing = false 112 | return res 113 | } 114 | 115 | func NewStackItem(deletes *DeleteSet, insertions *DeleteSet) *StackItem { 116 | return &StackItem{ 117 | Deletions: deletes, 118 | Insertions: insertions, 119 | Meta: make(map[interface{}]interface{}), 120 | } 121 | } 122 | 123 | func PopStackItem(undoManager *UndoManager, stack []*StackItem, eventType string) *StackItem { 124 | // Whether a change happened 125 | var result *StackItem 126 | 127 | // Keep a reference to the transaction so we can fire the event with the changedParentTypes 128 | var tr *Transaction 129 | doc := undoManager.GetDoc() 130 | scopes := undoManager.Scopes 131 | Transact(doc, func(trans *Transaction) { 132 | for len(stack) > 0 && result == nil { 133 | store := doc.Store 134 | stackItem := stack[len(stack)-1] 135 | stack = stack[:len(stack)-1] 136 | itemsToRedo := NewSet() 137 | var itemsToDelete []*Item 138 | performedChange := false 139 | 140 | IterateDeletedStructs(trans, stackItem.Insertions, func(s IAbstractStruct) { 141 | it, ok := s.(*Item) 142 | if ok { 143 | if it.Redone != nil { 144 | item, diff := FollowRedone(store, it.ID) 145 | if diff > 0 { 146 | item = GetItemCleanStart(trans, GenID(item.ID.Client, item.ID.Clock+diff)) 147 | } 148 | it = item 149 | } 150 | 151 | isParent := false 152 | for _, scope := range scopes { 153 | if IsParentOf(scope, it) { 154 | isParent = true 155 | break 156 | } 157 | } 158 | 159 | if !it.Deleted() && isParent { 160 | itemsToDelete = append(itemsToDelete, it) 161 | } 162 | } 163 | }) 164 | 165 | IterateDeletedStructs(trans, stackItem.Deletions, func(s IAbstractStruct) { 166 | it, ok := s.(*Item) 167 | isParent := false 168 | for _, scope := range scopes { 169 | if IsParentOf(scope, it) { 170 | isParent = true 171 | break 172 | } 173 | } 174 | 175 | if ok && isParent && !IsDeleted(stackItem.Insertions, s.GetID()) { 176 | // Never redo structs in stackItem.insertions because they were created and deleted in the same capture interval. 177 | itemsToRedo.Add(it) 178 | } 179 | }) 180 | 181 | for s := range itemsToRedo { 182 | performedChange = RedoItem(trans, s.(*Item), itemsToRedo) != nil || performedChange 183 | } 184 | 185 | // We want to delete in reverse order so that children are deleted before 186 | // parents, so we have more information available when items are filtered. 187 | for i := len(itemsToDelete) - 1; i >= 0; i-- { 188 | item := itemsToDelete[i] 189 | if undoManager.DeleteFilter(item) { 190 | item.Delete(trans) 191 | performedChange = true 192 | } 193 | } 194 | 195 | if performedChange { 196 | result = stackItem 197 | } else { 198 | result = nil 199 | } 200 | } 201 | 202 | for t, subProps := range trans.Changed { 203 | // destroy search marker if necessary 204 | if subProps.Has(nil) && t.(IAbstractType).GetSearchMarker() != nil { 205 | // destroy search marker if necessary 206 | t.(IAbstractType).SetSearchMarker(nil) 207 | } 208 | } 209 | 210 | tr = trans 211 | }, undoManager, true) 212 | 213 | if result != nil { 214 | changedParentTypes := tr.ChangedParentTypes 215 | obj := NewObject() 216 | obj["stackItem"] = result 217 | obj["type"] = eventType 218 | obj["changedParentTypes"] = changedParentTypes 219 | undoManager.Emit("stack-item-popped", obj, undoManager) 220 | } 221 | 222 | return result 223 | } 224 | 225 | func NewUndoManager(typeScope IAbstractType, captureTimeout Number, deleteFilter func(item *Item) bool, trackedOrigins Set) *UndoManager { 226 | u := &UndoManager{} 227 | u.Observable = NewObservable() 228 | u.Scopes = append(u.Scopes, typeScope) 229 | u.DeleteFilter = deleteFilter 230 | trackedOrigins.Add(u) 231 | u.TrackedOrigins = trackedOrigins 232 | 233 | u.Undoing = false 234 | u.Redoing = false 235 | 236 | doc := u.GetDoc() 237 | u.LastChange = 0 238 | doc.On("afterTransaction", NewObserverHandler(func(v ...interface{}) { 239 | // Only track certain transactions 240 | if len(v) == 0 { 241 | return 242 | } 243 | 244 | trans := v[0].(*Transaction) 245 | for _, t := range u.Scopes { 246 | if _, exist := trans.ChangedParentTypes[t]; !exist { 247 | return 248 | } 249 | } 250 | 251 | if trans.Origin != nil { 252 | if !u.TrackedOrigins.Has(trans.Origin) && (trans.Origin != nil || !u.TrackedOrigins.Has(reflect.TypeOf(trans.Origin).String())) { 253 | return 254 | } 255 | } 256 | undoing := u.Undoing 257 | redoing := u.Redoing 258 | 259 | var stack *[]*StackItem 260 | if undoing { 261 | stack = &u.RedoStack 262 | } else { 263 | stack = &u.UndoStack 264 | } 265 | 266 | if undoing { 267 | // next undo should not be appended to last stack item 268 | u.StopCapturing() 269 | } else if !redoing { 270 | // neither undoing nor redoing: delete redoStack 271 | u.RedoStack = nil 272 | } 273 | 274 | insertions := NewDeleteSet() 275 | for client, endClock := range trans.AfterState { 276 | startClock := trans.BeforeState[client] 277 | length := endClock - startClock 278 | if length > 0 { 279 | AddToDeleteSet(insertions, client, startClock, length) 280 | } 281 | } 282 | 283 | now := GetUnixTime() // ms 284 | if Number(now)-u.LastChange < captureTimeout && len(*stack) > 0 && !undoing && !redoing { 285 | // append change to last stack op 286 | lastOp := (*stack)[len(*stack)-1] 287 | lastOp.Deletions = MergeDeleteSets([]*DeleteSet{lastOp.Deletions, trans.DeleteSet}) 288 | lastOp.Insertions = MergeDeleteSets([]*DeleteSet{lastOp.Insertions, insertions}) 289 | } else { 290 | // create a new stack op 291 | *stack = append(*stack, NewStackItem(trans.DeleteSet, insertions)) 292 | } 293 | 294 | if !undoing && !redoing { 295 | u.LastChange = Number(now) 296 | } 297 | 298 | // make sure that deleted structs are not gc'd 299 | IterateDeletedStructs(trans, trans.DeleteSet, func(item IAbstractStruct) { 300 | keep := false 301 | if IsSameType(item, &Item{}) { 302 | it := item.(*Item) 303 | for _, t := range u.Scopes { 304 | if IsParentOf(t, it) { 305 | keep = true 306 | break 307 | } 308 | } 309 | } 310 | 311 | if keep { 312 | KeepItem(item.(*Item), true) 313 | } 314 | }) 315 | 316 | obj := NewObject() 317 | obj["stackItem"] = (*stack)[len(*stack)-1] 318 | obj["origin"] = trans.Origin 319 | if undoing { 320 | obj["type"] = "redo" 321 | } else { 322 | obj["type"] = "undo" 323 | } 324 | obj["changedParentTypes"] = trans.ChangedParentTypes 325 | u.Emit("stack-item-added", obj, u) 326 | })) 327 | 328 | return u 329 | } 330 | -------------------------------------------------------------------------------- /decoding.go: -------------------------------------------------------------------------------- 1 | package y_crdt 2 | 3 | import ( 4 | "bytes" 5 | "encoding/binary" 6 | "errors" 7 | "fmt" 8 | "io" 9 | "math" 10 | ) 11 | 12 | /* 13 | * Encoding table: 14 | | Data Type | Prefix | Encoding Method | Comment | 15 | | ------------------- | ------ | ------------------ | ------------------------------------------------------------------------------------ | 16 | | undefined | 127 | | Functions, symbol, and everything that cannot be identified is encoded as undefined | 17 | | null | 126 | | | 18 | | integer | 125 | WriteVarInt | Only encodes 32 bit signed integers | 19 | | float32 | 124 | WriteFloat32 | | 20 | | float64 | 123 | WriteFloat64 | | 21 | | bigint | 122 | writeBigInt64 | | 22 | | boolean (false) | 121 | | True and false are different data types so we save the following byte | 23 | | boolean (true) | 120 | | - 0b01111000 so the last bit determines whether true or false | 24 | | string | 119 | writeVarString | | 25 | | object | 118 | custom | Writes {length} then {length} key-value pairs | 26 | | array | 117 | custom | Writes {length} then {length} json values | 27 | | Uint8Array | 116 | WriteVarUint8Array | We use Uint8Array for any kind of binary data | 28 | */ 29 | 30 | var overflow = errors.New("binary: varint overflows a 64-bit integer") 31 | 32 | var ReadAnyLookupTable = []func(decoder *bytes.Buffer) (any, error){ 33 | undefined, // CASE 127: undefined 34 | null, // CASE 126: null 35 | ReadVarInt, // CASE 125: integer 36 | ReadFloat32, // CASE 124: float32 37 | ReadFloat64, // CASE 123: float64 38 | ReadBigInt64, // CASE 122: bigint 39 | readFalse, // CASE 121: boolean (false) 40 | readTrue, // CASE 120: boolean (true) 41 | ReadVarString, // CASE 119: string 42 | } 43 | 44 | func init() { 45 | ReadAnyLookupTable = append(ReadAnyLookupTable, ReadObject) // CASE 118: object 46 | ReadAnyLookupTable = append(ReadAnyLookupTable, ReadArray) // CASE 117: array 47 | ReadAnyLookupTable = append(ReadAnyLookupTable, ReadVarUint8Array) // CASE 116: Uint8Array 48 | } 49 | 50 | // undefined returns the Undefined constant indicating an undefined value. 51 | func undefined(decoder *bytes.Buffer) (any, error) { 52 | return Undefined, nil 53 | } 54 | 55 | // null returns the Null constant indicating a null value. 56 | func null(decoder *bytes.Buffer) (any, error) { 57 | return Null, nil 58 | } 59 | 60 | // hasContent checks if the decoder has any content to read. 61 | func hasContent(decoder *bytes.Buffer) bool { 62 | return decoder.Len() > 0 63 | } 64 | 65 | // readVarUint decodes a variable-length unsigned integer (Uvarint) from the decoder buffer. 66 | func readVarUint(decoder *bytes.Buffer) (any, error) { 67 | number, err := binary.ReadUvarint(decoder) 68 | if err != nil { 69 | return uint64(0), err 70 | } 71 | 72 | return number, nil 73 | } 74 | 75 | // readFalse returns the boolean false value. 76 | func readFalse(decoder *bytes.Buffer) (any, error) { 77 | return false, nil 78 | } 79 | 80 | // readTrue returns the boolean true value. 81 | func readTrue(decoder *bytes.Buffer) (any, error) { 82 | return true, nil 83 | } 84 | 85 | // ReadUint8 reads and returns a single uint8 from the decoder buffer. 86 | func ReadUint8(decoder *bytes.Buffer) (uint8, error) { 87 | data, err := decoder.ReadByte() 88 | if err != nil { 89 | return 0, err 90 | } 91 | 92 | return data, err 93 | } 94 | 95 | // ReadVarInt reads and returns a varint-encoded integer from the decoder buffer. 96 | func ReadVarInt(decoder *bytes.Buffer) (any, error) { 97 | data, err := decoder.ReadByte() 98 | if err != nil { 99 | return nil, err 100 | } 101 | 102 | // read the low 6 bits of the byte, the low 6 bits are the number. 103 | number := data & BITS6 104 | 105 | // the first bit is the sign bit, if the first bit is 1, then the number is negative, otherwise it is positive. 106 | sign := 1 107 | if data&BIT7 > 0 { 108 | sign = -1 109 | } 110 | 111 | // the next_flag is the 8th bit, if the next_flag is 0, then the number is done 112 | if data&BIT8 == 0 { 113 | return sign * Number(number), nil 114 | } 115 | 116 | n := uint64(number) 117 | s := uint(6) 118 | for i := 0; i < binary.MaxVarintLen64; i++ { 119 | b, err := decoder.ReadByte() 120 | if err != nil { 121 | return n, err 122 | } 123 | 124 | // if the next bit is 0, then the number is done 125 | if b < BIT8 { 126 | // a 10-byte varint can represent at most a 64-bit integer, 127 | // where the first 9 bytes each contribute 7 bits, 128 | // and the 10th byte contributes 1 bit. 129 | if i == 9 && b > 1 { 130 | return n, overflow 131 | } 132 | 133 | return sign * Number(n|uint64(b)< uint64(decoder.Len()) { 189 | return "", fmt.Errorf("buffer is not enough, expected %d, got %d", size, decoder.Len()) 190 | } 191 | 192 | buf := make([]byte, size) 193 | err = binary.Read(decoder, binary.LittleEndian, buf) 194 | if err != nil { 195 | return "", err 196 | } 197 | 198 | return string(buf), nil 199 | } 200 | 201 | // ReadString reads a variable-length string from the decoder buffer. 202 | func ReadString(decoder *bytes.Buffer) (string, error) { 203 | size, err := binary.ReadUvarint(decoder) 204 | if err != nil { 205 | return "", err 206 | } 207 | 208 | if size == 0 { 209 | return "", nil 210 | } 211 | 212 | // the size is too large, it is greater than the buffer size 213 | if size > uint64(decoder.Len()) { 214 | return "", errors.New("buffer is not enough") 215 | } 216 | 217 | buf := make([]byte, size) 218 | _, err = io.ReadFull(decoder, buf) 219 | if err != nil { 220 | return "", err 221 | } 222 | 223 | return string(buf), nil 224 | } 225 | 226 | // ReadObject decodes an object from the decoder buffer. 227 | func ReadObject(decoder *bytes.Buffer) (any, error) { 228 | size, err := binary.ReadUvarint(decoder) 229 | if err != nil { 230 | return nil, err 231 | } 232 | 233 | obj := NewObject() 234 | if size == 0 { 235 | return obj, nil 236 | } 237 | 238 | for i := uint64(0); i < size; i++ { 239 | key, err := ReadVarString(decoder) 240 | if err != nil { 241 | return obj, err 242 | } 243 | 244 | value, err := ReadAny(decoder) 245 | if err != nil { 246 | return obj, err 247 | } 248 | 249 | obj[key.(string)] = value 250 | } 251 | 252 | return obj, nil 253 | } 254 | 255 | // ReadArray decodes an array from the decoder buffer. 256 | func ReadArray(decoder *bytes.Buffer) (any, error) { 257 | array := make(ArrayAny, 0) 258 | 259 | size, err := binary.ReadUvarint(decoder) 260 | if err != nil { 261 | return array, err 262 | } 263 | 264 | if size == 0 { 265 | return array, nil 266 | } 267 | 268 | array = make(ArrayAny, size) 269 | for i := uint64(0); i < size; i++ { 270 | value, err := ReadAny(decoder) 271 | if err != nil { 272 | return array, err 273 | } 274 | 275 | array[i] = value 276 | } 277 | 278 | return array, nil 279 | } 280 | 281 | // ReadVarUnit8Array decodes a Uint8Array (byte slice) from the decoder buffer. 282 | func ReadVarUint8Array(decoder *bytes.Buffer) (any, error) { 283 | size, err := binary.ReadUvarint(decoder) 284 | if err != nil { 285 | return nil, err 286 | } 287 | 288 | buf := make([]byte, size) 289 | decoder.Read(buf) 290 | 291 | return buf, nil 292 | } 293 | 294 | // ReadVarUint is a wrapper around readVarUint that returns the decoded uint64. 295 | func ReadVarUint(decoder *bytes.Buffer) uint64 { 296 | value, _ := readVarUint(decoder) 297 | number, _ := value.(uint64) 298 | return number 299 | } 300 | 301 | // ReadAny is the general decoding dispatcher that uses ReadAnyLookupTable. 302 | func ReadAny(decoder *bytes.Buffer) (any, error) { 303 | tag, err := ReadUint8(decoder) 304 | if err != nil { 305 | return nil, err 306 | } 307 | 308 | refID := 127 - tag 309 | if int(refID) >= len(ReadAnyLookupTable) { 310 | return nil, fmt.Errorf("index out of range. tag:%d refID:%d len:%d", tag, refID, len(ReadAnyLookupTable)) 311 | } 312 | 313 | return ReadAnyLookupTable[127-tag](decoder) 314 | } 315 | -------------------------------------------------------------------------------- /utils.go: -------------------------------------------------------------------------------- 1 | package y_crdt 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "math/rand" 7 | "reflect" 8 | "sort" 9 | "strings" 10 | "time" 11 | "unicode/utf16" 12 | ) 13 | 14 | var DefaultGCFilter = func(item *Item) bool { 15 | return true 16 | } 17 | 18 | var Logf = func(format string, a ...interface{}) { 19 | fmt.Printf(format+"\n", a...) 20 | } 21 | 22 | var Log = func(a ...interface{}) { 23 | fmt.Println(a...) 24 | } 25 | 26 | // SpliceStruct inserts elements into a slice at the specified start index, 27 | // deleting deleteCount elements if deleteCount is greater than 0. 28 | // It returns the deleted elements if deleteCount is greater than 0, otherwise it returns nil. 29 | func SpliceStruct(ss *[]IAbstractStruct, start Number, deleteCount Number, elements []IAbstractStruct) { 30 | // check if the capacity is enough to store the elements, if the deleted elements are greater than or equal to the elements to be inserted, 31 | // then the elements to be deleted can be directly overwritten, and the remaining elements can be moved forward to the position of the elements to be deleted. 32 | if deleteCount >= len(elements) { 33 | // find the position to insert the elements, and directly overwrite the elements to be deleted 34 | partSlice := (*ss)[:start] 35 | partSlice = append(partSlice, elements...) 36 | 37 | // move the remaining elements forward to position of the elements to be deleted. 38 | first := start + len(elements) 39 | second := start + deleteCount 40 | for i := second; i < len(*ss); i++ { 41 | (*ss)[first] = (*ss)[second] 42 | first++ 43 | second++ 44 | } 45 | 46 | *ss = (*ss)[:first] 47 | return 48 | } 49 | 50 | // if the capacity is not enough to store the elements, then the elements need to be copied 51 | // and the capacity is expanded to the sum of the original capacity and the length of the elements to be inserted. 52 | if cap(*ss) < (len(*ss) + len(elements) - deleteCount) { 53 | SpliceStructInner(ss, start, deleteCount, elements) 54 | return 55 | } 56 | 57 | // the capacity is enough to store the elements, then the elements need to be moved forward to the position of the elements to be deleted, 58 | // and then the elements to be inserted are appended to the slice. 59 | originLength := len(*ss) 60 | (*ss) = (*ss)[:len(*ss)+len(elements)-deleteCount] 61 | 62 | offset := len(elements) - deleteCount 63 | for i := originLength - 1; i >= start; i-- { 64 | (*ss)[i+offset] = (*ss)[i] 65 | } 66 | 67 | partSlice := (*ss)[:start] 68 | partSlice = append(partSlice, elements...) 69 | 70 | return 71 | } 72 | 73 | // SpliceStructInner copies the elements to be inserted into a new slice, 74 | // and then appends the new slice to the original slice. 75 | // It returns the deleted elements if deleteCount is greater than 0, otherwise it returns nil. 76 | // The capacity of the original slice is not expanded. 77 | func SpliceStructInner(ss *[]IAbstractStruct, start Number, deleteCount Number, elements []IAbstractStruct) { 78 | combine := make([]IAbstractStruct, 0, len(*ss)+len(elements)-deleteCount) 79 | combine = append(combine, (*ss)[:start]...) 80 | combine = append(combine, elements...) 81 | combine = append(combine, (*ss)[start+deleteCount:]...) 82 | *ss = combine 83 | 84 | return 85 | } 86 | 87 | // SpliceArray inserts elements into a slice at the specified start index, 88 | // deleting deleteCount elements if deleteCount is greater than 0. 89 | // It returns the deleted elements if deleteCount is greater than 0, otherwise it returns nil. 90 | func SpliceArray(a *ArrayAny, start Number, deleteCount Number, elements ArrayAny) ArrayAny { 91 | // check if the capacity is enough to store the elements, if the deleted elements are greater than or equal to the elements to be inserted, 92 | // then the elements to be deleted can be directly overwritten, and the remaining elements can be moved forward to the position of the elements to be deleted. 93 | if deleteCount >= len(elements) { 94 | // find the position to insert the elements, and directly overwrite the elements to be deleted 95 | partSlice := (*a)[:start] 96 | partSlice = append(partSlice, elements...) 97 | 98 | // move the remaining elements forward to position of the elements to be deleted. 99 | first := start + len(elements) 100 | second := start + deleteCount 101 | for i := second; i < len(*a); i++ { 102 | (*a)[first] = (*a)[second] 103 | first++ 104 | second++ 105 | } 106 | 107 | *a = (*a)[:first] 108 | return nil 109 | } 110 | 111 | // the capacity is not enough to store the elements, then the elements need to be copied 112 | // and the capacity is expanded to the sum of the original capacity and the length of the elements to be inserted. 113 | if cap(*a) < (len(*a) + len(elements) - deleteCount) { 114 | return SpliceArrayInner(a, start, deleteCount, elements) 115 | } 116 | 117 | // the capacity is enough to store the elements, then the elements need to be moved forward to the position of the elements to be deleted, 118 | // and then the elements to be inserted are appended to the slice. 119 | originLength := len(*a) 120 | (*a) = (*a)[:len(*a)+len(elements)-deleteCount] 121 | 122 | offset := len(elements) - deleteCount 123 | for i := originLength - 1; i >= start; i-- { 124 | (*a)[i+offset] = (*a)[i] 125 | } 126 | 127 | partSlice := (*a)[:start] 128 | partSlice = append(partSlice, elements...) 129 | 130 | return nil 131 | } 132 | 133 | // SpliceArrayInner copies the elements to be inserted into a new slice, 134 | // and then appends the new slice to the original slice. 135 | func SpliceArrayInner(a *ArrayAny, start Number, deleteCount Number, elements ArrayAny) ArrayAny { 136 | var deleteElements ArrayAny 137 | if deleteCount > 0 { 138 | deleteElements = (*a)[start : start+deleteCount] 139 | } 140 | 141 | combine := make(ArrayAny, 0, len(*a)+len(elements)-deleteCount) 142 | combine = append(combine, (*a)[:start]...) 143 | combine = append(combine, elements...) 144 | combine = append(combine, (*a)[start+deleteCount:]...) 145 | *a = combine 146 | 147 | return deleteElements 148 | } 149 | 150 | // CharCodeAt returns the Unicode code point of the character at the specified index in the given string. 151 | // The index is the position of the character in the string, starting from 0. 152 | // If the index is out of range, it returns an error. 153 | func CharCodeAt(str string, pos Number) (uint16, error) { 154 | data := utf16.Encode([]rune(str)) 155 | if pos >= len(data) || pos < 0 { 156 | return 0, errors.New("index out of range") 157 | } 158 | 159 | return data[pos], nil 160 | } 161 | 162 | // StringHeader returns the substring of the given string from the beginning to the offset. 163 | func StringHeader(str string, offset Number) string { 164 | encode := utf16.Encode([]rune(str)) 165 | if offset >= len(encode) { 166 | return string(utf16.Decode(encode)) 167 | } 168 | 169 | return string(utf16.Decode(encode[:offset])) 170 | } 171 | 172 | // StringTail returns the substring of the given string from the offset to the end. 173 | func StringTail(str string, offset Number) string { 174 | encode := utf16.Encode([]rune(str)) 175 | if offset >= len(encode) { 176 | return "" 177 | } 178 | return string(utf16.Decode(encode[offset:])) 179 | } 180 | 181 | // ReplaceChar replaces the character at the specified index in the given string with the given character. 182 | func ReplaceChar(str string, pos Number, char uint16) string { 183 | data := utf16.Encode([]rune(str)) 184 | data[pos] = char 185 | return string(utf16.Decode(data)) 186 | } 187 | 188 | // StringLength returns the length of the given string in utf16 code points. 189 | func StringLength(str string) Number { 190 | encode := utf16.Encode([]rune(str)) 191 | return len(encode) 192 | } 193 | 194 | // MergeString merges two strings. 195 | func MergeString(str1, str2 string) string { 196 | // if the length of the two strings is less than or equal to 10000, 197 | // the fastest way is to use + to merge two strings. 198 | if len(str1)+len(str2) <= 10000 { 199 | return str1 + str2 200 | } 201 | 202 | builder := strings.Builder{} 203 | builder.Grow(len(str1) + len(str2)) 204 | builder.WriteString(str1) 205 | builder.WriteString(str2) 206 | return builder.String() 207 | } 208 | 209 | // Conditional returns a if cond is true, otherwise b. 210 | func Conditional(cond bool, a interface{}, b interface{}) interface{} { 211 | if cond { 212 | return a 213 | } 214 | 215 | return b 216 | } 217 | 218 | // GenerateNewClientID generates a new client ID. 219 | func GenerateNewClientID() Number { 220 | return Number(rand.Int31()) 221 | } 222 | 223 | // Check if `parent` is a parent of `child`. 224 | func IsParentOf(parent IAbstractType, child *Item) bool { 225 | for child != nil { 226 | if child.Parent == parent { 227 | return true 228 | } 229 | 230 | child = child.Parent.(IAbstractType).GetItem() 231 | } 232 | 233 | return false 234 | } 235 | 236 | // Max returns the maximum value of a and b. 237 | func Max(a, b Number) Number { 238 | if a > b { 239 | return a 240 | } 241 | 242 | return b 243 | } 244 | 245 | // Min returns the minimum value of a and b. 246 | func Min(a, b Number) Number { 247 | if a < b { 248 | return a 249 | } 250 | 251 | return b 252 | } 253 | 254 | // ArrayLast returns the last element of the given array. 255 | // If the array is empty, it returns an error. 256 | func ArrayLast(a ArrayAny) (interface{}, error) { 257 | if len(a) == 0 { 258 | return nil, errors.New("empty array") 259 | } 260 | 261 | return a[len(a)-1], nil 262 | } 263 | 264 | // ArrayFilter filters the elements of the given array by the given filter function. 265 | // It returns a new array that contains the elements that satisfy the filter function. 266 | func ArrayFilter(a []IEventType, filter func(e IEventType) bool) []IEventType { 267 | var targets []IEventType 268 | 269 | for _, e := range a { 270 | if filter(e) { 271 | targets = append(targets, e) 272 | } 273 | } 274 | 275 | return targets 276 | } 277 | 278 | // Unshift adds the given element to the beginning of the given array. 279 | func Unshift(a ArrayAny, e interface{}) ArrayAny { 280 | if a == nil { 281 | a = ArrayAny{e} 282 | } else { 283 | a = append(ArrayAny{e}, a) 284 | } 285 | 286 | return a 287 | } 288 | 289 | // MapAny returns true if the given map satisfies the given function. 290 | func MapAny(m map[Number]Number, f func(key, value Number) bool) bool { 291 | for key, value := range m { 292 | if f(key, value) { 293 | return true 294 | } 295 | } 296 | 297 | return false 298 | } 299 | 300 | // MergeSortedRange merges two sorted ranges of the given map. The isInc parameter determines the order of the ranges. 301 | func MapSortedRange(m map[Number]Number, isInc bool, f func(key, value Number)) { 302 | var keys NumberSlice 303 | for k := range m { 304 | keys = append(keys, k) 305 | } 306 | 307 | if isInc { 308 | sort.Sort(keys) 309 | } else { 310 | sort.Sort(sort.Reverse(keys)) 311 | } 312 | 313 | for _, k := range keys { 314 | f(k, m[k]) 315 | } 316 | } 317 | 318 | // CallAll calls all the functions in the given slice with the given arguments. 319 | func CallAll(fs *[]func(...interface{}), args ArrayAny, i int) { 320 | for ; i < len(*fs); i++ { 321 | (*fs)[i](args...) 322 | } 323 | } 324 | 325 | // EqualAttrs returns true if the two given objects have the same attributes. 326 | func EqualAttrs(a, b interface{}) bool { 327 | return reflect.DeepEqual(a, b) 328 | } 329 | 330 | // EqualContentFormat returns true if the two given ContentFormat objects have the same attributes. 331 | func EqualContentFormat(a, b interface{}) bool { 332 | fa, ok := a.(*ContentFormat) 333 | if !ok { 334 | return false 335 | } 336 | 337 | fb, ok := b.(*ContentFormat) 338 | if !ok { 339 | return false 340 | } 341 | 342 | return EqualAttrs(fa, fb) 343 | } 344 | 345 | // GetUnixTime returns the current Unix time in milliseconds. 346 | func GetUnixTime() int64 { 347 | return time.Now().UnixNano() / 1e6 348 | } 349 | 350 | // FindIndex returns the index of the first element in the given array that satisfies the given filter function. 351 | func FindIndex(a ArrayAny, filter func(e interface{}) bool) Number { 352 | for i, e := range a { 353 | if filter(e) { 354 | return i 355 | } 356 | } 357 | 358 | return -1 359 | } 360 | 361 | // The top types are mapped from y.share.get(keyname) => type. 362 | // `type` does not store any information about the `keyname`. 363 | // This function finds the correct `keyname` for `type` and throws otherwise. 364 | func FindRootTypeKey(t IAbstractType) string { 365 | // @ts-ignore _y must be defined, otherwise unexpected case 366 | for key, value := range t.GetDoc().Share { 367 | if value == t { 368 | return key 369 | } 370 | } 371 | 372 | return "" 373 | } 374 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # y-crdt 2 | A Golang implementation of the [Yjs](https://github.com/yjs/yjs) algorithms, designed to serve as a robust backend server for multi-terminal document collaboration. This implementation enhances real-time collaboration experiences across diverse user scenarios by efficiently merging updates from various terminals, extracting differential data, and supporting data archiving. 3 | 4 | In the future, we plan to develop a complete y-server service that synchronizes data with client terminals via WebSocket. Stay tuned for updates! 5 | 6 | # Compatibility test 7 | Test cases are implemented in compatibility_test.go , focusing on validating cross-version and cross-language compatibility with Yjs. 8 | Note: Encoder/decoder v2 support is pending development. 9 | 10 | **Compatibility testing passed.** 11 | 12 | ```go 13 | package y_crdt 14 | 15 | import ( 16 | "bytes" 17 | "encoding/json" 18 | "testing" 19 | 20 | "github.com/bytedance/mockey" 21 | ) 22 | 23 | func TestTextInsertDelete(t *testing.T) { 24 | // Generated via: 25 | // ```js 26 | // const doc = new Y.Doc() 27 | // const ytext = doc.getText('type') 28 | // doc..transact_mut()(function () { 29 | // ytext.insert(0, 'def') 30 | // ytext.insert(0, 'abc') 31 | // ytext.insert(6, 'ghi') 32 | // ytext.delete(2, 5) 33 | // }) 34 | // const update = Y.encodeStateAsUpdate(doc) 35 | // ytext.toString() // => 'abhi' 36 | // ``` 37 | // 38 | // This way we confirm that we can decode and apply: 39 | // 1. blocks without left/right origin consisting of multiple characters 40 | // 2. blocks with left/right origin consisting of multiple characters 41 | // 3. delete sets 42 | 43 | // construct doc by golang and check to see if the result is the same as the expected. 44 | doc := NewDoc("guid", false, nil, nil, false) 45 | ytext := doc.GetText("type") 46 | doc.Transact(func(trans *Transaction) { 47 | ytext.Insert(0, "def", nil) 48 | ytext.Insert(0, "abc", nil) 49 | ytext.Insert(6, "ghi", nil) 50 | ytext.Delete(2, 5) 51 | }, nil) 52 | 53 | if ytext.ToString() != "abhi" { 54 | t.Error("expected abhi, got ", ytext.ToString()) 55 | } 56 | t.Logf("construct by golang, ytext is %s", ytext.ToString()) 57 | 58 | // the payload was generated by javascript. 59 | var payload = []byte{ 60 | 1, 5, 152, 234, 173, 126, 0, 1, 1, 4, 116, 121, 112, 101, 3, 68, 152, 234, 173, 126, 0, 2, 61 | 97, 98, 193, 152, 234, 173, 126, 4, 152, 234, 173, 126, 0, 1, 129, 152, 234, 173, 126, 2, 62 | 1, 132, 152, 234, 173, 126, 6, 2, 104, 105, 1, 152, 234, 173, 126, 2, 0, 3, 5, 2, 63 | } 64 | 65 | // apply the update and check to see if the result is the same as the expected. 66 | doc = NewDoc("guid", false, nil, nil, false) 67 | doc.Transact(func(trans *Transaction) { 68 | ApplyUpdate(doc, payload, nil) 69 | }, nil) 70 | 71 | ytext = doc.GetText("type") 72 | if ytext.ToString() != "abhi" { 73 | t.Errorf("expected abhi, got %s", ytext.ToString()) 74 | } 75 | t.Logf("after apply update, ytext is %s", ytext.ToString()) 76 | } 77 | 78 | func TestMapSet(t *testing.T) { 79 | // Generated via: 80 | // ```js 81 | // const doc = new Y.Doc() 82 | // const x = doc.getMap('test') 83 | // x.set('k1', 'v1') 84 | // x.set('k2', 'v2') 85 | // const payload_v1 = Y.encodeStateAsUpdate(doc) 86 | // console.log(payload_v1); 87 | // const payload_v2 = Y.encodeStateAsUpdateV2(doc) 88 | // console.log(payload_v2); 89 | // ``` 90 | 91 | mocker := mockey.Mock(GenerateNewClientID).Return(440166001).Build() 92 | 93 | // construct doc by golang and check to see if the result is the same as the expected. 94 | doc := NewDoc("guid", false, nil, nil, false) 95 | x := doc.GetMap("test").(*YMap) 96 | doc.Transact(func(trans *Transaction) { 97 | x.Set("k1", "v1") 98 | x.Set("k2", "v2") 99 | }, nil) 100 | 101 | content, err := json.Marshal(x.ToJson()) 102 | if err != nil { 103 | t.Errorf("marshal x to json, err is %v", err) 104 | } 105 | 106 | t.Logf("construct by golang, x is %s", content) 107 | 108 | m := make(map[string]string) 109 | err = json.Unmarshal(content, &m) 110 | if err != nil || len(m) != 2 || m["k1"] != "v1" || m["k2"] != "v2" { 111 | t.Errorf("expected {\"k1\":\"v1\",\"k2\":\"v2\"}, got %s", content) 112 | } 113 | 114 | // the payload was generated by javascript. 115 | var payload = []byte{ 116 | 1, 2, 241, 204, 241, 209, 1, 0, 40, 1, 4, 116, 101, 115, 116, 2, 107, 49, 1, 119, 2, 118, 117 | 49, 40, 1, 4, 116, 101, 115, 116, 2, 107, 50, 1, 119, 2, 118, 50, 0, 118 | } 119 | 120 | // encode doc(geneareted by golang) and compare with payload(generated by javascript). 121 | update := EncodeStateAsUpdate(doc, nil) 122 | t.Logf("update is %v", update) 123 | if !bytes.Equal(update, payload) { 124 | t.Errorf("expect update:%v got update:%v", payload, update) 125 | } 126 | 127 | // apply the update(v1) and check to see if the result is the same as the expected. 128 | mocker.UnPatch() 129 | doc = NewDoc("guid", false, nil, nil, false) 130 | doc.Transact(func(trans *Transaction) { 131 | ApplyUpdate(doc, payload, nil) 132 | }, nil) 133 | 134 | content, err = json.Marshal(doc.GetMap("test").ToJson()) 135 | if err != nil { 136 | t.Errorf("marshal doc.GetMap(\"test\") to json, err is %v", err) 137 | } 138 | 139 | t.Logf("after apply update, x is %s", content) 140 | 141 | m = make(map[string]string) 142 | err = json.Unmarshal(content, &m) 143 | if err != nil || len(m) != 2 || m["k1"] != "v1" || m["k2"] != "v2" { 144 | t.Errorf("expected {\"k1\":\"v1\",\"k2\":\"v2\"}, got %s", content) 145 | } 146 | 147 | // decoder v2 not support yet. 148 | } 149 | 150 | func TestArrayInsert(t *testing.T) { 151 | // Generated via: 152 | // ```js 153 | // const doc = new Y.Doc() 154 | // const x = doc.getArray('test') 155 | // x.push(['a']); 156 | // x.push(['b']); 157 | // const payload_v1 = Y.encodeStateAsUpdate(doc) 158 | // console.log(payload_v1); 159 | // const payload_v2 = Y.encodeStateAsUpdateV2(doc) 160 | // console.log(payload_v2); 161 | // ``` 162 | 163 | mocker := mockey.Mock(GenerateNewClientID).Return(2525665872).Build() 164 | 165 | // construct doc by golang and check to see if the result is the same as the expected. 166 | doc := NewDoc("guid", false, nil, nil, false) 167 | x := doc.GetArray("test") 168 | x.Push([]any{"a"}) 169 | x.Push([]any{"b"}) 170 | 171 | content, err := json.Marshal(x.ToJson()) 172 | t.Logf("construct by golang, x is %s, err is %v", content, err) 173 | 174 | if !bytes.Equal(content, []byte("[\"a\",\"b\"]")) { 175 | t.Errorf("expected [\"a\",\"b\"], got %s", content) 176 | } 177 | 178 | // the payload was generated by javascript. 179 | var payload = []byte{ 180 | 1, 1, 208, 180, 170, 180, 9, 0, 8, 1, 4, 116, 101, 115, 116, 2, 119, 1, 97, 119, 1, 98, 0, 181 | } 182 | 183 | // encode doc(geneareted by golang) and compare with payload(generated by javascript). 184 | update := EncodeStateAsUpdate(doc, nil) 185 | t.Logf("update is %v", update) 186 | if !bytes.Equal(update, payload) { 187 | t.Errorf("expect update:%v got update:%v", payload, update) 188 | } 189 | 190 | // apply the update(v1) and check to see if the result is the same as the expected. 191 | mocker.UnPatch() 192 | doc = NewDoc("new doc", false, nil, nil, false) 193 | ApplyUpdate(doc, payload, nil) 194 | 195 | content, err = json.Marshal(doc.GetArray("test").ToJson()) 196 | t.Logf("after apply update, x is %s, err is %v", content, err) 197 | 198 | if !bytes.Equal(content, []byte("[\"a\",\"b\"]")) { 199 | t.Errorf("expected [\"a\",\"b\"], got %s", content) 200 | } 201 | } 202 | 203 | func TestXmlFragmentInsert(t *testing.T) { 204 | // Generated via: 205 | // ```js 206 | // const ydoc = new Y.Doc() 207 | // const yxmlFragment = ydoc.getXmlFragment('fragment-name') 208 | // const yxmlNested = new Y.XmlFragment('fragment-name') 209 | // const yxmlText = new Y.XmlText() 210 | // yxmlFragment.insert(0, [yxmlText]) 211 | // yxmlFragment.firstChild === yxmlText 212 | // yxmlFragment.insertAfter(yxmlText, [new Y.XmlElement('node-name')]) 213 | // const payload_v1 = Y.encodeStateAsUpdate(ydoc) 214 | // console.log(payload_v1); 215 | // const payload_v2 = Y.encodeStateAsUpdateV2(ydoc) 216 | // console.log(payload_v2); 217 | // ``` 218 | 219 | mockey.Mock(GenerateNewClientID).Return(2459881872).Build() 220 | 221 | // construct doc by golang and check to see if the result is the same as the expected. 222 | doc := NewDoc("guid", false, nil, nil, false) 223 | yxmlFragment := doc.GetXmlFragment("fragment-name").(*YXmlFragment) 224 | yxmlText := NewYXmlText() 225 | yxmlFragment.Insert(0, ArrayAny{yxmlText}) 226 | 227 | if yxmlFragment.GetFirstChild().(*YXmlText) != yxmlText { 228 | t.Errorf("expected yxmlFragment.GetFirstChild() is yxmlText, got %v", yxmlFragment.GetFirstChild()) 229 | } 230 | 231 | yxmlFragment.InsertAfter(yxmlText, ArrayAny{NewYXmlElement("node-name")}) 232 | update := EncodeStateAsUpdate(doc, nil) 233 | 234 | var payload = []byte{ 235 | 1, 2, 144, 163, 251, 148, 9, 0, 7, 1, 13, 102, 114, 97, 103, 109, 101, 110, 116, 45, 110, 236 | 97, 109, 101, 6, 135, 144, 163, 251, 148, 9, 0, 3, 9, 110, 111, 100, 101, 45, 110, 97, 109, 237 | 101, 0, 238 | } 239 | 240 | if !bytes.Equal(update, payload) { 241 | t.Errorf("expected update:%v got update:%v", payload, update) 242 | } 243 | } 244 | 245 | func TestStateVector(t *testing.T) { 246 | // Generated via: 247 | // ```js 248 | // const a = new Y.Doc() 249 | // const ta = a.getText('test') 250 | // ta.insert(0, 'abc') 251 | 252 | // const b = new Y.Doc() 253 | // const tb = b.getText('test') 254 | // tb.insert(0, 'de') 255 | 256 | // Y.applyUpdate(a, Y.encodeStateAsUpdate(b)) 257 | // console.log(Y.encodeStateVector(a)) 258 | // ``` 259 | 260 | var payload = []byte{2, 178, 219, 218, 44, 3, 190, 212, 225, 6, 2} 261 | sv := DecodeStateVector(payload) 262 | 263 | expected := map[Number]Number{ 264 | 14182974: 2, 265 | 93760946: 3, 266 | } 267 | 268 | for k, v := range expected { 269 | if sv[k] != v { 270 | t.Errorf("expected %v, got %v", v, sv[k]) 271 | } 272 | } 273 | 274 | serialized := EncodeStateVector(nil, sv, NewUpdateEncoderV1()) 275 | if !bytes.Equal(serialized, payload) { 276 | t.Errorf("expected: %v, got: %v", payload, serialized) 277 | } 278 | } 279 | 280 | 281 | ``` 282 | 283 | 284 | # Encoding & Decoding 285 | 286 | ## Encoding 287 | ### Basic Types 288 | - **WriteByte**: Write `uint8` to buffer. 289 | - **WriteVarUint**: Variable-length `uint64` encoding. 290 | - **WriteVarInt**: Variable-length `int` encoding with sign handling. 291 | - **WriteFloat32/64**: Big-endian float encoding. 292 | - **WriteInt64**: Big-endian `int64` encoding. 293 | 294 | ### Composite Types 295 | - **WriteString**: Write length + string data. 296 | - **WriteObject**: Write key-value count, then key-value pairs. 297 | - **WriteArray**: Write element count, then elements. 298 | - **WriteVarUint8Array/WriteUint8Array**: Write byte arrays with/without length prefix. 299 | 300 | ### Universal 301 | - **WriteAny**: Type-specific encoding with flag bytes. 302 | 303 | ## Decoding 304 | ### Basic Types 305 | - **readVarUint**: Read variable-length `uint64`. 306 | - **ReadUint8**: Read `uint8`. 307 | - **ReadVarInt**: Restore `int` from variable-length bytes. 308 | - **ReadFloat32/64**: Parse big-endian floats. 309 | - **ReadBigInt64**: Parse big-endian `int64`. 310 | 311 | ### Composite Types 312 | - **ReadString**: Read length, then string data. 313 | - **ReadObject**: Read count, then key-value pairs. 314 | - **ReadArray**: Read count, then elements. 315 | - **ReadVarUint8Array**: Read length, then byte array. 316 | 317 | ### Universal 318 | - **ReadAny**: Decode based on type flag byte. 319 | 320 | Encoding and decoding are symmetric, using variable-length and big-endian formats for efficiency. 321 | 322 | # Yjs Data Structures 323 | ## YMap 324 | - **YMap**: A key-value store with efficient updates. 325 | - **YMapItem**: Represents a key-value pair in the map. 326 | - **YMapItemMap**: Maps keys to YMapItem pointers. 327 | ## YArray 328 | - **YArray**: A dynamic array with efficient updates. 329 | - **YArrayItem**: Represents an element in the array. 330 | ## YText 331 | - **YText**: A text document with efficient updates. 332 | - **YTextItem**: Represents a character in the text. 333 | - **YTextItemMap**: Maps positions to YTextItem pointers. 334 | ## YXmlFragment 335 | - **YXmlFragment**: A fragment of an XML document. 336 | - **YXmlFragmentItem**: Represents an XML node. 337 | - **YXmlFragmentItemMap**: Maps positions to YXmlFragmentItem pointers. 338 | 339 | -------------------------------------------------------------------------------- /transaction.go: -------------------------------------------------------------------------------- 1 | package y_crdt 2 | 3 | import ( 4 | "sort" 5 | ) 6 | 7 | /* 8 | * A transaction is created for every change on the Yjs model. It is possible 9 | * to bundle changes on the Yjs model in a single transaction to 10 | * minimize the number on messages sent and the number of observer calls. 11 | * If possible the user of this library should bundle as many changes as 12 | * possible. Here is an example to illustrate the advantages of bundling: 13 | * 14 | * @example 15 | * ---------------------------------------------------------------------------- 16 | * const map = y.define('map', YMap) 17 | * // Log content when change is triggered 18 | * map.observe(() => { 19 | * console.Log('change triggered') 20 | * }) 21 | * // Each change on the map type triggers a Log message: 22 | * map.set('a', 0) // => "change triggered" 23 | * map.set('b', 0) // => "change triggered" 24 | * // When put in a transaction, it will trigger the Log after the transaction: 25 | * y.transact(() => { 26 | * map.set('a', 1) 27 | * map.set('b', 1) 28 | * }) // => "change triggered" 29 | * ---------------------------------------------------------------------------- 30 | */ 31 | 32 | type Transaction struct { 33 | // The yjs instance 34 | Doc *Doc 35 | 36 | // Describes the set of deleted items by ids 37 | DeleteSet *DeleteSet 38 | 39 | // Holds the state before the transaction started 40 | BeforeState map[Number]Number 41 | 42 | // Holds the state after the transaction 43 | AfterState map[Number]Number 44 | 45 | // All types that were directly modified (property added or child inserted/deleted). 46 | // New types are not included in this Set. Maps from type to parentSubs 47 | // (`item.parentSub = null` for YArray). 48 | Changed map[interface{}]Set 49 | 50 | // Stores the events for the types that observe also child elements. 51 | // It is mainly used by `observeDeep`. 52 | ChangedParentTypes map[interface{}][]IEventType 53 | 54 | // Stores the events for the types that observe also child elements. 55 | // It is mainly used by `observeDeep`. 56 | MergeStructs []IAbstractStruct 57 | 58 | Origin interface{} 59 | 60 | // Stores meta information on the transaction 61 | Meta map[interface{}]Set 62 | 63 | // Whether this change originates from this doc. 64 | Local bool 65 | 66 | SubdocsAdded Set 67 | SubdocsRemoved Set 68 | SubdocsLoaded Set 69 | } 70 | 71 | func NewTransaction(doc *Doc, origin interface{}, local bool) *Transaction { 72 | return &Transaction{ 73 | Doc: doc, 74 | Origin: origin, 75 | Local: local, 76 | BeforeState: GetStateVector(doc.Store), 77 | AfterState: make(map[Number]Number), 78 | Changed: make(map[interface{}]Set), 79 | DeleteSet: NewDeleteSet(), 80 | ChangedParentTypes: make(map[interface{}][]IEventType), 81 | Meta: make(map[interface{}]Set), 82 | SubdocsAdded: NewSet(), 83 | SubdocsRemoved: NewSet(), 84 | SubdocsLoaded: NewSet(), 85 | } 86 | } 87 | 88 | func WriteUpdateMessageFromTransaction(encoder *UpdateEncoderV1, trans *Transaction) bool { 89 | if len(trans.DeleteSet.Clients) == 0 && !MapAny(trans.AfterState, func(client, clock Number) bool { 90 | return trans.BeforeState[client] != clock 91 | }) { 92 | return false 93 | } 94 | 95 | SortAndMergeDeleteSet(trans.DeleteSet) 96 | WriteStructsFromTransaction(encoder, trans) 97 | WriteDeleteSet(encoder, trans.DeleteSet) 98 | return true 99 | } 100 | 101 | func NextID(trans *Transaction) ID { 102 | y := trans.Doc 103 | return GenID(y.ClientID, GetState(y.Store, y.ClientID)) 104 | } 105 | 106 | // If `type.parent` was added in current transaction, `type` technically 107 | // did not change, it was just added and we should not fire events for `type`. 108 | func AddChangedTypeToTransaction(trans *Transaction, t IAbstractType, parentSub string) { 109 | item := t.GetItem() 110 | if item == nil || (item.ID.Clock < trans.BeforeState[item.ID.Client] && !item.Deleted()) { 111 | _, exist := trans.Changed[t] 112 | if !exist { 113 | trans.Changed[t] = NewSet() 114 | } 115 | 116 | trans.Changed[t].Add(parentSub) 117 | } 118 | } 119 | 120 | func TryToMergeWithLeft(structs *[]IAbstractStruct, pos Number) { 121 | left := (*structs)[pos-1] 122 | right := (*structs)[pos] 123 | 124 | if left.Deleted() == right.Deleted() && IsSameType(left, right) { 125 | if left.MergeWith(right) { 126 | SpliceStruct(structs, pos, 1, nil) 127 | if IsItemPtr(right) { 128 | r := right.(*Item) 129 | if r.ParentSub != "" && r.Parent.(IAbstractType).GetMap()[r.ParentSub] == right { 130 | r.Parent.(IAbstractType).GetMap()[r.ParentSub] = left.(*Item) 131 | } 132 | } 133 | } 134 | } 135 | } 136 | 137 | func TryGcDeleteSet(ds *DeleteSet, store *StructStore, gcFilter func(item *Item) bool) { 138 | for client, deleteItems := range ds.Clients { 139 | structs := store.Clients[client] 140 | 141 | for di := len(deleteItems) - 1; di >= 0; di-- { 142 | deleteItem := deleteItems[di] 143 | endDeleteItemClock := deleteItem.Clock + deleteItem.Length 144 | 145 | si, _ := FindIndexSS(*structs, deleteItem.Clock) 146 | s := (*structs)[si] 147 | 148 | for si < len(*structs) && s.GetID().Clock < endDeleteItemClock { 149 | s = (*structs)[si] 150 | if deleteItem.Clock+deleteItem.Length <= s.GetID().Clock { 151 | break 152 | } 153 | 154 | if IsItemPtr(s) && s.Deleted() && !s.(*Item).Keep() && gcFilter(s.(*Item)) { 155 | s.(*Item).GC(store, false) 156 | } 157 | 158 | si++ 159 | // s = (*structs)[si] 160 | } 161 | } 162 | } 163 | } 164 | 165 | func TryMergeDeleteSet(ds *DeleteSet, store *StructStore) { 166 | // try to merge deleted / gc'd items 167 | // merge from right to left for better efficiecy and so we don't miss any merge targets 168 | for client, deleteItems := range ds.Clients { 169 | structs := store.Clients[client] 170 | for di := len(deleteItems) - 1; di >= 0; di-- { 171 | deleteItem := deleteItems[di] 172 | // start with merging the item next to the last deleted item 173 | n, _ := FindIndexSS(*structs, deleteItem.Clock+deleteItem.Length-1) 174 | mostRightIndexToCheck := Min(len(*structs)-1, 1+n) 175 | 176 | si := mostRightIndexToCheck 177 | s := (*structs)[si] 178 | for si > 0 && s.GetID().Clock >= deleteItem.Clock { 179 | TryToMergeWithLeft(structs, si) 180 | 181 | si-- 182 | s = (*structs)[si] 183 | } 184 | } 185 | } 186 | } 187 | 188 | func TryGc(ds *DeleteSet, store *StructStore, gcFilter func(item *Item) bool) { 189 | TryGcDeleteSet(ds, store, gcFilter) 190 | TryMergeDeleteSet(ds, store) 191 | } 192 | 193 | func CleanupTransactions(transactionCleanups []*Transaction, i Number) { 194 | if i < len(transactionCleanups) { 195 | trans := transactionCleanups[i] 196 | doc := trans.Doc 197 | store := doc.Store 198 | ds := trans.DeleteSet 199 | mergeStructs := trans.MergeStructs 200 | 201 | SortAndMergeDeleteSet(ds) 202 | trans.AfterState = GetStateVector(trans.Doc.Store) 203 | doc.Trans = nil 204 | doc.Emit("beforeObserverCalls", trans, doc) 205 | 206 | // An array of event callbacks. 207 | var fs []func(...interface{}) 208 | for itemType, subs := range trans.Changed { 209 | fs = append(fs, func(...interface{}) { 210 | if itemType.(IAbstractType).GetItem() == nil || !itemType.(IAbstractType).GetItem().Deleted() { 211 | itemType.(IAbstractType).CallObserver(trans, subs) 212 | } 213 | }) 214 | } 215 | 216 | fs = append(fs, func(...interface{}) { 217 | // deep observe events 218 | for t, events := range trans.ChangedParentTypes { 219 | fs = append(fs, func(i ...interface{}) { 220 | // We need to think about the possibility that the user transforms the 221 | // Y.Doc in the event. 222 | if t.(IAbstractType).GetItem() == nil || !t.(IAbstractType).GetItem().Deleted() { 223 | events = ArrayFilter(events, func(e IEventType) bool { 224 | return e.GetTarget().GetItem() == nil || !e.GetTarget().GetItem().Deleted() 225 | }) 226 | 227 | for _, e := range events { 228 | e.SetCurrentTarget(t.(IAbstractType)) 229 | } 230 | 231 | // sort events by path length so that top-level events are fired first. 232 | sort.Slice(events, func(i, j int) bool { 233 | return len(events[i].Path()) < len(events[j].Path()) 234 | }) 235 | 236 | // We don't need to check for events.length 237 | // because we know it has at least one element 238 | CallEventHandlerListeners(t.(IAbstractType).GetDEH(), events, trans) 239 | } 240 | }) 241 | } 242 | 243 | fs = append(fs, func(...interface{}) { 244 | doc.Emit("afterTransaction", trans, doc) 245 | }) 246 | }) 247 | 248 | CallAll(&fs, nil, 0) 249 | 250 | // Replace deleted items with ItemDeleted / GC. 251 | // This is where content is actually remove from the Yjs Doc. 252 | if doc.GC { 253 | TryGcDeleteSet(ds, store, doc.GCFilter) 254 | } 255 | TryMergeDeleteSet(ds, store) 256 | 257 | // on all affected store.clients props, try to merge 258 | for client, clock := range trans.AfterState { 259 | beforeClock := trans.BeforeState[client] 260 | if beforeClock != clock { 261 | structs := store.Clients[client] 262 | 263 | // we iterate from right to left so we can safely remove entries 264 | index, _ := FindIndexSS(*structs, beforeClock) 265 | firstChangePos := Max(index, 1) 266 | for i := len(*structs) - 1; i >= firstChangePos; i-- { 267 | TryToMergeWithLeft(structs, i) 268 | } 269 | } 270 | } 271 | 272 | // try to merge mergeStructs 273 | // @todo: it makes more sense to transform mergeStructs to a DS, sort it, and merge from right to left 274 | // but at the moment DS does not handle duplicates 275 | for i := 0; i < len(mergeStructs); i++ { 276 | id := mergeStructs[i].GetID() 277 | structs := store.Clients[id.Client] 278 | replacedStructPos, _ := FindIndexSS(*structs, id.Clock) 279 | if replacedStructPos+1 < len(*structs) { 280 | TryToMergeWithLeft(structs, replacedStructPos+1) 281 | } 282 | 283 | if replacedStructPos > 0 { 284 | TryToMergeWithLeft(structs, replacedStructPos) 285 | } 286 | } 287 | 288 | if !trans.Local && trans.AfterState[doc.ClientID] != trans.BeforeState[doc.ClientID] { 289 | doc.ClientID = GenerateNewClientID() 290 | Logf("[crdt] Changed the client-id because another client seems to be using it.") 291 | } 292 | 293 | // @todo Merge all the transactions into one and provide send the data as a single update message 294 | doc.Emit("afterTransactionCleanup", trans, doc) 295 | if _, exist := doc.Observers["update"]; exist { 296 | encoder := NewUpdateEncoderV1() 297 | hasContent := WriteUpdateMessageFromTransaction(encoder, trans) 298 | if hasContent { 299 | doc.Emit("update", encoder.ToUint8Array(), trans.Origin, doc, trans) 300 | } 301 | } 302 | 303 | if _, exist := doc.Observers["updateV2"]; exist { 304 | encoderV1 := NewUpdateEncoderV1() 305 | 306 | hasContent := WriteUpdateMessageFromTransaction(encoderV1, trans) 307 | if hasContent { 308 | encoderV2 := NewUpdateEncoderV2() 309 | encoderV2.RestEncoder = encoderV1.RestEncoder 310 | 311 | doc.Emit("updateV2", encoderV2.ToUint8Array(), trans.Origin, doc, trans) 312 | } 313 | } 314 | 315 | for subdoc := range trans.SubdocsAdded { 316 | doc.SubDocs.Add(subdoc) 317 | } 318 | 319 | for subdoc := range trans.SubdocsRemoved { 320 | doc.SubDocs.Delete(subdoc) 321 | } 322 | 323 | doc.Emit("subdocs", Object{ 324 | "loaded": trans.SubdocsLoaded, 325 | "added": trans.SubdocsAdded, 326 | "removed": trans.SubdocsRemoved}) 327 | 328 | for subdoc := range trans.SubdocsRemoved { 329 | subdoc.(*Doc).Destroy() 330 | } 331 | 332 | if len(transactionCleanups) <= i+1 { 333 | doc.TransCleanup = nil 334 | doc.Emit("afterAllTransactions", doc, transactionCleanups) 335 | } else { 336 | CleanupTransactions(transactionCleanups, i+1) 337 | } 338 | } 339 | } 340 | 341 | // Implements the functionality of `y.transact(()=>{..})` 342 | // 343 | // default parameters: origin = nil, local = true 344 | func Transact(doc *Doc, f func(trans *Transaction), origin interface{}, local bool) { 345 | transactionCleanups := doc.TransCleanup 346 | initialCall := false 347 | 348 | if doc.Trans == nil { 349 | initialCall = true 350 | doc.Trans = NewTransaction(doc, origin, local) 351 | transactionCleanups = append(transactionCleanups, doc.Trans) 352 | if len(transactionCleanups) == 1 { 353 | doc.Emit("beforeAllTransactions", doc) 354 | } 355 | 356 | doc.Emit("beforeTransaction", doc.Trans, doc) 357 | } 358 | 359 | f(doc.Trans) 360 | 361 | if initialCall && transactionCleanups[0] == doc.Trans { 362 | // The first transaction ended, now process observer calls. 363 | // Observer call may create new transactions for which we need to call the observers and do cleanup. 364 | // We don't want to nest these calls, so we execute these calls one after 365 | // another. 366 | // Also we need to ensure that all cleanups are called, even if the 367 | // observes throw errors. 368 | // This file is full of hacky try {} finally {} blocks to ensure that an 369 | // event can throw errors and also that the cleanup is called. 370 | CleanupTransactions(transactionCleanups, 0) 371 | } 372 | } 373 | --------------------------------------------------------------------------------