@kyneta/yjs-schema 1.2.0 → 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/dist/index.d.ts +82 -170
- package/dist/index.js +176 -260
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
- package/src/__tests__/bind-constraints.test.ts +5 -13
- package/src/__tests__/bind-yjs.test.ts +57 -46
- package/src/__tests__/create.test.ts +80 -56
- package/src/__tests__/reader.test.ts +3 -14
- package/src/__tests__/record-text-spike.test.ts +38 -36
- package/src/__tests__/substrate.test.ts +47 -40
- package/src/bind-yjs.ts +9 -40
- package/src/change-mapping.ts +7 -2
- package/src/index.ts +24 -26
- package/src/native-map.ts +37 -0
- package/src/populate.ts +1 -1
- package/src/substrate.ts +19 -4
- package/src/version.ts +14 -67
- package/src/yjs-resolve.ts +1 -1
- package/src/create.ts +0 -177
- package/src/sync.ts +0 -107
|
@@ -1,8 +1,20 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
change,
|
|
3
|
+
createRef,
|
|
4
|
+
exportEntirety,
|
|
5
|
+
RawPath,
|
|
6
|
+
Schema,
|
|
7
|
+
unwrap,
|
|
8
|
+
} from "@kyneta/schema"
|
|
2
9
|
import { describe, expect, it } from "vitest"
|
|
3
10
|
import * as Y from "yjs"
|
|
4
11
|
import { yjs } from "../bind-yjs.js"
|
|
5
|
-
|
|
12
|
+
|
|
13
|
+
// ===========================================================================
|
|
14
|
+
// Helper — createDoc using the generic API
|
|
15
|
+
// ===========================================================================
|
|
16
|
+
|
|
17
|
+
import { createDoc } from "@kyneta/schema"
|
|
6
18
|
|
|
7
19
|
// ===========================================================================
|
|
8
20
|
// Schemas used across tests
|
|
@@ -83,8 +95,7 @@ describe("yjs.bind", () => {
|
|
|
83
95
|
d.title.insert(0, "Snap")
|
|
84
96
|
d.count.set(42)
|
|
85
97
|
})
|
|
86
|
-
const
|
|
87
|
-
const snapshot = substrate1.exportEntirety()
|
|
98
|
+
const snapshot = exportEntirety(doc1)
|
|
88
99
|
|
|
89
100
|
// Restore
|
|
90
101
|
const substrate2 = factory.fromEntirety(snapshot, SimpleSchema)
|
|
@@ -117,8 +128,12 @@ describe("yjs.bind", () => {
|
|
|
117
128
|
const _s2 = factory.create(SimpleSchema)
|
|
118
129
|
|
|
119
130
|
// Both docs should have the same clientID
|
|
120
|
-
const doc1 =
|
|
121
|
-
|
|
131
|
+
const doc1 = unwrap(
|
|
132
|
+
createYjsDocFromFactory(factory, SimpleSchema),
|
|
133
|
+
) as Y.Doc
|
|
134
|
+
const doc2 = unwrap(
|
|
135
|
+
createYjsDocFromFactory(factory, SimpleSchema),
|
|
136
|
+
) as Y.Doc
|
|
122
137
|
|
|
123
138
|
expect(doc1.clientID).toBe(doc2.clientID)
|
|
124
139
|
})
|
|
@@ -128,8 +143,12 @@ describe("yjs.bind", () => {
|
|
|
128
143
|
const factory1 = bound.factory({ peerId: "peer-alpha" })
|
|
129
144
|
const factory2 = bound.factory({ peerId: "peer-beta" })
|
|
130
145
|
|
|
131
|
-
const doc1 =
|
|
132
|
-
|
|
146
|
+
const doc1 = unwrap(
|
|
147
|
+
createYjsDocFromFactory(factory1, SimpleSchema),
|
|
148
|
+
) as Y.Doc
|
|
149
|
+
const doc2 = unwrap(
|
|
150
|
+
createYjsDocFromFactory(factory2, SimpleSchema),
|
|
151
|
+
) as Y.Doc
|
|
133
152
|
|
|
134
153
|
expect(doc1.clientID).not.toBe(doc2.clientID)
|
|
135
154
|
})
|
|
@@ -137,7 +156,9 @@ describe("yjs.bind", () => {
|
|
|
137
156
|
it("clientID is a valid uint32", () => {
|
|
138
157
|
const bound = yjs.bind(SimpleSchema)
|
|
139
158
|
const factory = bound.factory({ peerId: "test-peer-id-12345" })
|
|
140
|
-
const doc =
|
|
159
|
+
const doc = unwrap(
|
|
160
|
+
createYjsDocFromFactory(factory, SimpleSchema),
|
|
161
|
+
) as Y.Doc
|
|
141
162
|
|
|
142
163
|
expect(typeof doc.clientID).toBe("number")
|
|
143
164
|
expect(doc.clientID).toBeGreaterThanOrEqual(0)
|
|
@@ -151,61 +172,70 @@ describe("yjs.bind", () => {
|
|
|
151
172
|
// Simulate two separate "sessions" — both should hash to the same value
|
|
152
173
|
const bound1 = yjs.bind(SimpleSchema)
|
|
153
174
|
const factory1 = bound1.factory({ peerId })
|
|
154
|
-
const doc1 =
|
|
175
|
+
const doc1 = unwrap(
|
|
176
|
+
createYjsDocFromFactory(factory1, SimpleSchema),
|
|
177
|
+
) as Y.Doc
|
|
155
178
|
|
|
156
179
|
const bound2 = yjs.bind(SimpleSchema)
|
|
157
180
|
const factory2 = bound2.factory({ peerId })
|
|
158
|
-
const doc2 =
|
|
181
|
+
const doc2 = unwrap(
|
|
182
|
+
createYjsDocFromFactory(factory2, SimpleSchema),
|
|
183
|
+
) as Y.Doc
|
|
159
184
|
|
|
160
185
|
expect(doc1.clientID).toBe(doc2.clientID)
|
|
161
186
|
})
|
|
162
187
|
})
|
|
163
188
|
|
|
164
189
|
// -------------------------------------------------------------------------
|
|
165
|
-
//
|
|
190
|
+
// unwrap() escape hatch
|
|
166
191
|
// -------------------------------------------------------------------------
|
|
167
192
|
|
|
168
|
-
describe("
|
|
169
|
-
it("returns the underlying Y.Doc from a
|
|
170
|
-
const doc =
|
|
193
|
+
describe("unwrap() escape hatch", () => {
|
|
194
|
+
it("returns the underlying Y.Doc from a createDoc ref", () => {
|
|
195
|
+
const doc = createDoc(yjs.bind(SimpleSchema))
|
|
171
196
|
change(doc, (d: any) => {
|
|
172
197
|
d.title.insert(0, "Escape")
|
|
173
198
|
d.count.set(0)
|
|
174
199
|
})
|
|
175
|
-
const yjsDoc =
|
|
200
|
+
const yjsDoc = unwrap(doc) as Y.Doc
|
|
176
201
|
|
|
177
202
|
expect(yjsDoc).toBeInstanceOf(Y.Doc)
|
|
178
203
|
expect(yjsDoc.getMap("root").get("count")).toBe(0)
|
|
179
204
|
})
|
|
180
205
|
|
|
181
206
|
it("returns a Y.Doc with the correct root map state", () => {
|
|
182
|
-
const doc =
|
|
207
|
+
const doc = createDoc(yjs.bind(SimpleSchema))
|
|
183
208
|
change(doc, (d: any) => {
|
|
184
209
|
d.title.insert(0, "Hello")
|
|
185
210
|
d.count.set(42)
|
|
186
211
|
})
|
|
187
|
-
const yjsDoc =
|
|
212
|
+
const yjsDoc = unwrap(doc) as Y.Doc
|
|
188
213
|
const rootMap = yjsDoc.getMap("root")
|
|
189
214
|
|
|
190
215
|
expect((rootMap.get("title") as Y.Text).toJSON()).toBe("Hello")
|
|
191
216
|
expect(rootMap.get("count")).toBe(42)
|
|
192
217
|
})
|
|
193
218
|
|
|
194
|
-
it("
|
|
195
|
-
expect(
|
|
219
|
+
it("returns undefined for non-refs (plain object)", () => {
|
|
220
|
+
expect(unwrap({} as any)).toBeUndefined()
|
|
196
221
|
})
|
|
197
222
|
|
|
198
|
-
it("
|
|
223
|
+
it("returns undefined for non-refs (random object with properties)", () => {
|
|
199
224
|
const fake = {
|
|
200
225
|
title: () => "fake",
|
|
201
226
|
count: () => 0,
|
|
202
227
|
}
|
|
203
|
-
expect((
|
|
228
|
+
expect(unwrap(fake as any)).toBeUndefined()
|
|
229
|
+
})
|
|
230
|
+
|
|
231
|
+
it("throws for primitives", () => {
|
|
232
|
+
expect(() => unwrap(null as any)).toThrow("unwrap() requires a ref")
|
|
233
|
+
expect(() => unwrap(undefined as any)).toThrow("unwrap() requires a ref")
|
|
204
234
|
})
|
|
205
235
|
|
|
206
236
|
it("mutations through escape hatch are visible via kyneta ref", () => {
|
|
207
|
-
const doc =
|
|
208
|
-
const yjsDoc =
|
|
237
|
+
const doc = createDoc(yjs.bind(SimpleSchema))
|
|
238
|
+
const yjsDoc = unwrap(doc) as Y.Doc
|
|
209
239
|
|
|
210
240
|
// Mutate via raw Yjs
|
|
211
241
|
yjsDoc.getMap("root").set("count", 99)
|
|
@@ -213,11 +243,11 @@ describe("yjs.bind", () => {
|
|
|
213
243
|
})
|
|
214
244
|
|
|
215
245
|
it("text mutations through escape hatch are visible", () => {
|
|
216
|
-
const doc =
|
|
246
|
+
const doc = createDoc(yjs.bind(SimpleSchema))
|
|
217
247
|
change(doc, (d: any) => {
|
|
218
248
|
d.title.insert(0, "Hello")
|
|
219
249
|
})
|
|
220
|
-
const yjsDoc =
|
|
250
|
+
const yjsDoc = unwrap(doc) as Y.Doc
|
|
221
251
|
|
|
222
252
|
const text = yjsDoc.getMap("root").get("title") as Y.Text
|
|
223
253
|
text.insert(5, " World")
|
|
@@ -231,14 +261,6 @@ describe("yjs.bind", () => {
|
|
|
231
261
|
// ===========================================================================
|
|
232
262
|
|
|
233
263
|
import type { Schema as SchemaType, SubstrateFactory } from "@kyneta/schema"
|
|
234
|
-
import {
|
|
235
|
-
interpret,
|
|
236
|
-
observation,
|
|
237
|
-
readable,
|
|
238
|
-
registerSubstrate,
|
|
239
|
-
unwrap,
|
|
240
|
-
writable,
|
|
241
|
-
} from "@kyneta/schema"
|
|
242
264
|
|
|
243
265
|
/**
|
|
244
266
|
* Helper to create a kyneta ref from a factory (mimicking what exchange.get does).
|
|
@@ -249,16 +271,5 @@ function createYjsDocFromFactory(
|
|
|
249
271
|
schema: SchemaType,
|
|
250
272
|
): any {
|
|
251
273
|
const substrate = factory.create(schema)
|
|
252
|
-
|
|
253
|
-
.with(readable)
|
|
254
|
-
.with(writable)
|
|
255
|
-
.with(observation)
|
|
256
|
-
.done()
|
|
257
|
-
|
|
258
|
-
// Register for escape hatch — createYjsSubstrate already registered
|
|
259
|
-
// substrate → Y.Doc internally, but we also need ref → substrate
|
|
260
|
-
// for unwrap() (used by the yjs() escape hatch).
|
|
261
|
-
registerSubstrate(doc, substrate)
|
|
262
|
-
|
|
263
|
-
return doc
|
|
274
|
+
return createRef(schema, substrate)
|
|
264
275
|
}
|
|
@@ -1,10 +1,17 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
change,
|
|
3
|
+
createDoc,
|
|
4
|
+
createRef,
|
|
5
|
+
Schema,
|
|
6
|
+
subscribe,
|
|
7
|
+
unwrap,
|
|
8
|
+
} from "@kyneta/schema"
|
|
2
9
|
import { describe, expect, it } from "vitest"
|
|
3
10
|
import * as Y from "yjs"
|
|
4
11
|
import { yjs } from "../bind-yjs.js"
|
|
5
|
-
import {
|
|
12
|
+
import { exportEntirety, exportSince, merge, version } from "../index.js"
|
|
6
13
|
import { ensureContainers } from "../populate.js"
|
|
7
|
-
import {
|
|
14
|
+
import { createYjsSubstrate } from "../substrate.js"
|
|
8
15
|
import { YjsVersion } from "../version.js"
|
|
9
16
|
|
|
10
17
|
// ===========================================================================
|
|
@@ -35,18 +42,26 @@ const NestedSchema = Schema.struct({
|
|
|
35
42
|
labels: Schema.record(Schema.string()),
|
|
36
43
|
})
|
|
37
44
|
|
|
45
|
+
// ===========================================================================
|
|
46
|
+
// Helpers
|
|
47
|
+
// ===========================================================================
|
|
48
|
+
|
|
49
|
+
const boundSimple = yjs.bind(SimpleSchema)
|
|
50
|
+
const boundStructList = yjs.bind(StructListSchema)
|
|
51
|
+
const boundNested = yjs.bind(NestedSchema)
|
|
52
|
+
|
|
38
53
|
// ===========================================================================
|
|
39
54
|
// Tests
|
|
40
55
|
// ===========================================================================
|
|
41
56
|
|
|
42
|
-
describe("
|
|
57
|
+
describe("createDoc", () => {
|
|
43
58
|
// -------------------------------------------------------------------------
|
|
44
59
|
// Default values
|
|
45
60
|
// -------------------------------------------------------------------------
|
|
46
61
|
|
|
47
62
|
describe("with defaults", () => {
|
|
48
63
|
it("creates a doc with empty containers for shared types", () => {
|
|
49
|
-
const doc =
|
|
64
|
+
const doc = createDoc(boundSimple)
|
|
50
65
|
// Text annotation returns "" (empty Y.Text)
|
|
51
66
|
expect(doc.title()).toBe("")
|
|
52
67
|
// Plain scalars return structural zeros
|
|
@@ -56,7 +71,7 @@ describe("createYjsDoc", () => {
|
|
|
56
71
|
})
|
|
57
72
|
|
|
58
73
|
it("creates a doc with nested struct empty containers", () => {
|
|
59
|
-
const doc =
|
|
74
|
+
const doc = createDoc(boundNested)
|
|
60
75
|
expect(doc.title()).toBe("")
|
|
61
76
|
// Plain scalar inside struct returns structural zero
|
|
62
77
|
expect(doc.meta.author()).toBe("")
|
|
@@ -65,7 +80,7 @@ describe("createYjsDoc", () => {
|
|
|
65
80
|
})
|
|
66
81
|
|
|
67
82
|
it("creates a doc with struct list defaults", () => {
|
|
68
|
-
const doc =
|
|
83
|
+
const doc = createDoc(boundStructList)
|
|
69
84
|
expect(doc.tasks()).toEqual([])
|
|
70
85
|
expect(doc.tasks.length).toBe(0)
|
|
71
86
|
})
|
|
@@ -77,7 +92,7 @@ describe("createYjsDoc", () => {
|
|
|
77
92
|
|
|
78
93
|
describe("with seeds", () => {
|
|
79
94
|
it("creates a doc with scalar seed values", () => {
|
|
80
|
-
const doc =
|
|
95
|
+
const doc = createDoc(boundSimple)
|
|
81
96
|
change(doc, (d: any) => {
|
|
82
97
|
d.title.insert(0, "Hello")
|
|
83
98
|
d.count.set(42)
|
|
@@ -93,7 +108,7 @@ describe("createYjsDoc", () => {
|
|
|
93
108
|
})
|
|
94
109
|
|
|
95
110
|
it("creates a doc with partial seed (defaults fill gaps)", () => {
|
|
96
|
-
const doc =
|
|
111
|
+
const doc = createDoc(boundSimple)
|
|
97
112
|
change(doc, (d: any) => {
|
|
98
113
|
d.title.insert(0, "Partial")
|
|
99
114
|
})
|
|
@@ -103,7 +118,7 @@ describe("createYjsDoc", () => {
|
|
|
103
118
|
})
|
|
104
119
|
|
|
105
120
|
it("creates a doc with nested struct seed", () => {
|
|
106
|
-
const doc =
|
|
121
|
+
const doc = createDoc(boundNested)
|
|
107
122
|
change(doc, (d: any) => {
|
|
108
123
|
d.title.insert(0, "Doc")
|
|
109
124
|
d.meta.author.set("Alice")
|
|
@@ -118,7 +133,7 @@ describe("createYjsDoc", () => {
|
|
|
118
133
|
})
|
|
119
134
|
|
|
120
135
|
it("creates a doc with struct list seed items", () => {
|
|
121
|
-
const doc =
|
|
136
|
+
const doc = createDoc(boundStructList)
|
|
122
137
|
// Separate change() calls for list pushes to preserve order
|
|
123
138
|
change(doc, (d: any) => d.tasks.push({ name: "Task 1", done: false }))
|
|
124
139
|
change(doc, (d: any) => d.tasks.push({ name: "Task 2", done: true }))
|
|
@@ -145,7 +160,10 @@ describe("createYjsDoc", () => {
|
|
|
145
160
|
;(rootMap.get("items") as Y.Array<string>).push(["x"])
|
|
146
161
|
})
|
|
147
162
|
|
|
148
|
-
const doc =
|
|
163
|
+
const doc = createRef(
|
|
164
|
+
SimpleSchema,
|
|
165
|
+
createYjsSubstrate(yjsDoc, SimpleSchema),
|
|
166
|
+
)
|
|
149
167
|
expect(doc.title()).toBe("External")
|
|
150
168
|
expect(doc.count()).toBe(99)
|
|
151
169
|
expect(doc.items()).toEqual(["x"])
|
|
@@ -155,7 +173,10 @@ describe("createYjsDoc", () => {
|
|
|
155
173
|
const yjsDoc = new Y.Doc()
|
|
156
174
|
ensureContainers(yjsDoc, SimpleSchema)
|
|
157
175
|
|
|
158
|
-
const doc =
|
|
176
|
+
const doc = createRef(
|
|
177
|
+
SimpleSchema,
|
|
178
|
+
createYjsSubstrate(yjsDoc, SimpleSchema),
|
|
179
|
+
)
|
|
159
180
|
change(doc, (d: any) => {
|
|
160
181
|
d.title.insert(0, "Hello")
|
|
161
182
|
d.count.set(42)
|
|
@@ -170,7 +191,10 @@ describe("createYjsDoc", () => {
|
|
|
170
191
|
const yjsDoc = new Y.Doc()
|
|
171
192
|
ensureContainers(yjsDoc, SimpleSchema)
|
|
172
193
|
|
|
173
|
-
const doc =
|
|
194
|
+
const doc = createRef(
|
|
195
|
+
SimpleSchema,
|
|
196
|
+
createYjsSubstrate(yjsDoc, SimpleSchema),
|
|
197
|
+
)
|
|
174
198
|
|
|
175
199
|
const rootMap = yjsDoc.getMap("root")
|
|
176
200
|
rootMap.set("count", 77)
|
|
@@ -178,12 +202,15 @@ describe("createYjsDoc", () => {
|
|
|
178
202
|
expect(doc.count()).toBe(77)
|
|
179
203
|
})
|
|
180
204
|
|
|
181
|
-
it("
|
|
205
|
+
it("unwrap() escape hatch returns the same Y.Doc", () => {
|
|
182
206
|
const yjsDoc = new Y.Doc()
|
|
183
207
|
ensureContainers(yjsDoc, SimpleSchema)
|
|
184
208
|
|
|
185
|
-
const doc =
|
|
186
|
-
|
|
209
|
+
const doc = createRef(
|
|
210
|
+
SimpleSchema,
|
|
211
|
+
createYjsSubstrate(yjsDoc, SimpleSchema),
|
|
212
|
+
)
|
|
213
|
+
const escaped = unwrap(doc)
|
|
187
214
|
|
|
188
215
|
expect(escaped).toBe(yjsDoc)
|
|
189
216
|
})
|
|
@@ -191,12 +218,12 @@ describe("createYjsDoc", () => {
|
|
|
191
218
|
})
|
|
192
219
|
|
|
193
220
|
// ===========================================================================
|
|
194
|
-
//
|
|
221
|
+
// createDoc with payload (fromEntirety)
|
|
195
222
|
// ===========================================================================
|
|
196
223
|
|
|
197
|
-
describe("
|
|
224
|
+
describe("createDoc with payload", () => {
|
|
198
225
|
it("reconstructs state from a snapshot", () => {
|
|
199
|
-
const doc1 =
|
|
226
|
+
const doc1 = createDoc(boundSimple)
|
|
200
227
|
change(doc1, (d: any) => {
|
|
201
228
|
d.title.insert(0, "Snapshot")
|
|
202
229
|
d.count.set(42)
|
|
@@ -205,7 +232,7 @@ describe("createYjsDocFromEntirety", () => {
|
|
|
205
232
|
change(doc1, (d: any) => d.items.push("b"))
|
|
206
233
|
|
|
207
234
|
const payload = exportEntirety(doc1)
|
|
208
|
-
const doc2 =
|
|
235
|
+
const doc2 = createDoc(boundSimple, payload)
|
|
209
236
|
|
|
210
237
|
expect(doc2.title()).toBe("Snapshot")
|
|
211
238
|
expect(doc2.count()).toBe(42)
|
|
@@ -213,7 +240,7 @@ describe("createYjsDocFromEntirety", () => {
|
|
|
213
240
|
})
|
|
214
241
|
|
|
215
242
|
it("reconstructs state after mutations", () => {
|
|
216
|
-
const doc1 =
|
|
243
|
+
const doc1 = createDoc(boundSimple)
|
|
217
244
|
change(doc1, (d: any) => {
|
|
218
245
|
d.title.insert(0, "Start")
|
|
219
246
|
})
|
|
@@ -225,7 +252,7 @@ describe("createYjsDocFromEntirety", () => {
|
|
|
225
252
|
})
|
|
226
253
|
|
|
227
254
|
const payload = exportEntirety(doc1)
|
|
228
|
-
const doc2 =
|
|
255
|
+
const doc2 = createDoc(boundSimple, payload)
|
|
229
256
|
|
|
230
257
|
expect(doc2.title()).toBe("Start End")
|
|
231
258
|
expect(doc2.count()).toBe(99)
|
|
@@ -233,7 +260,7 @@ describe("createYjsDocFromEntirety", () => {
|
|
|
233
260
|
})
|
|
234
261
|
|
|
235
262
|
it("reconstructs nested struct state from snapshot", () => {
|
|
236
|
-
const doc1 =
|
|
263
|
+
const doc1 = createDoc(boundNested)
|
|
237
264
|
change(doc1, (d: any) => {
|
|
238
265
|
d.title.insert(0, "Nested")
|
|
239
266
|
d.meta.author.set("Alice")
|
|
@@ -243,7 +270,7 @@ describe("createYjsDocFromEntirety", () => {
|
|
|
243
270
|
change(doc1, (d: any) => d.meta.tags.push("v2"))
|
|
244
271
|
|
|
245
272
|
const payload = exportEntirety(doc1)
|
|
246
|
-
const doc2 =
|
|
273
|
+
const doc2 = createDoc(boundNested, payload)
|
|
247
274
|
|
|
248
275
|
expect(doc2.title()).toBe("Nested")
|
|
249
276
|
expect(doc2.meta.author()).toBe("Alice")
|
|
@@ -253,12 +280,12 @@ describe("createYjsDocFromEntirety", () => {
|
|
|
253
280
|
})
|
|
254
281
|
|
|
255
282
|
it("reconstructs struct list state from snapshot", () => {
|
|
256
|
-
const doc1 =
|
|
283
|
+
const doc1 = createDoc(boundStructList)
|
|
257
284
|
change(doc1, (d: any) => d.tasks.push({ name: "Task A", done: false }))
|
|
258
285
|
change(doc1, (d: any) => d.tasks.push({ name: "Task B", done: true }))
|
|
259
286
|
|
|
260
287
|
const payload = exportEntirety(doc1)
|
|
261
|
-
const doc2 =
|
|
288
|
+
const doc2 = createDoc(boundStructList, payload)
|
|
262
289
|
|
|
263
290
|
expect(doc2.tasks.length).toBe(2)
|
|
264
291
|
expect((doc2.tasks.at(0) as any).name()).toBe("Task A")
|
|
@@ -266,12 +293,12 @@ describe("createYjsDocFromEntirety", () => {
|
|
|
266
293
|
})
|
|
267
294
|
|
|
268
295
|
it("is writable after reconstruction", () => {
|
|
269
|
-
const doc1 =
|
|
296
|
+
const doc1 = createDoc(boundSimple)
|
|
270
297
|
change(doc1, (d: any) => {
|
|
271
298
|
d.title.insert(0, "Original")
|
|
272
299
|
})
|
|
273
300
|
const payload = exportEntirety(doc1)
|
|
274
|
-
const doc2 =
|
|
301
|
+
const doc2 = createDoc(boundSimple, payload)
|
|
275
302
|
|
|
276
303
|
change(doc2, (d: any) => {
|
|
277
304
|
d.title.insert(8, " Copy")
|
|
@@ -283,12 +310,12 @@ describe("createYjsDocFromEntirety", () => {
|
|
|
283
310
|
})
|
|
284
311
|
|
|
285
312
|
it("is observable after reconstruction", () => {
|
|
286
|
-
const doc1 =
|
|
313
|
+
const doc1 = createDoc(boundSimple)
|
|
287
314
|
change(doc1, (d: any) => {
|
|
288
315
|
d.title.insert(0, "Original")
|
|
289
316
|
})
|
|
290
317
|
const payload = exportEntirety(doc1)
|
|
291
|
-
const doc2 =
|
|
318
|
+
const doc2 = createDoc(boundSimple, payload)
|
|
292
319
|
|
|
293
320
|
const received: any[] = []
|
|
294
321
|
subscribe(doc2, (changeset: any) => {
|
|
@@ -310,13 +337,13 @@ describe("createYjsDocFromEntirety", () => {
|
|
|
310
337
|
describe("sync primitives", () => {
|
|
311
338
|
describe("version", () => {
|
|
312
339
|
it("returns a YjsVersion", () => {
|
|
313
|
-
const doc =
|
|
340
|
+
const doc = createDoc(boundSimple)
|
|
314
341
|
const v = version(doc)
|
|
315
342
|
expect(v).toBeInstanceOf(YjsVersion)
|
|
316
343
|
})
|
|
317
344
|
|
|
318
345
|
it("advances after mutations", () => {
|
|
319
|
-
const doc =
|
|
346
|
+
const doc = createDoc(boundSimple)
|
|
320
347
|
const v1 = version(doc)
|
|
321
348
|
|
|
322
349
|
change(doc, (d: any) => {
|
|
@@ -328,7 +355,7 @@ describe("sync primitives", () => {
|
|
|
328
355
|
})
|
|
329
356
|
|
|
330
357
|
it("serialize/parse round-trips", () => {
|
|
331
|
-
const doc =
|
|
358
|
+
const doc = createDoc(boundSimple)
|
|
332
359
|
change(doc, (d: any) => {
|
|
333
360
|
d.title.insert(0, "Test")
|
|
334
361
|
})
|
|
@@ -341,7 +368,7 @@ describe("sync primitives", () => {
|
|
|
341
368
|
|
|
342
369
|
describe("exportEntirety", () => {
|
|
343
370
|
it("returns a binary payload", () => {
|
|
344
|
-
const doc =
|
|
371
|
+
const doc = createDoc(boundSimple)
|
|
345
372
|
change(doc, (d: any) => {
|
|
346
373
|
d.title.insert(0, "Snap")
|
|
347
374
|
})
|
|
@@ -354,11 +381,11 @@ describe("sync primitives", () => {
|
|
|
354
381
|
|
|
355
382
|
describe("exportSince + merge", () => {
|
|
356
383
|
it("syncs incremental changes between two docs", () => {
|
|
357
|
-
const doc1 =
|
|
384
|
+
const doc1 = createDoc(boundSimple)
|
|
358
385
|
change(doc1, (d: any) => {
|
|
359
386
|
d.title.insert(0, "Start")
|
|
360
387
|
})
|
|
361
|
-
const doc2 =
|
|
388
|
+
const doc2 = createDoc(boundSimple, exportEntirety(doc1))
|
|
362
389
|
|
|
363
390
|
const v2Before = version(doc2)
|
|
364
391
|
|
|
@@ -382,8 +409,8 @@ describe("sync primitives", () => {
|
|
|
382
409
|
})
|
|
383
410
|
|
|
384
411
|
it("syncs multiple incremental deltas", () => {
|
|
385
|
-
const doc1 =
|
|
386
|
-
const doc2 =
|
|
412
|
+
const doc1 = createDoc(boundSimple)
|
|
413
|
+
const doc2 = createDoc(boundSimple, exportEntirety(doc1))
|
|
387
414
|
|
|
388
415
|
// First round
|
|
389
416
|
let vBefore = version(doc2)
|
|
@@ -411,11 +438,11 @@ describe("sync primitives", () => {
|
|
|
411
438
|
})
|
|
412
439
|
|
|
413
440
|
it("changefeed fires on merge", () => {
|
|
414
|
-
const doc1 =
|
|
441
|
+
const doc1 = createDoc(boundSimple)
|
|
415
442
|
change(doc1, (d: any) => {
|
|
416
443
|
d.title.insert(0, "Source")
|
|
417
444
|
})
|
|
418
|
-
const doc2 =
|
|
445
|
+
const doc2 = createDoc(boundSimple, exportEntirety(doc1))
|
|
419
446
|
|
|
420
447
|
const v2Before = version(doc2)
|
|
421
448
|
|
|
@@ -437,8 +464,8 @@ describe("sync primitives", () => {
|
|
|
437
464
|
})
|
|
438
465
|
|
|
439
466
|
it("merge passes origin to changefeed", () => {
|
|
440
|
-
const doc1 =
|
|
441
|
-
const doc2 =
|
|
467
|
+
const doc1 = createDoc(boundSimple)
|
|
468
|
+
const doc2 = createDoc(boundSimple, exportEntirety(doc1))
|
|
442
469
|
|
|
443
470
|
const v2Before = version(doc2)
|
|
444
471
|
change(doc1, (d: any) => {
|
|
@@ -458,7 +485,7 @@ describe("sync primitives", () => {
|
|
|
458
485
|
|
|
459
486
|
describe("versions equal after sync", () => {
|
|
460
487
|
it("versions equal after full snapshot sync", () => {
|
|
461
|
-
const doc1 =
|
|
488
|
+
const doc1 = createDoc(boundSimple)
|
|
462
489
|
change(doc1, (d: any) => {
|
|
463
490
|
d.title.insert(0, "Same")
|
|
464
491
|
})
|
|
@@ -466,14 +493,14 @@ describe("sync primitives", () => {
|
|
|
466
493
|
d.count.set(42)
|
|
467
494
|
})
|
|
468
495
|
|
|
469
|
-
const doc2 =
|
|
496
|
+
const doc2 = createDoc(boundSimple, exportEntirety(doc1))
|
|
470
497
|
|
|
471
498
|
expect(version(doc1).compare(version(doc2))).toBe("equal")
|
|
472
499
|
})
|
|
473
500
|
|
|
474
501
|
it("versions equal after bidirectional delta sync", () => {
|
|
475
|
-
const doc1 =
|
|
476
|
-
const doc2 =
|
|
502
|
+
const doc1 = createDoc(boundSimple)
|
|
503
|
+
const doc2 = createDoc(boundSimple, exportEntirety(doc1))
|
|
477
504
|
|
|
478
505
|
const v1Before = version(doc1)
|
|
479
506
|
const v2Before = version(doc2)
|
|
@@ -504,11 +531,8 @@ describe("sync primitives", () => {
|
|
|
504
531
|
describe("full workflow", () => {
|
|
505
532
|
it("create → mutate → sync → observe", () => {
|
|
506
533
|
// 1. Create two docs
|
|
507
|
-
const doc1 =
|
|
508
|
-
const doc2 =
|
|
509
|
-
StructListSchema,
|
|
510
|
-
exportEntirety(doc1),
|
|
511
|
-
)
|
|
534
|
+
const doc1 = createDoc(boundStructList)
|
|
535
|
+
const doc2 = createDoc(boundStructList, exportEntirety(doc1))
|
|
512
536
|
|
|
513
537
|
// 2. Set up observer on doc2
|
|
514
538
|
const changes: any[] = []
|
|
@@ -559,7 +583,7 @@ describe("full workflow", () => {
|
|
|
559
583
|
|
|
560
584
|
it("create → mutate → snapshot → reconstruct → continue", () => {
|
|
561
585
|
// 1. Create and mutate
|
|
562
|
-
const doc1 =
|
|
586
|
+
const doc1 = createDoc(boundSimple)
|
|
563
587
|
change(doc1, (d: any) => {
|
|
564
588
|
d.title.insert(0, "Start")
|
|
565
589
|
})
|
|
@@ -573,7 +597,7 @@ describe("full workflow", () => {
|
|
|
573
597
|
const snapshot = exportEntirety(doc1)
|
|
574
598
|
|
|
575
599
|
// 3. Reconstruct
|
|
576
|
-
const doc2 =
|
|
600
|
+
const doc2 = createDoc(boundSimple, snapshot)
|
|
577
601
|
expect(doc2.title()).toBe("Start Middle")
|
|
578
602
|
expect(doc2.count()).toBe(10)
|
|
579
603
|
expect(doc2.items()).toEqual(["first"])
|
|
@@ -598,8 +622,8 @@ describe("full workflow", () => {
|
|
|
598
622
|
|
|
599
623
|
it("concurrent edits converge correctly", () => {
|
|
600
624
|
// 1. Create two peers from the same initial state
|
|
601
|
-
const doc1 =
|
|
602
|
-
const doc2 =
|
|
625
|
+
const doc1 = createDoc(boundSimple)
|
|
626
|
+
const doc2 = createDoc(boundSimple, exportEntirety(doc1))
|
|
603
627
|
|
|
604
628
|
const v1Before = version(doc1)
|
|
605
629
|
const v2Before = version(doc2)
|
|
@@ -15,10 +15,7 @@ import { yjsReader } from "../reader.js"
|
|
|
15
15
|
* After `ensureContainers` the doc has the correct shared types but no
|
|
16
16
|
* values. We populate values via raw Yjs API within a single transact.
|
|
17
17
|
*/
|
|
18
|
-
function setup(
|
|
19
|
-
schema: any,
|
|
20
|
-
seed?: Record<string, unknown>,
|
|
21
|
-
) {
|
|
18
|
+
function setup(schema: any, seed?: Record<string, unknown>) {
|
|
22
19
|
const doc = new Y.Doc()
|
|
23
20
|
ensureContainers(doc, schema)
|
|
24
21
|
if (seed) {
|
|
@@ -166,15 +163,9 @@ function buildStructMap(
|
|
|
166
163
|
if (Array.isArray(value)) {
|
|
167
164
|
for (const item of value) {
|
|
168
165
|
const itemSchema = fieldSchema.item
|
|
169
|
-
if (
|
|
170
|
-
itemSchema &&
|
|
171
|
-
itemSchema[KIND] === "product"
|
|
172
|
-
) {
|
|
166
|
+
if (itemSchema && itemSchema[KIND] === "product") {
|
|
173
167
|
arr.push([
|
|
174
|
-
buildStructMap(
|
|
175
|
-
itemSchema,
|
|
176
|
-
item as Record<string, unknown>,
|
|
177
|
-
),
|
|
168
|
+
buildStructMap(itemSchema, item as Record<string, unknown>),
|
|
178
169
|
])
|
|
179
170
|
} else {
|
|
180
171
|
arr.push([item])
|
|
@@ -204,8 +195,6 @@ function buildStructMap(
|
|
|
204
195
|
return map
|
|
205
196
|
}
|
|
206
197
|
|
|
207
|
-
|
|
208
|
-
|
|
209
198
|
/** Build a RawPath from variadic key/index segments. */
|
|
210
199
|
function p(...segs: (string | number)[]): RawPath {
|
|
211
200
|
let path = RawPath.empty
|