mirror of
https://github.com/hanzoai/collab.git
synced 2026-10-30 18:44:20 +00:00
0.1.x kept each document as one row of docs(doc_id, state), every message it received concatenated. The frame store never read that table, so the first start of a later image on a 0.1.x volume served every document empty. NewSQLite now moves docs into doc and frame, in one transaction, and drops it. A state that splits into whole y-websocket messages keeps its content messages (sync step 2 and update) as frames, in order: what Append stores for the same messages. A state that does not split is kept whole as one frame, which is what 0.1.x replayed. Migrated frames follow any the document already has. Each row is deleted before its frames are written, so they reuse its pages; if they still outgrow COLLAB_STORE_BYTES, the transaction rolls back, docs is left as it was, and startup fails with "migrate 0.1 docs". pkg/frame holds the y-websocket framing: Content, which the room already used to decide what to store, and Split, which the migration uses. COLLAB_SQLITE_PATH is a file path; the store sets its own pragmas. 0.1.1 took a driver DSN there, and a DSN given to this store became "file:file:..." and failed as "unable to open database file: out of memory (14)". A "file:" URI or a path with a query is now refused before anything opens, naming it. Tests seed a database with 0.1.x's own DDL and upsert: its Yjs update and step 2 come back as frames, a non-Yjs state comes back whole, a document with frames keeps them first, a migration larger than half the cap fits, one that cannot fit leaves docs byte for byte, and reopening migrates nothing twice. Co-authored-by: Hanzo Dev <[email protected]>
71 lines
2.2 KiB
Go
71 lines
2.2 KiB
Go
package frame
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"testing"
|
|
)
|
|
|
|
// Messages as y-websocket writes them, captured from yjs 13.6 and y-protocols
|
|
// 1.0: update inserts "hi" into Y.Text "t", step1 and step2 are an empty
|
|
// document's, awareness sets one client's state to {"user":"a"}.
|
|
var (
|
|
update = []byte{0, 2, 16, 1, 1, 192, 217, 245, 190, 7, 0, 4, 1, 1, 116, 2, 104, 105, 0}
|
|
step1 = []byte{0, 0, 1, 0}
|
|
step2 = []byte{0, 1, 2, 0, 0}
|
|
awareness = []byte{1, 20, 1, 192, 217, 245, 190, 7, 1, 12, 123, 34, 117, 115, 101, 114, 34, 58, 34, 97, 34, 125}
|
|
query = []byte{3}
|
|
denied = []byte{2, 0, 2, 'n', 'o'}
|
|
)
|
|
|
|
func TestSplit(t *testing.T) {
|
|
msgs := [][]byte{step1, awareness, update, step2, query, denied}
|
|
got, ok := Split(bytes.Join(msgs, nil))
|
|
if !ok || fmt.Sprint(got) != fmt.Sprint(msgs) {
|
|
t.Fatalf("Split = %v, %v; want %v", got, ok, msgs)
|
|
}
|
|
if got, ok := Split(nil); !ok || len(got) != 0 {
|
|
t.Fatalf("Split(nil) = %v, %v", got, ok)
|
|
}
|
|
}
|
|
|
|
// A length past 127 takes a second varUint byte.
|
|
func TestSplitLongLength(t *testing.T) {
|
|
body := bytes.Repeat([]byte{7}, 300)
|
|
msg := append([]byte{0, 2, 0xac, 0x02}, body...) // 300 = 0b10_0101100
|
|
got, ok := Split(append(msg, step1...))
|
|
if !ok || len(got) != 2 || !bytes.Equal(got[0], msg) || !bytes.Equal(got[1], step1) {
|
|
t.Fatalf("Split = %d messages, %v", len(got), ok)
|
|
}
|
|
}
|
|
|
|
func TestSplitRefuses(t *testing.T) {
|
|
for name, b := range map[string][]byte{
|
|
"text": []byte("update-from-A-hanzo:blue:relay1790152991638"),
|
|
"truncated": update[:len(update)-1],
|
|
"trailing": append(append([]byte{}, update...), 0),
|
|
"sync type 3": {0, 3, 0},
|
|
"auth type 1": {2, 1, 0},
|
|
"message type 4": {4},
|
|
"endless varUint": {0, 2, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x01},
|
|
} {
|
|
if got, ok := Split(b); ok || got != nil {
|
|
t.Errorf("%s: Split = %v, %v; want nil, false", name, got, ok)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestContent(t *testing.T) {
|
|
for _, c := range []struct {
|
|
msg []byte
|
|
want bool
|
|
}{
|
|
{update, true}, {step2, true}, {step1, false}, {awareness, false},
|
|
{query, false}, {denied, false}, {[]byte{0}, false}, {nil, false},
|
|
} {
|
|
if got := Content(c.msg); got != c.want {
|
|
t.Errorf("Content(%v) = %v, want %v", c.msg, got, c.want)
|
|
}
|
|
}
|
|
}
|