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]>
90 lines
2.1 KiB
Go
90 lines
2.1 KiB
Go
// Package frame reads the y-websocket message framing (y-protocols sync,
|
|
// awareness and auth, lib0 variable-length encoding).
|
|
//
|
|
// A message is a varUint message type followed by its body:
|
|
//
|
|
// 0 sync: varUint sync type (0 step1, 1 step2, 2 update), varUint8Array
|
|
// 1 awareness: varUint8Array
|
|
// 2 auth: varUint 0 (permission denied), varString reason
|
|
// 3 query awareness: no body
|
|
//
|
|
// varUint8Array and varString are a varUint length and that many bytes.
|
|
package frame
|
|
|
|
// Content reports whether msg is a sync message that carries document content:
|
|
// messageSync (0) followed by syncStep2 (1) or syncUpdate (2). Sync step 1 (a
|
|
// state vector) and awareness (presence) carry none.
|
|
func Content(msg []byte) bool {
|
|
return len(msg) >= 2 && msg[0] == 0 && (msg[1] == 1 || msg[1] == 2)
|
|
}
|
|
|
|
// Split cuts b into the whole messages it concatenates, in order. ok is false,
|
|
// and msgs nil, when b is not such a sequence: an unknown type, or a length
|
|
// running past the end.
|
|
func Split(b []byte) (msgs [][]byte, ok bool) {
|
|
for d := (decoder{b: b}); len(d.b) > 0; {
|
|
start := d.b
|
|
d.message()
|
|
if d.bad {
|
|
return nil, false
|
|
}
|
|
msgs = append(msgs, start[:len(start)-len(d.b)])
|
|
}
|
|
return msgs, true
|
|
}
|
|
|
|
type decoder struct {
|
|
b []byte
|
|
bad bool
|
|
}
|
|
|
|
// message consumes one message.
|
|
func (d *decoder) message() {
|
|
switch d.uint() {
|
|
case 0:
|
|
if d.uint() > 2 {
|
|
d.bad = true
|
|
}
|
|
d.bytes()
|
|
case 1:
|
|
d.bytes()
|
|
case 2:
|
|
if d.uint() != 0 {
|
|
d.bad = true
|
|
}
|
|
d.bytes()
|
|
case 3:
|
|
default:
|
|
d.bad = true
|
|
}
|
|
}
|
|
|
|
// uint consumes a lib0 varUint: 7 bits a byte, least significant first, the
|
|
// high bit set on every byte but the last. lib0 writes at most 53 bits.
|
|
func (d *decoder) uint() uint64 {
|
|
var v uint64
|
|
for shift := uint(0); !d.bad; shift += 7 {
|
|
if len(d.b) == 0 || shift > 49 {
|
|
d.bad = true
|
|
break
|
|
}
|
|
c := d.b[0]
|
|
d.b = d.b[1:]
|
|
v |= uint64(c&0x7f) << shift
|
|
if c < 0x80 {
|
|
return v
|
|
}
|
|
}
|
|
return 0
|
|
}
|
|
|
|
// bytes consumes a varUint length and that many bytes.
|
|
func (d *decoder) bytes() {
|
|
n := d.uint()
|
|
if d.bad || n > uint64(len(d.b)) {
|
|
d.bad = true
|
|
return
|
|
}
|
|
d.b = d.b[n:]
|
|
}
|