17 files changed,
+955,
-1
M
map.go
+1,
-0
1@@ -1,3 +1,4 @@
2 examples/load_document
3 docs/
4 ref/
5+node_modules/
+615,
-0
1@@ -0,0 +1,615 @@
2+package ygo_test
3+
4+import (
5+ "encoding/json"
6+ "os"
7+ "testing"
8+
9+ "github.com/BTBurke/ygo"
10+)
11+
12+func TestYjsCompatibility(t *testing.T) {
13+ t.Run("shared YText", func(t *testing.T) {
14+ data, err := os.ReadFile("testdata/shared-YText.yjs")
15+ if err != nil {
16+ t.Fatalf("failed to read testdata: %v", err)
17+ }
18+
19+ doc, err := ygo.NewDoc()
20+ if err != nil {
21+ t.Fatalf("failed to create doc: %v", err)
22+ }
23+ defer doc.Destroy()
24+
25+ err = doc.WithWriteTransaction(func(txn *ygo.Transaction) error {
26+ update := ygo.UpdateFromBytes(data)
27+ return txn.ApplyUpdate(update)
28+ })
29+ if err != nil {
30+ t.Fatalf("failed to apply update: %v", err)
31+ }
32+
33+ txt, err := doc.GetText("test")
34+ if err != nil {
35+ t.Fatalf("failed to get text: %v", err)
36+ }
37+ defer txt.Destroy()
38+
39+ var content string
40+ err = doc.WithReadTransaction(func(txn *ygo.Transaction) error {
41+ var err error
42+ content, err = txt.String(txn)
43+ return err
44+ })
45+ if err != nil {
46+ t.Fatalf("failed to read text: %v", err)
47+ }
48+
49+ if content != "test text" {
50+ t.Errorf("expected 'test text', got %q", content)
51+ }
52+ })
53+
54+ t.Run("map value types", func(t *testing.T) {
55+ data, err := os.ReadFile("testdata/map-value-types.yjs")
56+ if err != nil {
57+ t.Fatalf("failed to read testdata: %v", err)
58+ }
59+
60+ doc, err := ygo.NewDoc()
61+ if err != nil {
62+ t.Fatalf("failed to create doc: %v", err)
63+ }
64+ defer doc.Destroy()
65+
66+ err = doc.WithWriteTransaction(func(txn *ygo.Transaction) error {
67+ update := ygo.UpdateFromBytes(data)
68+ return txn.ApplyUpdate(update)
69+ })
70+ if err != nil {
71+ t.Fatalf("failed to apply update: %v", err)
72+ }
73+
74+ m, err := doc.GetMap("test")
75+ if err != nil {
76+ t.Fatalf("failed to get map: %v", err)
77+ }
78+ defer m.Destroy()
79+
80+ err = doc.WithReadTransaction(func(txn *ygo.Transaction) error {
81+ out, err := m.Get(txn, "string")
82+ if err != nil {
83+ t.Errorf("failed to get 'string': %v", err)
84+ } else if v, ok := out.String(); !ok || v != "value" {
85+ t.Errorf("expected string='value', got %v", v)
86+ }
87+ out.Destroy()
88+
89+ out, err = m.Get(txn, "bool")
90+ if err != nil {
91+ t.Errorf("failed to get 'bool': %v", err)
92+ } else if v, ok := out.Bool(); !ok || v != true {
93+ t.Errorf("expected bool=true, got %v", v)
94+ }
95+ out.Destroy()
96+
97+ out, err = m.Get(txn, "number")
98+ if err != nil {
99+ t.Errorf("failed to get 'number': %v", err)
100+ } else if v, ok := out.Float(); !ok || v != 1 {
101+ t.Errorf("expected number=1, got %v", v)
102+ }
103+ out.Destroy()
104+
105+ out, err = m.Get(txn, "object")
106+ if err != nil {
107+ t.Errorf("failed to get 'object': %v", err)
108+ } else {
109+ var obj map[string]interface{}
110+ if err := out.JSON(&obj); err != nil {
111+ t.Errorf("failed to parse JSON: %v", err)
112+ } else {
113+ if obj["foo"] != "bar" {
114+ t.Errorf("expected object.foo='bar', got %v", obj["foo"])
115+ }
116+ if obj["num"] != float64(1) {
117+ t.Errorf("expected object.num=1, got %v", obj["num"])
118+ }
119+ if obj["test"] != true {
120+ t.Errorf("expected object.test=true, got %v", obj["test"])
121+ }
122+ }
123+ }
124+ out.Destroy()
125+
126+ return nil
127+ })
128+ if err != nil {
129+ t.Fatalf("map verification failed: %v", err)
130+ }
131+ })
132+
133+ t.Run("shared YArray", func(t *testing.T) {
134+ data, err := os.ReadFile("testdata/shared-YArray.yjs")
135+ if err != nil {
136+ t.Fatalf("failed to read testdata: %v", err)
137+ }
138+
139+ doc, err := ygo.NewDoc()
140+ if err != nil {
141+ t.Fatalf("failed to create doc: %v", err)
142+ }
143+ defer doc.Destroy()
144+
145+ err = doc.WithWriteTransaction(func(txn *ygo.Transaction) error {
146+ update := ygo.UpdateFromBytes(data)
147+ return txn.ApplyUpdate(update)
148+ })
149+ if err != nil {
150+ t.Fatalf("failed to apply update: %v", err)
151+ }
152+
153+ arr, err := doc.GetArray("test")
154+ if err != nil {
155+ t.Fatalf("failed to get array: %v", err)
156+ }
157+ defer arr.Destroy()
158+
159+ var length uint32
160+ err = doc.WithReadTransaction(func(txn *ygo.Transaction) error {
161+ var err error
162+ length, err = arr.Len()
163+ return err
164+ })
165+ if err != nil {
166+ t.Fatalf("failed to get array length: %v", err)
167+ }
168+
169+ if length != 4 {
170+ t.Errorf("expected array length 4, got %d", length)
171+ }
172+
173+ err = doc.WithReadTransaction(func(txn *ygo.Transaction) error {
174+ out, err := arr.Get(txn, 0)
175+ if err != nil {
176+ t.Errorf("failed to get element 0: %v", err)
177+ } else if v, ok := out.String(); !ok || v != "first" {
178+ t.Errorf("expected element 0='first', got %v", v)
179+ }
180+ out.Destroy()
181+
182+ out, err = arr.Get(txn, 1)
183+ if err != nil {
184+ t.Errorf("failed to get element 1: %v", err)
185+ } else if v, ok := out.String(); !ok || v != "middle" {
186+ t.Errorf("expected element 1='middle', got %v", v)
187+ }
188+ out.Destroy()
189+
190+ out, err = arr.Get(txn, 2)
191+ if err != nil {
192+ t.Errorf("failed to get element 2: %v", err)
193+ } else if v, ok := out.String(); !ok || v != "second" {
194+ t.Errorf("expected element 2='second', got %v", v)
195+ }
196+ out.Destroy()
197+
198+ out, err = arr.Get(txn, 3)
199+ if err != nil {
200+ t.Errorf("failed to get element 3: %v", err)
201+ } else if v, ok := out.String(); !ok || v != "third" {
202+ t.Errorf("expected element 3='third', got %v", v)
203+ }
204+ out.Destroy()
205+
206+ return nil
207+ })
208+ if err != nil {
209+ t.Fatalf("array element verification failed: %v", err)
210+ }
211+ })
212+
213+ t.Run("nested XmlElement with attrs", func(t *testing.T) {
214+ data, err := os.ReadFile("testdata/nested-XmlElement-with-attrs.yjs")
215+ if err != nil {
216+ t.Fatalf("failed to read testdata: %v", err)
217+ }
218+
219+ doc, err := ygo.NewDoc()
220+ if err != nil {
221+ t.Fatalf("failed to create doc: %v", err)
222+ }
223+ defer doc.Destroy()
224+
225+ err = doc.WithWriteTransaction(func(txn *ygo.Transaction) error {
226+ update := ygo.UpdateFromBytes(data)
227+ return txn.ApplyUpdate(update)
228+ })
229+ if err != nil {
230+ t.Fatalf("failed to apply update: %v", err)
231+ }
232+
233+ frag, err := doc.GetXmlFragment("test")
234+ if err != nil {
235+ t.Fatalf("failed to get xml fragment: %v", err)
236+ }
237+ defer frag.Destroy()
238+
239+ err = doc.WithReadTransaction(func(txn *ygo.Transaction) error {
240+ jsonBytes, err := frag.MarshalJSONWithTxn(txn)
241+ if err != nil {
242+ return err
243+ }
244+
245+ var children []interface{}
246+ if err := json.Unmarshal(jsonBytes, &children); err != nil {
247+ return err
248+ }
249+
250+ if len(children) < 1 {
251+ t.Error("expected at least one child")
252+ return nil
253+ }
254+
255+ div, ok := children[0].(map[string]interface{})
256+ if !ok {
257+ t.Error("expected first child to be a map")
258+ return nil
259+ }
260+
261+ if div["type"] != "div" {
262+ t.Errorf("expected type='div', got %v", div["type"])
263+ }
264+
265+ attrs, ok := div["attrs"].(map[string]interface{})
266+ if !ok {
267+ t.Error("expected attrs to be a map")
268+ return nil
269+ }
270+ if attrs["class"] != "container" {
271+ t.Errorf("expected class='container', got %v", attrs["class"])
272+ }
273+ if attrs["data-id"] != "main" {
274+ t.Errorf("expected data-id='main', got %v", attrs["data-id"])
275+ }
276+
277+ childList, ok := div["children"].([]interface{})
278+ if !ok {
279+ t.Error("expected children to be a list")
280+ return nil
281+ }
282+ if len(childList) != 2 {
283+ t.Errorf("expected 2 children, got %d", len(childList))
284+ }
285+
286+ return nil
287+ })
288+ if err != nil {
289+ t.Fatalf("xml verification failed: %v", err)
290+ }
291+ })
292+
293+ t.Run("mixed content array", func(t *testing.T) {
294+ data, err := os.ReadFile("testdata/mixed-content-array.yjs")
295+ if err != nil {
296+ t.Fatalf("failed to read testdata: %v", err)
297+ }
298+
299+ doc, err := ygo.NewDoc()
300+ if err != nil {
301+ t.Fatalf("failed to create doc: %v", err)
302+ }
303+ defer doc.Destroy()
304+
305+ err = doc.WithWriteTransaction(func(txn *ygo.Transaction) error {
306+ update := ygo.UpdateFromBytes(data)
307+ return txn.ApplyUpdate(update)
308+ })
309+ if err != nil {
310+ t.Fatalf("failed to apply update: %v", err)
311+ }
312+
313+ arr, err := doc.GetArray("test")
314+ if err != nil {
315+ t.Fatalf("failed to get array: %v", err)
316+ }
317+ defer arr.Destroy()
318+
319+ var length uint32
320+ err = doc.WithReadTransaction(func(txn *ygo.Transaction) error {
321+ var err error
322+ length, err = arr.Len()
323+ return err
324+ })
325+ if err != nil {
326+ t.Fatalf("failed to get array length: %v", err)
327+ }
328+
329+ if length != 5 {
330+ t.Errorf("expected array length 5, got %d", length)
331+ }
332+
333+ err = doc.WithReadTransaction(func(txn *ygo.Transaction) error {
334+ out, err := arr.Get(txn, 0)
335+ if err != nil {
336+ t.Errorf("failed to get element 0: %v", err)
337+ } else if v, ok := out.String(); !ok || v != "string" {
338+ t.Errorf("expected element 0='string', got %v", v)
339+ }
340+ out.Destroy()
341+
342+ out, err = arr.Get(txn, 1)
343+ if err != nil {
344+ t.Errorf("failed to get element 1: %v", err)
345+ } else if v, ok := out.Float(); !ok || v != 42 {
346+ t.Errorf("expected element 1=42, got %v", v)
347+ }
348+ out.Destroy()
349+
350+ out, err = arr.Get(txn, 2)
351+ if err != nil {
352+ t.Errorf("failed to get element 2: %v", err)
353+ } else if v, ok := out.Bool(); !ok || v != true {
354+ t.Errorf("expected element 2=true, got %v", v)
355+ }
356+ out.Destroy()
357+
358+ out, err = arr.Get(txn, 3)
359+ if err != nil {
360+ t.Errorf("failed to get element 3: %v", err)
361+ } else if !out.IsNull() {
362+ t.Errorf("expected element 3=null")
363+ }
364+ out.Destroy()
365+
366+ out, err = arr.Get(txn, 4)
367+ if err != nil {
368+ t.Errorf("failed to get element 4: %v", err)
369+ } else {
370+ var obj map[string]interface{}
371+ if err := out.JSON(&obj); err != nil {
372+ t.Errorf("failed to parse JSON: %v", err)
373+ } else {
374+ if obj["nested"] != "object" {
375+ t.Errorf("expected element 4.nested='object', got %v", obj["nested"])
376+ }
377+ }
378+ }
379+ out.Destroy()
380+
381+ return nil
382+ })
383+ if err != nil {
384+ t.Fatalf("array element verification failed: %v", err)
385+ }
386+ })
387+
388+ t.Run("nested YMap in YMap", func(t *testing.T) {
389+ data, err := os.ReadFile("testdata/nested-YMap-in-YMap.yjs")
390+ if err != nil {
391+ t.Fatalf("failed to read testdata: %v", err)
392+ }
393+
394+ doc, err := ygo.NewDoc()
395+ if err != nil {
396+ t.Fatalf("failed to create doc: %v", err)
397+ }
398+ defer doc.Destroy()
399+
400+ err = doc.WithWriteTransaction(func(txn *ygo.Transaction) error {
401+ update := ygo.UpdateFromBytes(data)
402+ return txn.ApplyUpdate(update)
403+ })
404+ if err != nil {
405+ t.Fatalf("failed to apply update: %v", err)
406+ }
407+
408+ m, err := doc.GetMap("test")
409+ if err != nil {
410+ t.Fatalf("failed to get map: %v", err)
411+ }
412+ defer m.Destroy()
413+
414+ err = doc.WithReadTransaction(func(txn *ygo.Transaction) error {
415+ out, err := m.Get(txn, "child")
416+ if err != nil {
417+ t.Fatalf("failed to get 'child': %v", err)
418+ }
419+ defer out.Destroy()
420+
421+ if out.Tag() != ygo.TagMap {
422+ t.Fatalf("expected TagMap, got %v", out.Tag())
423+ }
424+
425+ childMap, err := m.GetNestedMap(txn, "child")
426+ if err != nil {
427+ t.Fatalf("failed to get nested map: %v", err)
428+ }
429+ defer childMap.Destroy()
430+
431+ childOut, err := childMap.Get(txn, "key1")
432+ if err != nil {
433+ t.Fatalf("failed to get nested key1: %v", err)
434+ }
435+ defer childOut.Destroy()
436+
437+ val, ok := childOut.String()
438+ if !ok || val != "value1" {
439+ t.Errorf("expected 'value1', got %v", val)
440+ }
441+
442+ childOut, err = childMap.Get(txn, "key2")
443+ if err != nil {
444+ t.Fatalf("failed to get nested key2: %v", err)
445+ }
446+ defer childOut.Destroy()
447+
448+ num, ok := childOut.Float()
449+ if !ok || num != 42 {
450+ t.Errorf("expected 42, got %v", num)
451+ }
452+
453+ return nil
454+ })
455+ if err != nil {
456+ t.Fatalf("nested map test failed: %v", err)
457+ }
458+ })
459+
460+ t.Run("nested YArray in YMap", func(t *testing.T) {
461+ data, err := os.ReadFile("testdata/nested-YArray-in-YMap.yjs")
462+ if err != nil {
463+ t.Fatalf("failed to read testdata: %v", err)
464+ }
465+
466+ doc, err := ygo.NewDoc()
467+ if err != nil {
468+ t.Fatalf("failed to create doc: %v", err)
469+ }
470+ defer doc.Destroy()
471+
472+ err = doc.WithWriteTransaction(func(txn *ygo.Transaction) error {
473+ update := ygo.UpdateFromBytes(data)
474+ return txn.ApplyUpdate(update)
475+ })
476+ if err != nil {
477+ t.Fatalf("failed to apply update: %v", err)
478+ }
479+
480+ m, err := doc.GetMap("test")
481+ if err != nil {
482+ t.Fatalf("failed to get map: %v", err)
483+ }
484+ defer m.Destroy()
485+
486+ err = doc.WithReadTransaction(func(txn *ygo.Transaction) error {
487+ arr, err := m.GetNestedArray(txn, "arr")
488+ if err != nil {
489+ t.Fatalf("failed to get nested array: %v", err)
490+ }
491+ defer arr.Destroy()
492+
493+ length, err := arr.Len()
494+ if err != nil {
495+ t.Fatalf("failed to get array length: %v", err)
496+ }
497+ if length != 3 {
498+ t.Errorf("expected length 3, got %d", length)
499+ }
500+
501+ out, err := arr.Get(txn, 0)
502+ if err != nil {
503+ t.Fatalf("failed to get element 0: %v", err)
504+ }
505+ defer out.Destroy()
506+
507+ val, ok := out.String()
508+ if !ok || val != "a" {
509+ t.Errorf("expected 'a', got %v", val)
510+ }
511+
512+ out, err = arr.Get(txn, 2)
513+ if err != nil {
514+ t.Fatalf("failed to get element 2: %v", err)
515+ }
516+ defer out.Destroy()
517+
518+ val, ok = out.String()
519+ if !ok || val != "c" {
520+ t.Errorf("expected 'c', got %v", val)
521+ }
522+
523+ return nil
524+ })
525+ if err != nil {
526+ t.Fatalf("nested array test failed: %v", err)
527+ }
528+ })
529+
530+ t.Run("nested YMap in YArray", func(t *testing.T) {
531+ data, err := os.ReadFile("testdata/nested-YMap-in-YArray.yjs")
532+ if err != nil {
533+ t.Fatalf("failed to read testdata: %v", err)
534+ }
535+
536+ doc, err := ygo.NewDoc()
537+ if err != nil {
538+ t.Fatalf("failed to create doc: %v", err)
539+ }
540+ defer doc.Destroy()
541+
542+ err = doc.WithWriteTransaction(func(txn *ygo.Transaction) error {
543+ update := ygo.UpdateFromBytes(data)
544+ return txn.ApplyUpdate(update)
545+ })
546+ if err != nil {
547+ t.Fatalf("failed to apply update: %v", err)
548+ }
549+
550+ arr, err := doc.GetArray("test")
551+ if err != nil {
552+ t.Fatalf("failed to get array: %v", err)
553+ }
554+ defer arr.Destroy()
555+
556+ err = doc.WithReadTransaction(func(txn *ygo.Transaction) error {
557+ length, err := arr.Len()
558+ if err != nil {
559+ t.Fatalf("failed to get array length: %v", err)
560+ }
561+ if length != 2 {
562+ t.Errorf("expected length 2, got %d", length)
563+ }
564+
565+ itemOut, err := arr.Get(txn, 0)
566+ if err != nil {
567+ t.Fatalf("failed to get element 0: %v", err)
568+ }
569+ defer itemOut.Destroy()
570+
571+ childMap, err := itemOut.ToMap()
572+ if err != nil {
573+ t.Fatalf("failed to convert to map: %v", err)
574+ }
575+ defer childMap.Destroy()
576+
577+ nameOut, err := childMap.Get(txn, "name")
578+ if err != nil {
579+ t.Fatalf("failed to get nested 'name': %v", err)
580+ }
581+ defer nameOut.Destroy()
582+
583+ val, ok := nameOut.String()
584+ if !ok || val != "first" {
585+ t.Errorf("expected 'first', got %v", val)
586+ }
587+
588+ itemOut, err = arr.Get(txn, 1)
589+ if err != nil {
590+ t.Fatalf("failed to get element 1: %v", err)
591+ }
592+ defer itemOut.Destroy()
593+
594+ childMap, err = itemOut.ToMap()
595+ if err != nil {
596+ t.Fatalf("failed to convert to map: %v", err)
597+ }
598+
599+ nameOut, err = childMap.Get(txn, "name")
600+ if err != nil {
601+ t.Fatalf("failed to get nested 'name' from element 1: %v", err)
602+ }
603+ defer nameOut.Destroy()
604+
605+ val, ok = nameOut.String()
606+ if !ok || val != "second" {
607+ t.Errorf("expected 'second', got %v", val)
608+ }
609+
610+ return nil
611+ })
612+ if err != nil {
613+ t.Fatalf("nested map in array test failed: %v", err)
614+ }
615+ })
616+}
+96,
-0
1@@ -0,0 +1,96 @@
2+import * as Y from "yjs";
3+import fs from "node:fs";
4+
5+const writeTestData = (fname, f) => {
6+ const name = "testdata/" + fname.replace(/\s/g, "-") + ".yjs";
7+ console.log(`writing test ${name}`);
8+
9+ const doc = f();
10+ const content = Y.encodeStateAsUpdate(doc);
11+ doc.destroy();
12+
13+ fs.writeFileSync(name, content);
14+};
15+
16+writeTestData("shared YText", () => {
17+ const doc = new Y.Doc();
18+ const yt = doc.getText("test");
19+ yt.insert(0, "test text");
20+ return doc;
21+});
22+
23+writeTestData("map value types", () => {
24+ const doc = new Y.Doc();
25+ const ym = doc.getMap("test");
26+ ym.set("string", "value");
27+ ym.set("bool", true);
28+ ym.set("number", 1);
29+ ym.set("object", { foo: "bar", num: 1, test: true });
30+ return doc;
31+});
32+
33+writeTestData("shared YArray", () => {
34+ const doc = new Y.Doc();
35+ const ya = doc.getArray("test");
36+ ya.push(["first", "second", "third"]);
37+ ya.insert(1, ["middle"]);
38+ return doc;
39+});
40+
41+writeTestData("nested XmlElement with attrs", () => {
42+ const doc = new Y.Doc();
43+ const frag = doc.getXmlFragment("test");
44+
45+ const div = new Y.XmlElement("div");
46+ div.setAttribute("class", "container");
47+ div.setAttribute("data-id", "main");
48+
49+ const p = new Y.XmlElement("p");
50+ p.setAttribute("style", "color:red");
51+ p.insert(0, [new Y.XmlText("Hello")]);
52+
53+ div.insert(0, [p]);
54+ div.insert(1, [new Y.XmlText("World")]);
55+
56+ frag.insert(0, [div]);
57+ return doc;
58+});
59+
60+writeTestData("mixed content array", () => {
61+ const doc = new Y.Doc();
62+ const ya = doc.getArray("test");
63+ ya.push(["string", 42, true, null, { nested: "object" }]);
64+ return doc;
65+});
66+
67+writeTestData("nested YMap in YMap", () => {
68+ const doc = new Y.Doc();
69+ const parent = doc.getMap("test");
70+ const child = new Y.Map();
71+ child.set("key1", "value1");
72+ child.set("key2", 42);
73+ parent.set("child", child);
74+ return doc;
75+});
76+
77+writeTestData("nested YArray in YMap", () => {
78+ const doc = new Y.Doc();
79+ const parent = doc.getMap("test");
80+ const child = new Y.Array();
81+ child.push(["a", "b", "c"]);
82+ parent.set("arr", child);
83+ return doc;
84+});
85+
86+writeTestData("nested YMap in YArray", () => {
87+ const doc = new Y.Doc();
88+ const arr = doc.getArray("test");
89+ const item1 = new Y.Map();
90+ item1.set("name", "first");
91+ const item2 = new Y.Map();
92+ item2.set("name", "second");
93+ arr.push([item1, item2]);
94+ return doc;
95+});
96+
97+console.log("All test files written successfully");
+3,
-1
1@@ -9,7 +9,9 @@
2 "ripgrep@latest",
3 "yarn@latest",
4 "rustup@latest",
5- "rustc@latest"
6+ "rustc@latest",
7+ "nodejs@latest",
8+ "corepack@latest"
9 ],
10 "shell": {
11 "init_hook": [
+97,
-0
1@@ -173,6 +173,54 @@
2 }
3 }
4 },
5+ "corepack@latest": {
6+ "last_modified": "2026-03-21T07:29:51Z",
7+ "resolved": "github:NixOS/nixpkgs/09061f748ee21f68a089cd5d91ec1859cd93d0be#corepack",
8+ "source": "devbox-search",
9+ "version": "0.34.6",
10+ "systems": {
11+ "aarch64-darwin": {
12+ "outputs": [
13+ {
14+ "name": "out",
15+ "path": "/nix/store/b81ff3525q050yr2n31raaij9l86a70z-corepack-0.34.6",
16+ "default": true
17+ }
18+ ],
19+ "store_path": "/nix/store/b81ff3525q050yr2n31raaij9l86a70z-corepack-0.34.6"
20+ },
21+ "aarch64-linux": {
22+ "outputs": [
23+ {
24+ "name": "out",
25+ "path": "/nix/store/krd99cx9wh0pflf43ghi8yy5bzaqrks6-corepack-0.34.6",
26+ "default": true
27+ }
28+ ],
29+ "store_path": "/nix/store/krd99cx9wh0pflf43ghi8yy5bzaqrks6-corepack-0.34.6"
30+ },
31+ "x86_64-darwin": {
32+ "outputs": [
33+ {
34+ "name": "out",
35+ "path": "/nix/store/p056js9jzlnq07gpl728fkvw036qdmlz-corepack-0.34.6",
36+ "default": true
37+ }
38+ ],
39+ "store_path": "/nix/store/p056js9jzlnq07gpl728fkvw036qdmlz-corepack-0.34.6"
40+ },
41+ "x86_64-linux": {
42+ "outputs": [
43+ {
44+ "name": "out",
45+ "path": "/nix/store/pvgjai36liywyb5gyxnxnx5s2fsf7cwz-corepack-0.34.6",
46+ "default": true
47+ }
48+ ],
49+ "store_path": "/nix/store/pvgjai36liywyb5gyxnxnx5s2fsf7cwz-corepack-0.34.6"
50+ }
51+ }
52+ },
53 "coreutils@latest": {
54 "last_modified": "2026-03-21T07:29:51Z",
55 "resolved": "github:NixOS/nixpkgs/09061f748ee21f68a089cd5d91ec1859cd93d0be#coreutils",
56@@ -405,6 +453,55 @@
57 }
58 }
59 },
60+ "nodejs@latest": {
61+ "last_modified": "2026-03-27T11:17:38Z",
62+ "plugin_version": "0.0.2",
63+ "resolved": "github:NixOS/nixpkgs/832efc09b4caf6b4569fbf9dc01bec3082a00611#nodejs_25",
64+ "source": "devbox-search",
65+ "version": "25.8.2",
66+ "systems": {
67+ "aarch64-darwin": {
68+ "outputs": [
69+ {
70+ "name": "out",
71+ "path": "/nix/store/qflz0c97921bn5i3jc49dy24hhwpk67d-nodejs-25.8.2",
72+ "default": true
73+ }
74+ ],
75+ "store_path": "/nix/store/qflz0c97921bn5i3jc49dy24hhwpk67d-nodejs-25.8.2"
76+ },
77+ "aarch64-linux": {
78+ "outputs": [
79+ {
80+ "name": "out",
81+ "path": "/nix/store/vdakqgvs2qjrdsrjz3wpwn39yvj0fmvv-nodejs-25.8.2",
82+ "default": true
83+ }
84+ ],
85+ "store_path": "/nix/store/vdakqgvs2qjrdsrjz3wpwn39yvj0fmvv-nodejs-25.8.2"
86+ },
87+ "x86_64-darwin": {
88+ "outputs": [
89+ {
90+ "name": "out",
91+ "path": "/nix/store/d94jkiwkdf3qm25pchchdx37sh4fy4g7-nodejs-25.8.2",
92+ "default": true
93+ }
94+ ],
95+ "store_path": "/nix/store/d94jkiwkdf3qm25pchchdx37sh4fy4g7-nodejs-25.8.2"
96+ },
97+ "x86_64-linux": {
98+ "outputs": [
99+ {
100+ "name": "out",
101+ "path": "/nix/store/qads515hyg9b6vs0l1cyxhggvc98ycdx-nodejs-25.8.2",
102+ "default": true
103+ }
104+ ],
105+ "store_path": "/nix/store/qads515hyg9b6vs0l1cyxhggvc98ycdx-nodejs-25.8.2"
106+ }
107+ }
108+ },
109 "ripgrep@latest": {
110 "last_modified": "2026-03-21T07:29:51Z",
111 "resolved": "github:NixOS/nixpkgs/09061f748ee21f68a089cd5d91ec1859cd93d0be#ripgrep",
M
map.go
+42,
-0
1@@ -124,6 +124,48 @@ func (m *Map) Branch() unsafe.Pointer {
2 return unsafe.Pointer(m.branch)
3 }
4
5+// GetNestedMap retrieves a nested Y.Map value by key.
6+// Returns error if the key doesn't exist or the value is not a Y.Map.
7+func (m *Map) GetNestedMap(txn *Transaction, key string) (*Map, error) {
8+ out, err := m.Get(txn, key)
9+ if err != nil {
10+ return nil, err
11+ }
12+ defer out.Destroy()
13+
14+ if out.Tag() != TagMap {
15+ return nil, fmt.Errorf("value at key %q is not a Y.Map (got %v)", key, out.Tag())
16+ }
17+
18+ branch := C.youtput_read_ymap(out.ptr)
19+ if branch == nil {
20+ return nil, fmt.Errorf("failed to read nested ymap")
21+ }
22+
23+ return &Map{branch: branch}, nil
24+}
25+
26+// GetNestedArray retrieves a nested Y.Array value by key.
27+// Returns error if the key doesn't exist or the value is not a Y.Array.
28+func (m *Map) GetNestedArray(txn *Transaction, key string) (*Array, error) {
29+ out, err := m.Get(txn, key)
30+ if err != nil {
31+ return nil, err
32+ }
33+ defer out.Destroy()
34+
35+ if out.Tag() != TagArray {
36+ return nil, fmt.Errorf("value at key %q is not a Y.Array (got %v)", key, out.Tag())
37+ }
38+
39+ branch := C.youtput_read_yarray(out.ptr)
40+ if branch == nil {
41+ return nil, fmt.Errorf("failed to read nested yarray")
42+ }
43+
44+ return &Array{branch: branch}, nil
45+}
46+
47 // MapEntry represents a key-value pair from a map.
48 type MapEntry struct {
49 Key string
+45,
-0
1@@ -60,6 +60,51 @@ func (o *Output) Destroy() {
2 }
3 }
4
5+// Branch returns the underlying branch pointer for shared types.
6+// Only valid for outputs with TagArray, TagMap, TagText, TagXmlElem, TagXmlText, or TagXmlFrag.
7+// Returns nil for non-shared types.
8+func (o *Output) Branch() unsafe.Pointer {
9+ if o.ptr == nil {
10+ return nil
11+ }
12+ switch o.Tag() {
13+ case TagArray:
14+ branch := C.youtput_read_yarray(o.ptr)
15+ return unsafe.Pointer(branch)
16+ case TagMap:
17+ branch := C.youtput_read_ymap(o.ptr)
18+ return unsafe.Pointer(branch)
19+ default:
20+ return nil
21+ }
22+}
23+
24+// ToMap converts the output to a Map.
25+// Returns error if the output is not a Y.Map.
26+func (o *Output) ToMap() (*Map, error) {
27+ if o.Tag() != TagMap {
28+ return nil, fmt.Errorf("output is not a Y.Map (got %v)", o.Tag())
29+ }
30+ branch := C.youtput_read_ymap(o.ptr)
31+ if branch == nil {
32+ return nil, fmt.Errorf("failed to read ymap from output")
33+ }
34+ return &Map{branch: branch}, nil
35+}
36+
37+// ToArray converts the output to an Array.
38+// Returns error if the output is not a Y.Array.
39+func (o *Output) ToArray() (*Array, error) {
40+ if o.Tag() != TagArray {
41+ return nil, fmt.Errorf("output is not a Y.Array (got %v)", o.Tag())
42+ }
43+ branch := C.youtput_read_yarray(o.ptr)
44+ if branch == nil {
45+ return nil, fmt.Errorf("failed to read yarray from output")
46+ }
47+ return &Array{branch: branch}, nil
48+}
49+
50 // IsNull returns true if the value is null.
51 func (o *Output) IsNull() bool {
52 return o.Tag() == TagJSONNull
+17,
-0
1@@ -0,0 +1,17 @@
2+{
3+ "name": "ygo",
4+ "version": "1.0.0",
5+ "description": "",
6+ "main": "index.js",
7+ "type": "module",
8+ "scripts": {
9+ "test": "echo \"Error: no test specified\" && exit 1"
10+ },
11+ "keywords": [],
12+ "author": "",
13+ "license": "ISC",
14+ "packageManager": "[email protected]+sha512.10568bb4a6afb58c9eb3630da90cc9516417abebd3fabbe6739f0ae795728da1491e9db5a544c76ad8eb7570f5c4bb3d6c637b2cb41bfdcdb47fa823c8649319",
15+ "devDependencies": {
16+ "yjs": "^13.6.30"
17+ }
18+}
+39,
-0
1@@ -0,0 +1,39 @@
2+lockfileVersion: '9.0'
3+
4+settings:
5+ autoInstallPeers: true
6+ excludeLinksFromLockfile: false
7+
8+importers:
9+
10+ .:
11+ devDependencies:
12+ yjs:
13+ specifier: ^13.6.30
14+ version: 13.6.30
15+
16+packages:
17+
18+ [email protected]:
19+ resolution: {integrity: sha512-PIeMbHqMt4DnUP3MA/Flc0HElYjMXArsw1qwJZcm9sqR8mq3l8NYizFMty0pWwE/tzIGH3EKK5+jes5mAr85yw==}
20+
21+ [email protected]:
22+ resolution: {integrity: sha512-DeXj9X5xDCjgKLU/7RR+/HQEVzuuEUiwldwOGsHK/sfAfELGWEyTcf0x+uOvCvK3O2zPmZePXWL85vtia6GyZw==}
23+ engines: {node: '>=16'}
24+ hasBin: true
25+
26+ [email protected]:
27+ resolution: {integrity: sha512-vv/9h42eCMC81ZHDFswuu/MKzkl/vyq1BhaNGfHyOonwlG4CJbQF4oiBBJPvfdeCt/PlVDWh7Nov9D34YY09uQ==}
28+ engines: {node: '>=16.0.0', npm: '>=8.0.0'}
29+
30+snapshots:
31+
32+ [email protected]: {}
33+
34+ [email protected]:
35+ dependencies:
36+ isomorphic.js: 0.2.5
37+
38+ [email protected]:
39+ dependencies:
40+ lib0: 0.2.117
+0,
-0
+0,
-0
+0,
-0
+0,
-0
+0,
-0