@kyneta/yjs-schema 1.7.0 → 2.0.0

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.
@@ -17,7 +17,7 @@
17
17
  // 4. Sync: two peers should converge after exchanging deltas
18
18
 
19
19
  import {
20
- change,
20
+ batch,
21
21
  createDoc,
22
22
  exportEntirety,
23
23
  exportSince,
@@ -76,7 +76,7 @@ describe("record-of-struct (plain baseline)", () => {
76
76
  it("set a record entry and read it back", () => {
77
77
  const doc = createDoc(BoundPlainRecord)
78
78
 
79
- change(doc, (d: any) => {
79
+ batch(doc, (d: any) => {
80
80
  d.profiles.set("alice", { displayName: "Alice", age: 30 })
81
81
  })
82
82
 
@@ -89,7 +89,7 @@ describe("record-of-struct (plain baseline)", () => {
89
89
  it("set multiple entries and read all back", () => {
90
90
  const doc = createDoc(BoundPlainRecord)
91
91
 
92
- change(doc, (d: any) => {
92
+ batch(doc, (d: any) => {
93
93
  d.profiles.set("alice", { displayName: "Alice", age: 30 })
94
94
  d.profiles.set("bob", { displayName: "Bob", age: 25 })
95
95
  })
@@ -104,7 +104,7 @@ describe("record-of-struct (plain baseline)", () => {
104
104
  it("navigate into a record entry via .at()", () => {
105
105
  const doc = createDoc(BoundPlainRecord)
106
106
 
107
- change(doc, (d: any) => {
107
+ batch(doc, (d: any) => {
108
108
  d.profiles.set("alice", { displayName: "Alice", age: 30 })
109
109
  })
110
110
 
@@ -117,7 +117,7 @@ describe("record-of-struct (plain baseline)", () => {
117
117
  it("syncs record-of-struct between two peers via snapshot", () => {
118
118
  const docA = createDoc(BoundPlainRecord)
119
119
 
120
- change(docA, (d: any) => {
120
+ batch(docA, (d: any) => {
121
121
  d.profiles.set("alice", { displayName: "Alice", age: 30 })
122
122
  })
123
123
 
@@ -132,7 +132,7 @@ describe("record-of-struct (plain baseline)", () => {
132
132
  it("syncs record-of-struct between two peers via delta", () => {
133
133
  const docA = createDoc(BoundPlainRecord)
134
134
 
135
- change(docA, (d: any) => {
135
+ batch(docA, (d: any) => {
136
136
  d.profiles.set("alice", { displayName: "Alice", age: 30 })
137
137
  })
138
138
 
@@ -141,7 +141,7 @@ describe("record-of-struct (plain baseline)", () => {
141
141
 
142
142
  const v0 = version(docB)
143
143
 
144
- change(docA, (d: any) => {
144
+ batch(docA, (d: any) => {
145
145
  d.profiles.set("bob", { displayName: "Bob", age: 25 })
146
146
  })
147
147
 
@@ -164,7 +164,7 @@ describe("text-inside-struct-inside-record", () => {
164
164
  it("set a record entry with text field omitted and read it back", () => {
165
165
  const doc = createDoc(BoundProfile)
166
166
 
167
- change(doc, (d: any) => {
167
+ batch(doc, (d: any) => {
168
168
  d.profiles.set("alice", { displayName: "Alice" })
169
169
  })
170
170
 
@@ -177,7 +177,7 @@ describe("text-inside-struct-inside-record", () => {
177
177
  it("set a record entry with text field provided and read it back", () => {
178
178
  const doc = createDoc(BoundProfile)
179
179
 
180
- change(doc, (d: any) => {
180
+ batch(doc, (d: any) => {
181
181
  d.profiles.set("alice", { displayName: "Alice", bio: "Hello world" })
182
182
  })
183
183
 
@@ -190,7 +190,7 @@ describe("text-inside-struct-inside-record", () => {
190
190
  it("navigate into a record entry and read the text", () => {
191
191
  const doc = createDoc(BoundProfile)
192
192
 
193
- change(doc, (d: any) => {
193
+ batch(doc, (d: any) => {
194
194
  d.profiles.set("alice", { displayName: "Alice" })
195
195
  })
196
196
 
@@ -203,11 +203,11 @@ describe("text-inside-struct-inside-record", () => {
203
203
  it("insert text into a text field inside a record entry (field omitted at creation)", () => {
204
204
  const doc = createDoc(BoundProfile)
205
205
 
206
- change(doc, (d: any) => {
206
+ batch(doc, (d: any) => {
207
207
  d.profiles.set("alice", { displayName: "Alice" })
208
208
  })
209
209
 
210
- change(doc, (d: any) => {
210
+ batch(doc, (d: any) => {
211
211
  d.profiles.at("alice").bio.insert(0, "Hello world")
212
212
  })
213
213
 
@@ -217,11 +217,11 @@ describe("text-inside-struct-inside-record", () => {
217
217
  it("insert text into a text field inside a record entry (field provided at creation)", () => {
218
218
  const doc = createDoc(BoundProfile)
219
219
 
220
- change(doc, (d: any) => {
220
+ batch(doc, (d: any) => {
221
221
  d.profiles.set("alice", { displayName: "Alice", bio: "Initial" })
222
222
  })
223
223
 
224
- change(doc, (d: any) => {
224
+ batch(doc, (d: any) => {
225
225
  d.profiles.at("alice").bio.insert(7, " bio")
226
226
  })
227
227
 
@@ -231,12 +231,12 @@ describe("text-inside-struct-inside-record", () => {
231
231
  it("set multiple entries and edit text independently", () => {
232
232
  const doc = createDoc(BoundProfile)
233
233
 
234
- change(doc, (d: any) => {
234
+ batch(doc, (d: any) => {
235
235
  d.profiles.set("alice", { displayName: "Alice" })
236
236
  d.profiles.set("bob", { displayName: "Bob" })
237
237
  })
238
238
 
239
- change(doc, (d: any) => {
239
+ batch(doc, (d: any) => {
240
240
  d.profiles.at("alice").bio.insert(0, "Alice's bio")
241
241
  d.profiles.at("bob").bio.insert(0, "Bob's bio")
242
242
  })
@@ -248,7 +248,7 @@ describe("text-inside-struct-inside-record", () => {
248
248
  it("subscribe fires on text edit inside record entry", () => {
249
249
  const doc = createDoc(BoundProfile)
250
250
 
251
- change(doc, (d: any) => {
251
+ batch(doc, (d: any) => {
252
252
  d.profiles.set("alice", { displayName: "Alice" })
253
253
  })
254
254
 
@@ -257,7 +257,7 @@ describe("text-inside-struct-inside-record", () => {
257
257
  fired = true
258
258
  })
259
259
 
260
- change(doc, (d: any) => {
260
+ batch(doc, (d: any) => {
261
261
  d.profiles.at("alice").bio.insert(0, "Hello")
262
262
  })
263
263
 
@@ -267,10 +267,10 @@ describe("text-inside-struct-inside-record", () => {
267
267
  it("syncs text-inside-record via snapshot", () => {
268
268
  const docA = createDoc(BoundProfile)
269
269
 
270
- change(docA, (d: any) => {
270
+ batch(docA, (d: any) => {
271
271
  d.profiles.set("alice", { displayName: "Alice" })
272
272
  })
273
- change(docA, (d: any) => {
273
+ batch(docA, (d: any) => {
274
274
  d.profiles.at("alice").bio.insert(0, "Collaborative bio")
275
275
  })
276
276
 
@@ -288,7 +288,7 @@ describe("text-inside-struct-inside-record", () => {
288
288
  it("syncs text-inside-record via delta", () => {
289
289
  const docA = createDoc(BoundProfile)
290
290
 
291
- change(docA, (d: any) => {
291
+ batch(docA, (d: any) => {
292
292
  d.profiles.set("alice", { displayName: "Alice" })
293
293
  })
294
294
 
@@ -298,7 +298,7 @@ describe("text-inside-struct-inside-record", () => {
298
298
  const docB = createDoc(BoundProfile, exportEntirety(docA))
299
299
  const v0 = version(docB)
300
300
 
301
- change(docA, (d: any) => {
301
+ batch(docA, (d: any) => {
302
302
  d.profiles.at("alice").bio.insert(0, "Hello from A")
303
303
  })
304
304
 
@@ -311,7 +311,7 @@ describe("text-inside-struct-inside-record", () => {
311
311
  })
312
312
 
313
313
  // Text on B is functional — can insert independently
314
- change(docB, (d: any) => {
314
+ batch(docB, (d: any) => {
315
315
  d.profiles.at("alice").bio.insert(12, "!")
316
316
  })
317
317
  expect((docB as any).profiles.at("alice").bio()).toBe("Hello from A!")
@@ -321,7 +321,7 @@ describe("text-inside-struct-inside-record", () => {
321
321
  const docA = createDoc(BoundProfile)
322
322
 
323
323
  // Sync initial state: A creates the entry, B starts from snapshot
324
- change(docA, (d: any) => {
324
+ batch(docA, (d: any) => {
325
325
  d.profiles.set("alice", { displayName: "Alice" })
326
326
  })
327
327
  const docB = createDoc(BoundProfile, exportEntirety(docA))
@@ -330,10 +330,10 @@ describe("text-inside-struct-inside-record", () => {
330
330
  const vA = version(docA)
331
331
  const vB = version(docB)
332
332
 
333
- change(docA, (d: any) => {
333
+ batch(docA, (d: any) => {
334
334
  d.profiles.at("alice").bio.insert(0, "Hello ")
335
335
  })
336
- change(docB, (d: any) => {
336
+ batch(docB, (d: any) => {
337
337
  d.profiles.at("alice").bio.insert(0, "World")
338
338
  })
339
339
 
@@ -360,7 +360,7 @@ describe("text-inside-struct-inside-list", () => {
360
360
  it("push a struct with text field omitted and read it back", () => {
361
361
  const doc = createDoc(BoundListProfile)
362
362
 
363
- change(doc, (d: any) => {
363
+ batch(doc, (d: any) => {
364
364
  d.players.push({ name: "Alice" })
365
365
  })
366
366
 
@@ -372,7 +372,7 @@ describe("text-inside-struct-inside-list", () => {
372
372
  it("push a struct with text field provided and read it back", () => {
373
373
  const doc = createDoc(BoundListProfile)
374
374
 
375
- change(doc, (d: any) => {
375
+ batch(doc, (d: any) => {
376
376
  d.players.push({ name: "Alice", bio: "Hi there" })
377
377
  })
378
378
 
@@ -384,11 +384,11 @@ describe("text-inside-struct-inside-list", () => {
384
384
  it("insert text into a text field inside a list item (field omitted at creation)", () => {
385
385
  const doc = createDoc(BoundListProfile)
386
386
 
387
- change(doc, (d: any) => {
387
+ batch(doc, (d: any) => {
388
388
  d.players.push({ name: "Alice" })
389
389
  })
390
390
 
391
- change(doc, (d: any) => {
391
+ batch(doc, (d: any) => {
392
392
  d.players.at(0).bio.insert(0, "Alice's bio")
393
393
  })
394
394
 
@@ -398,12 +398,12 @@ describe("text-inside-struct-inside-list", () => {
398
398
  it("multiple list items with independent text fields", () => {
399
399
  const doc = createDoc(BoundListProfile)
400
400
 
401
- change(doc, (d: any) => {
401
+ batch(doc, (d: any) => {
402
402
  d.players.push({ name: "Alice" })
403
403
  d.players.push({ name: "Bob" })
404
404
  })
405
405
 
406
- change(doc, (d: any) => {
406
+ batch(doc, (d: any) => {
407
407
  d.players.at(0).bio.insert(0, "Alice's bio")
408
408
  d.players.at(1).bio.insert(0, "Bob's bio")
409
409
  })
@@ -415,7 +415,7 @@ describe("text-inside-struct-inside-list", () => {
415
415
  it("syncs list-of-struct-with-text via delta", () => {
416
416
  const docA = createDoc(BoundListProfile)
417
417
 
418
- change(docA, (d: any) => {
418
+ batch(docA, (d: any) => {
419
419
  d.players.push({ name: "Alice" })
420
420
  })
421
421
 
@@ -423,7 +423,7 @@ describe("text-inside-struct-inside-list", () => {
423
423
  const docB = createDoc(BoundListProfile, exportEntirety(docA))
424
424
  const v0 = version(docB)
425
425
 
426
- change(docA, (d: any) => {
426
+ batch(docA, (d: any) => {
427
427
  d.players.at(0).bio.insert(0, "Synced bio")
428
428
  })
429
429
 
@@ -1,5 +1,5 @@
1
1
  import {
2
- change,
2
+ batch,
3
3
  createDoc,
4
4
  createRef,
5
5
  exportEntirety,
@@ -8,6 +8,7 @@ import {
8
8
  RawPath,
9
9
  Schema,
10
10
  subscribe,
11
+ unwrap,
11
12
  version,
12
13
  } from "@kyneta/schema"
13
14
  import { describe, expect, it } from "vitest"
@@ -75,16 +76,16 @@ describe("YjsSubstrate", () => {
75
76
  expect(substrate.reader.read(RawPath.empty.field("items"))).toEqual([])
76
77
  })
77
78
 
78
- it("creates a substrate and populates via change()", () => {
79
+ it("creates a substrate and populates via batch()", () => {
79
80
  const doc = createDoc(yjs.bind(SimpleSchema))
80
- change(doc, (d: any) => {
81
+ batch(doc, (d: any) => {
81
82
  d.title.insert(0, "Hello")
82
83
  d.count.set(42)
83
84
  })
84
- // Separate change() calls for list pushes to preserve order
85
+ // Separate batch() calls for list pushes to preserve order
85
86
  // (Yjs reverses order within a single transaction)
86
- change(doc, (d: any) => d.items.push("a"))
87
- change(doc, (d: any) => d.items.push("b"))
87
+ batch(doc, (d: any) => d.items.push("a"))
88
+ batch(doc, (d: any) => d.items.push("b"))
88
89
  expect(doc.title()).toBe("Hello")
89
90
  expect(doc.count()).toBe(42)
90
91
  expect(doc.items()).toEqual(["a", "b"])
@@ -92,7 +93,7 @@ describe("YjsSubstrate", () => {
92
93
 
93
94
  it("creates a substrate with partial values (unset fields stay empty)", () => {
94
95
  const doc = createDoc(yjs.bind(SimpleSchema))
95
- change(doc, (d: any) => {
96
+ batch(doc, (d: any) => {
96
97
  d.title.insert(0, "Partial")
97
98
  })
98
99
  expect(doc.title()).toBe("Partial")
@@ -100,19 +101,19 @@ describe("YjsSubstrate", () => {
100
101
  expect(doc.items()).toEqual([])
101
102
  })
102
103
 
103
- it("creates a substrate with nested struct values via change()", () => {
104
+ it("creates a substrate with nested struct values via batch()", () => {
104
105
  const doc = createDoc(yjs.bind(FullSchema))
105
- change(doc, (d: any) => {
106
+ batch(doc, (d: any) => {
106
107
  d.meta.author.set("Alice")
107
108
  })
108
109
  expect(doc.meta.author()).toBe("Alice")
109
110
  })
110
111
 
111
- it("creates a substrate with struct list values via change()", () => {
112
+ it("creates a substrate with struct list values via batch()", () => {
112
113
  const doc = createDoc(yjs.bind(StructListSchema))
113
- // Separate change() calls for list pushes to preserve order
114
- change(doc, (d: any) => d.tasks.push({ name: "Task 1", done: false }))
115
- change(doc, (d: any) => d.tasks.push({ name: "Task 2", done: true }))
114
+ // Separate batch() calls for list pushes to preserve order
115
+ batch(doc, (d: any) => d.tasks.push({ name: "Task 1", done: false }))
116
+ batch(doc, (d: any) => d.tasks.push({ name: "Task 2", done: true }))
116
117
  expect((doc.tasks.at(0) as any).name()).toBe("Task 1")
117
118
  expect((doc.tasks.at(1) as any).done()).toBe(true)
118
119
  })
@@ -125,7 +126,7 @@ describe("YjsSubstrate", () => {
125
126
  describe("write round-trip", () => {
126
127
  it("text insert round-trips through prepare/flush", () => {
127
128
  const doc = createDoc(yjs.bind(SimpleSchema))
128
- change(doc, (d: any) => {
129
+ batch(doc, (d: any) => {
129
130
  d.title.insert(0, "Hello")
130
131
  })
131
132
  expect(doc.title()).toBe("Hello")
@@ -133,7 +134,7 @@ describe("YjsSubstrate", () => {
133
134
 
134
135
  it("scalar set round-trips through prepare/flush", () => {
135
136
  const doc = createDoc(yjs.bind(SimpleSchema))
136
- change(doc, (d: any) => {
137
+ batch(doc, (d: any) => {
137
138
  d.count.set(42)
138
139
  })
139
140
  expect(doc.count()).toBe(42)
@@ -141,10 +142,10 @@ describe("YjsSubstrate", () => {
141
142
 
142
143
  it("list push round-trips through prepare/flush", () => {
143
144
  const doc = createDoc(yjs.bind(SimpleSchema))
144
- change(doc, (d: any) => {
145
+ batch(doc, (d: any) => {
145
146
  d.items.push("a")
146
147
  })
147
- change(doc, (d: any) => {
148
+ batch(doc, (d: any) => {
148
149
  d.items.push("b")
149
150
  })
150
151
  expect(doc.items()).toEqual(["a", "b"])
@@ -161,7 +162,7 @@ describe("YjsSubstrate", () => {
161
162
  const doc = createDoc(yjs.bind(SimpleSchema))
162
163
  const v1 = version(doc)
163
164
 
164
- change(doc, (d: any) => {
165
+ batch(doc, (d: any) => {
165
166
  d.title.insert(0, "Hi")
166
167
  })
167
168
  const v2 = version(doc)
@@ -172,7 +173,7 @@ describe("YjsSubstrate", () => {
172
173
 
173
174
  it("version serialize/parse round-trips", () => {
174
175
  const doc = createDoc(yjs.bind(SimpleSchema))
175
- change(doc, (d: any) => {
176
+ batch(doc, (d: any) => {
176
177
  d.title.insert(0, "Test")
177
178
  d.count.set(5)
178
179
  })
@@ -185,12 +186,12 @@ describe("YjsSubstrate", () => {
185
186
 
186
187
  it("version changes after a delete-only mutation", () => {
187
188
  const doc = createDoc(yjs.bind(SimpleSchema))
188
- change(doc, (d: any) => {
189
+ batch(doc, (d: any) => {
189
190
  d.title.insert(0, "hello")
190
191
  })
191
192
  const vAfterInsert = version(doc)
192
193
 
193
- change(doc, (d: any) => {
194
+ batch(doc, (d: any) => {
194
195
  d.title.delete(1, 1)
195
196
  })
196
197
  const vAfterDelete = version(doc)
@@ -209,7 +210,7 @@ describe("YjsSubstrate", () => {
209
210
  describe("export/import snapshot", () => {
210
211
  it("exports a binary payload", () => {
211
212
  const doc = createDoc(yjs.bind(SimpleSchema))
212
- change(doc, (d: any) => {
213
+ batch(doc, (d: any) => {
213
214
  d.title.insert(0, "Snapshot")
214
215
  })
215
216
  const payload = exportEntirety(doc)
@@ -219,14 +220,14 @@ describe("YjsSubstrate", () => {
219
220
 
220
221
  it("reconstructs equivalent state from snapshot", () => {
221
222
  const doc1 = createDoc(yjs.bind(SimpleSchema))
222
- change(doc1, (d: any) => {
223
+ batch(doc1, (d: any) => {
223
224
  d.title.insert(0, "Hello")
224
225
  d.count.set(42)
225
226
  })
226
- // Separate change() calls for list pushes to preserve order
227
- change(doc1, (d: any) => d.items.push("a"))
228
- change(doc1, (d: any) => d.items.push("b"))
229
- change(doc1, (d: any) => {
227
+ // Separate batch() calls for list pushes to preserve order
228
+ batch(doc1, (d: any) => d.items.push("a"))
229
+ batch(doc1, (d: any) => d.items.push("b"))
230
+ batch(doc1, (d: any) => {
230
231
  d.title.insert(5, " World")
231
232
  })
232
233
 
@@ -246,14 +247,14 @@ describe("YjsSubstrate", () => {
246
247
  describe("delta sync", () => {
247
248
  it("exportSince → merge syncs state", () => {
248
249
  const doc1 = createDoc(yjs.bind(SimpleSchema))
249
- change(doc1, (d: any) => {
250
+ batch(doc1, (d: any) => {
250
251
  d.title.insert(0, "Start")
251
252
  })
252
253
  const doc2 = createDoc(yjs.bind(SimpleSchema), exportEntirety(doc1))
253
254
 
254
255
  const v1Before = version(doc1)
255
256
 
256
- change(doc1, (d: any) => {
257
+ batch(doc1, (d: any) => {
257
258
  d.title.insert(5, " Edited")
258
259
  d.count.set(99)
259
260
  })
@@ -274,10 +275,10 @@ describe("YjsSubstrate", () => {
274
275
  const v2Before = version(doc2)
275
276
 
276
277
  // Independent mutations
277
- change(doc1, (d: any) => {
278
+ batch(doc1, (d: any) => {
278
279
  d.title.insert(0, "A")
279
280
  })
280
- change(doc2, (d: any) => {
281
+ batch(doc2, (d: any) => {
281
282
  d.count.set(7)
282
283
  })
283
284
 
@@ -314,14 +315,14 @@ describe("YjsSubstrate", () => {
314
315
  describe("changefeed", () => {
315
316
  it("fires on merge", () => {
316
317
  const doc1 = createDoc(yjs.bind(SimpleSchema))
317
- change(doc1, (d: any) => {
318
+ batch(doc1, (d: any) => {
318
319
  d.title.insert(0, "A")
319
320
  })
320
321
  const doc2 = createDoc(yjs.bind(SimpleSchema), exportEntirety(doc1))
321
322
 
322
323
  const v2Before = version(doc2)
323
324
 
324
- change(doc1, (d: any) => {
325
+ batch(doc1, (d: any) => {
325
326
  d.count.set(42)
326
327
  })
327
328
 
@@ -366,7 +367,7 @@ describe("YjsSubstrate", () => {
366
367
  received.push(changeset)
367
368
  })
368
369
 
369
- change(doc, (d: any) => {
370
+ batch(doc, (d: any) => {
370
371
  d.count.set(42)
371
372
  })
372
373
 
@@ -380,7 +381,7 @@ describe("YjsSubstrate", () => {
380
381
  const _doc2 = createDoc(yjs.bind(StructListSchema), exportEntirety(doc1))
381
382
 
382
383
  // Add a struct item on doc1, sync to doc2
383
- change(doc1, (d: any) => {
384
+ batch(doc1, (d: any) => {
384
385
  d.tasks.push({ name: "Buy milk", done: false })
385
386
  })
386
387
  const snap = exportEntirety(doc1)
@@ -397,7 +398,7 @@ describe("YjsSubstrate", () => {
397
398
  const unsub = cf.subscribe((cs: unknown) => fieldChanges.push(cs))
398
399
 
399
400
  // Toggle done on doc1
400
- change(doc1, (d: any) => {
401
+ batch(doc1, (d: any) => {
401
402
  d.tasks.at(0).done.set(true)
402
403
  })
403
404
 
@@ -418,7 +419,7 @@ describe("YjsSubstrate", () => {
418
419
  const doc1 = createDoc(yjs.bind(StructListSchema))
419
420
 
420
421
  // Add a struct item, sync to doc2
421
- change(doc1, (d: any) => {
422
+ batch(doc1, (d: any) => {
422
423
  d.tasks.push({ name: "Buy milk", done: false })
423
424
  })
424
425
  const doc2 = createDoc(yjs.bind(StructListSchema), exportEntirety(doc1))
@@ -434,8 +435,8 @@ describe("YjsSubstrate", () => {
434
435
  const unsub1 = cfName.subscribe((cs: unknown) => nameChanges.push(cs))
435
436
  const unsub2 = cfDone.subscribe((cs: unknown) => doneChanges.push(cs))
436
437
 
437
- // Update both fields in a single change() on doc1
438
- change(doc1, (d: any) => {
438
+ // Update both fields in a single batch() on doc1
439
+ batch(doc1, (d: any) => {
439
440
  const task = d.tasks.at(0)
440
441
  task.name.set("Buy oat milk")
441
442
  task.done.set(true)
@@ -462,7 +463,7 @@ describe("YjsSubstrate", () => {
462
463
  // -------------------------------------------------------------------------
463
464
 
464
465
  describe("transaction support", () => {
465
- it("multi-op change() is atomic", () => {
466
+ it("multi-op batch() is atomic", () => {
466
467
  const doc = createDoc(yjs.bind(SimpleSchema))
467
468
 
468
469
  const received: any[] = []
@@ -470,7 +471,7 @@ describe("YjsSubstrate", () => {
470
471
  received.push(changeset)
471
472
  })
472
473
 
473
- change(doc, (d: any) => {
474
+ batch(doc, (d: any) => {
474
475
  d.title.insert(0, "Hello")
475
476
  d.count.set(42)
476
477
  d.items.push("a")
@@ -501,7 +502,7 @@ describe("YjsSubstrate", () => {
501
502
  it("push struct into list, read back via navigation", () => {
502
503
  const doc = createDoc(yjs.bind(StructListSchema))
503
504
 
504
- change(doc, (d: any) => {
505
+ batch(doc, (d: any) => {
505
506
  d.tasks.push({ name: "Task 1", done: false })
506
507
  })
507
508
 
@@ -509,7 +510,7 @@ describe("YjsSubstrate", () => {
509
510
  expect((doc.tasks.at(0) as any).name()).toBe("Task 1")
510
511
  expect((doc.tasks.at(0) as any).done()).toBe(false)
511
512
 
512
- change(doc, (d: any) => {
513
+ batch(doc, (d: any) => {
513
514
  d.tasks.push({ name: "Task 2", done: true })
514
515
  })
515
516
 
@@ -520,12 +521,12 @@ describe("YjsSubstrate", () => {
520
521
 
521
522
  it("nested struct write round-trip", () => {
522
523
  const doc = createDoc(yjs.bind(FullSchema))
523
- change(doc, (d: any) => {
524
+ batch(doc, (d: any) => {
524
525
  d.meta.author.set("Alice")
525
526
  })
526
527
  expect(doc.meta.author()).toBe("Alice")
527
528
 
528
- change(doc, (d: any) => {
529
+ batch(doc, (d: any) => {
529
530
  d.meta.author.set("Bob")
530
531
  })
531
532
 
@@ -579,7 +580,7 @@ describe("YjsSubstrate", () => {
579
580
 
580
581
  it("reconstructs from snapshot with correct state", () => {
581
582
  const doc = createDoc(yjs.bind(SimpleSchema))
582
- change(doc, (d: any) => {
583
+ batch(doc, (d: any) => {
583
584
  d.title.insert(0, "Snapshot Test")
584
585
  d.count.set(77)
585
586
  d.items.push("x")
@@ -612,17 +613,17 @@ describe("YjsSubstrate", () => {
612
613
  // Re-entrant write during merge replay
613
614
  // -------------------------------------------------------------------------
614
615
  //
615
- // A subscriber that calls `change(doc, ...)` while delivering a sync
616
+ // A subscriber that calls `batch(doc, ...)` while delivering a sync
616
617
  // merge must reach Yjs — otherwise the substrate stalls and the
617
618
  // subscriber loops on stale state until the lease budget trips.
618
619
  // Context: jj:qpultxsw.
619
620
 
620
621
  describe("re-entrant write during merge replay", () => {
621
- it("subscriber's local change() inside a merge-replay batch lands in Yjs", () => {
622
+ it("subscriber's local batch() inside a merge-replay batch lands in Yjs", () => {
622
623
  const docA = createDoc(yjs.bind(SimpleSchema))
623
624
  const docB = createDoc(yjs.bind(SimpleSchema))
624
625
 
625
- change(docA, (d: any) => {
626
+ batch(docA, (d: any) => {
626
627
  d.title.insert(0, "seed")
627
628
  })
628
629
  merge(docB, exportEntirety(docA), { origin: "sync" })
@@ -634,14 +635,14 @@ describe("YjsSubstrate", () => {
634
635
  subscribe(docB.title, () => {
635
636
  if (writes === 0 && (docB.title() as string) === "seedmore") {
636
637
  writes++
637
- change(docB, (d: any) => {
638
+ batch(docB, (d: any) => {
638
639
  d.count.set(42)
639
640
  })
640
641
  }
641
642
  })
642
643
 
643
644
  const v0 = version(docB)
644
- change(docA, (d: any) => {
645
+ batch(docA, (d: any) => {
645
646
  d.title.insert((d.title() as string).length, "more")
646
647
  })
647
648
  const delta = exportSince(docA, v0)!
@@ -652,4 +653,58 @@ describe("YjsSubstrate", () => {
652
653
  expect(writes).toBe(1)
653
654
  })
654
655
  })
656
+
657
+ // -------------------------------------------------------------------------
658
+ // Origin-free discriminator tests
659
+ // -------------------------------------------------------------------------
660
+
661
+ describe("origin-free discriminator", () => {
662
+ it("options.origin survives to transaction.origin", () => {
663
+ const doc = createDoc(yjs.bind(SimpleSchema))
664
+ const native = unwrap(doc) as Y.Doc
665
+
666
+ let capturedOrigin: string | undefined = "not-called"
667
+ native.on("afterTransaction", tr => {
668
+ capturedOrigin = tr.origin
669
+ })
670
+
671
+ batch(doc, d => d.title.insert(0, "x"), { origin: "undo" })
672
+ expect(capturedOrigin).toBe("undo")
673
+ })
674
+
675
+ it("external wrapping kyneta is correctly classified as own", () => {
676
+ const doc = createDoc(yjs.bind(SimpleSchema))
677
+
678
+ let kynetaFires = 0
679
+ subscribe(doc, () => {
680
+ kynetaFires++
681
+ })
682
+
683
+ const native = unwrap(doc) as Y.Doc
684
+ native.transact(() => {
685
+ batch(doc, d => d.title.insert(0, "x"))
686
+ }, "external")
687
+
688
+ // Should fire exactly once (captured via wrappedPrepare),
689
+ // and NOT twice (the bridge should skip the external transaction
690
+ // because the inner kyneta transact marked the transaction).
691
+ expect(kynetaFires).toBe(1)
692
+ })
693
+
694
+ it("external raw transact with any string origin is bridged", () => {
695
+ const doc = createDoc(yjs.bind(SimpleSchema))
696
+
697
+ let kynetaFires = 0
698
+ subscribe(doc, () => {
699
+ kynetaFires++
700
+ })
701
+
702
+ const native = unwrap(doc) as Y.Doc
703
+ native.transact(() => {
704
+ native.getMap("root").set("title", "ext")
705
+ }, "kyneta-prepare")
706
+
707
+ expect(kynetaFires).toBe(1)
708
+ })
709
+ })
655
710
  })
package/src/bind-yjs.ts CHANGED
@@ -168,5 +168,5 @@ export const yjs: BindingTarget<YjsLaws, YjsNativeMap> = createBindingTarget<
168
168
  >({
169
169
  factory: ctx => createYjsFactory(ctx.peerId, ctx.binding),
170
170
  replicaFactory: yjsReplicaFactory,
171
- syncProtocol: SYNC_COLLABORATIVE,
171
+ syncMode: SYNC_COLLABORATIVE,
172
172
  })