@kyneta/yjs-schema 1.1.0 → 1.2.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.
@@ -0,0 +1,333 @@
1
+ // bind-constraints — compile-time and runtime tests for yjs.bind() caps enforcement.
2
+ //
3
+ // Verifies that `yjs.bind()` rejects schemas containing capabilities
4
+ // that Yjs doesn't support (counter, movable, tree, set) at COMPILE TIME
5
+ // via the `RestrictCaps` / `AllowedCaps` mechanism, while accepting
6
+ // capabilities it does support (text) and plain schemas.
7
+
8
+ import {
9
+ type BoundSchema,
10
+ type ExtractCaps,
11
+ json,
12
+ Schema,
13
+ } from "@kyneta/schema"
14
+ import { describe, expect, expectTypeOf, it } from "vitest"
15
+ import { yjs } from "../bind-yjs.js"
16
+
17
+ // ===========================================================================
18
+ // §1 — Compile-time acceptance: schemas that yjs.bind() SHOULD accept
19
+ // ===========================================================================
20
+
21
+ describe("yjs.bind() accepts Yjs-compatible schemas", () => {
22
+ it("plain schema (no caps)", () => {
23
+ const schema = Schema.struct({
24
+ name: Schema.string(),
25
+ count: Schema.number(),
26
+ active: Schema.boolean(),
27
+ })
28
+ const bound = yjs.bind(schema)
29
+ expect(bound).toBeDefined()
30
+ expect(bound.schema).toBe(schema)
31
+ expectTypeOf(bound).toMatchTypeOf<BoundSchema<typeof schema>>()
32
+ })
33
+
34
+ it("schema with text", () => {
35
+ const schema = Schema.struct({
36
+ title: Schema.text(),
37
+ })
38
+ const bound = yjs.bind(schema)
39
+ expect(bound).toBeDefined()
40
+ expect(bound.schema).toBe(schema)
41
+ })
42
+
43
+ it("schema with text + plain scalars", () => {
44
+ const schema = Schema.struct({
45
+ title: Schema.text(),
46
+ count: Schema.number(),
47
+ active: Schema.boolean(),
48
+ tags: Schema.list(Schema.string()),
49
+ })
50
+ const bound = yjs.bind(schema)
51
+ expect(bound).toBeDefined()
52
+ })
53
+
54
+ it("deeply nested text is accepted", () => {
55
+ const schema = Schema.struct({
56
+ channels: Schema.list(
57
+ Schema.struct({
58
+ meta: Schema.record(
59
+ Schema.struct({
60
+ description: Schema.text(),
61
+ }),
62
+ ),
63
+ }),
64
+ ),
65
+ })
66
+ const bound = yjs.bind(schema)
67
+ expect(bound).toBeDefined()
68
+ })
69
+
70
+ it("optional text field alongside plain scalars is accepted", () => {
71
+ const schema = Schema.struct({
72
+ title: Schema.text(),
73
+ draft: Schema.text(),
74
+ count: Schema.number(),
75
+ })
76
+ const bound = yjs.bind(schema)
77
+ expect(bound).toBeDefined()
78
+ })
79
+
80
+ it("preserves full schema type through bind()", () => {
81
+ const schema = Schema.struct({
82
+ title: Schema.text(),
83
+ items: Schema.list(
84
+ Schema.struct({ name: Schema.string(), done: Schema.boolean() }),
85
+ ),
86
+ })
87
+ const bound = yjs.bind(schema)
88
+ expectTypeOf(bound.schema).toEqualTypeOf(schema)
89
+ })
90
+
91
+ it("json merge boundary is accepted", () => {
92
+ const schema = Schema.struct({
93
+ title: Schema.text(),
94
+ metadata: Schema.struct.json({
95
+ version: Schema.number(),
96
+ tags: Schema.list(Schema.string()),
97
+ }),
98
+ })
99
+ const bound = yjs.bind(schema)
100
+ expect(bound).toBeDefined()
101
+ })
102
+ })
103
+
104
+ // ===========================================================================
105
+ // §2 — Compile-time rejection: schemas that yjs.bind() SHOULD reject
106
+ // ===========================================================================
107
+
108
+ describe("yjs.bind() rejects schemas with unsupported caps", () => {
109
+ it("rejects counter", () => {
110
+ const schema = Schema.struct({
111
+ count: Schema.counter(),
112
+ })
113
+ // @ts-expect-error — counter is not in YjsCaps
114
+ yjs.bind(schema)
115
+ })
116
+
117
+ it("rejects movableList", () => {
118
+ const schema = Schema.struct({
119
+ items: Schema.movableList(Schema.string()),
120
+ })
121
+ // @ts-expect-error — movable is not in YjsCaps
122
+ yjs.bind(schema)
123
+ })
124
+
125
+ it("rejects tree", () => {
126
+ const schema = Schema.struct({
127
+ hierarchy: Schema.tree(
128
+ Schema.struct({ label: Schema.string() }),
129
+ ),
130
+ })
131
+ // @ts-expect-error — tree is not in YjsCaps
132
+ yjs.bind(schema)
133
+ })
134
+
135
+ it("rejects set", () => {
136
+ const schema = Schema.struct({
137
+ tags: Schema.set(Schema.string()),
138
+ })
139
+ // @ts-expect-error — set is not in YjsCaps
140
+ yjs.bind(schema)
141
+ })
142
+
143
+ it("rejects deeply nested counter", () => {
144
+ const schema = Schema.struct({
145
+ items: Schema.list(
146
+ Schema.struct({
147
+ meta: Schema.record(
148
+ Schema.struct({ hits: Schema.counter() }),
149
+ ),
150
+ }),
151
+ ),
152
+ })
153
+ // @ts-expect-error — counter is deeply nested but still caught
154
+ yjs.bind(schema)
155
+ })
156
+
157
+ it("rejects mix of supported and unsupported (text + counter)", () => {
158
+ const schema = Schema.struct({
159
+ title: Schema.text(),
160
+ views: Schema.counter(),
161
+ })
162
+ // @ts-expect-error — counter is not in YjsCaps
163
+ yjs.bind(schema)
164
+ })
165
+
166
+ it("rejects counter inside list", () => {
167
+ const schema = Schema.struct({
168
+ scores: Schema.list(
169
+ Schema.struct({ value: Schema.counter() }),
170
+ ),
171
+ })
172
+ // @ts-expect-error — counter nested inside list struct
173
+ yjs.bind(schema)
174
+ })
175
+ })
176
+
177
+ // ===========================================================================
178
+ // §3 — Cross-substrate: same schema, different bind targets
179
+ // ===========================================================================
180
+
181
+ describe("cross-substrate: universal schema vs substrate-specific schema", () => {
182
+ // A schema using only universally-supported features
183
+ const universalSchema = Schema.struct({
184
+ title: Schema.text(),
185
+ items: Schema.list(
186
+ Schema.struct({
187
+ name: Schema.string(),
188
+ done: Schema.boolean(),
189
+ }),
190
+ ),
191
+ })
192
+
193
+ // A schema using Loro-specific features (counter, movable)
194
+ const loroSpecificSchema = Schema.struct({
195
+ title: Schema.text(),
196
+ count: Schema.counter(),
197
+ tasks: Schema.movableList(
198
+ Schema.struct({ name: Schema.string() }),
199
+ ),
200
+ })
201
+
202
+ it("universal schema is Yjs-compatible (ExtractCaps check)", () => {
203
+ type Caps = ExtractCaps<typeof universalSchema>
204
+ // Only "text" — in YjsCaps
205
+ expectTypeOf<Caps>().toEqualTypeOf<"text">()
206
+ })
207
+
208
+ it("Loro-specific schema is NOT Yjs-compatible (ExtractCaps check)", () => {
209
+ type Caps = ExtractCaps<typeof loroSpecificSchema>
210
+ // Includes "counter" and "movable" which are NOT in YjsCaps
211
+ expectTypeOf<Caps>().toEqualTypeOf<"text" | "counter" | "movable">()
212
+ })
213
+
214
+ it("universal schema binds to yjs", () => {
215
+ const bound = yjs.bind(universalSchema)
216
+ expect(bound).toBeDefined()
217
+ expect(bound.schema).toBe(universalSchema)
218
+ })
219
+
220
+ it("Loro-specific schema is rejected by yjs.bind()", () => {
221
+ // @ts-expect-error — counter and movable not in YjsCaps
222
+ yjs.bind(loroSpecificSchema)
223
+ })
224
+
225
+ it("json.bind() accepts schemas with all caps (AllowedCaps = string)", () => {
226
+ const schema = Schema.struct({
227
+ title: Schema.text(),
228
+ count: Schema.counter(),
229
+ tasks: Schema.movableList(Schema.string()),
230
+ })
231
+ const bound = json.bind(schema)
232
+ expect(bound).toBeDefined()
233
+ expect(bound.schema).toBe(schema)
234
+ })
235
+ })
236
+
237
+ // ===========================================================================
238
+ // §4 — Edge cases: discriminated unions, multiple text fields
239
+ // ===========================================================================
240
+
241
+ describe("bind constraint edge cases", () => {
242
+ it("discriminated union with all-plain variants is accepted", () => {
243
+ const schema = Schema.struct({
244
+ content: Schema.discriminatedUnion("type", [
245
+ Schema.struct({
246
+ type: Schema.string("text"),
247
+ body: Schema.string(),
248
+ }),
249
+ Schema.struct({
250
+ type: Schema.string("image"),
251
+ url: Schema.string(),
252
+ }),
253
+ ]),
254
+ })
255
+ const bound = yjs.bind(schema)
256
+ expect(bound).toBeDefined()
257
+ })
258
+
259
+ it("struct with counter alongside plain variants is rejected", () => {
260
+ const schema = Schema.struct({
261
+ content: Schema.discriminatedUnion("type", [
262
+ Schema.struct({
263
+ type: Schema.string("text"),
264
+ body: Schema.string(),
265
+ }),
266
+ Schema.struct({
267
+ type: Schema.string("image"),
268
+ url: Schema.string(),
269
+ }),
270
+ ]),
271
+ hits: Schema.counter(),
272
+ })
273
+ // @ts-expect-error — counter taints the whole schema
274
+ yjs.bind(schema)
275
+ })
276
+
277
+ it("multiple text fields are all accepted", () => {
278
+ const schema = Schema.struct({
279
+ title: Schema.text(),
280
+ body: Schema.text(),
281
+ summary: Schema.text(),
282
+ })
283
+ const bound = yjs.bind(schema)
284
+ expect(bound).toBeDefined()
285
+ })
286
+
287
+ it("plain-only schema (no caps at all) is accepted", () => {
288
+ const schema = Schema.struct({
289
+ name: Schema.string(),
290
+ age: Schema.number(),
291
+ active: Schema.boolean(),
292
+ tags: Schema.list(Schema.string()),
293
+ address: Schema.struct({
294
+ street: Schema.string(),
295
+ city: Schema.string(),
296
+ }),
297
+ metadata: Schema.record(Schema.any()),
298
+ })
299
+ const bound = yjs.bind(schema)
300
+ expect(bound).toBeDefined()
301
+ })
302
+ })
303
+
304
+ // ===========================================================================
305
+ // §5 — Root kind rejection: bind() requires a product (struct) root
306
+ // ===========================================================================
307
+
308
+ describe("yjs.bind() rejects non-product root schemas", () => {
309
+ it("rejects bare list at root", () => {
310
+ // @ts-expect-error — SequenceSchema is not ProductSchema
311
+ yjs.bind(Schema.list(Schema.string()))
312
+ })
313
+
314
+ it("rejects bare record at root", () => {
315
+ // @ts-expect-error — MapSchema is not ProductSchema
316
+ yjs.bind(Schema.record(Schema.string()))
317
+ })
318
+
319
+ it("rejects bare text at root", () => {
320
+ // @ts-expect-error — TextSchema is not ProductSchema
321
+ yjs.bind(Schema.text())
322
+ })
323
+
324
+ it("rejects bare scalar at root", () => {
325
+ // @ts-expect-error — ScalarSchema is not ProductSchema
326
+ yjs.bind(Schema.string())
327
+ })
328
+
329
+ it("rejects list of structs at root", () => {
330
+ // @ts-expect-error — SequenceSchema<ProductSchema> is still not ProductSchema
331
+ yjs.bind(Schema.list(Schema.struct({ name: Schema.string() })))
332
+ })
333
+ })
@@ -1,17 +1,15 @@
1
1
  import { change, RawPath, Schema } from "@kyneta/schema"
2
2
  import { describe, expect, it } from "vitest"
3
3
  import * as Y from "yjs"
4
- import { bindYjs } from "../bind-yjs.js"
4
+ import { yjs } from "../bind-yjs.js"
5
5
  import { createYjsDoc } from "../create.js"
6
- import { yjsSubstrateFactory } from "../substrate.js"
7
- import { yjs } from "../yjs-escape.js"
8
6
 
9
7
  // ===========================================================================
10
8
  // Schemas used across tests
11
9
  // ===========================================================================
12
10
 
13
- const TodoSchema = Schema.doc({
14
- title: Schema.annotated("text"),
11
+ const TodoSchema = Schema.struct({
12
+ title: Schema.text(),
15
13
  items: Schema.list(
16
14
  Schema.struct({
17
15
  name: Schema.string(),
@@ -20,8 +18,8 @@ const TodoSchema = Schema.doc({
20
18
  ),
21
19
  })
22
20
 
23
- const SimpleSchema = Schema.doc({
24
- title: Schema.annotated("text"),
21
+ const SimpleSchema = Schema.struct({
22
+ title: Schema.text(),
25
23
  count: Schema.number(),
26
24
  })
27
25
 
@@ -29,26 +27,26 @@ const SimpleSchema = Schema.doc({
29
27
  // Tests
30
28
  // ===========================================================================
31
29
 
32
- describe("bindYjs", () => {
30
+ describe("yjs.bind", () => {
33
31
  // -------------------------------------------------------------------------
34
32
  // BoundSchema shape
35
33
  // -------------------------------------------------------------------------
36
34
 
37
35
  describe("BoundSchema", () => {
38
- it("creates BoundSchema with causal strategy", () => {
39
- const bound = bindYjs(TodoSchema)
36
+ it("creates BoundSchema with collaborative strategy", () => {
37
+ const bound = yjs.bind(TodoSchema)
40
38
  expect(bound._brand).toBe("BoundSchema")
41
39
  expect(bound.schema).toBe(TodoSchema)
42
- expect(bound.strategy).toBe("causal")
40
+ expect(bound.strategy).toBe("collaborative")
43
41
  })
44
42
 
45
43
  it("has a factory builder function", () => {
46
- const bound = bindYjs(TodoSchema)
44
+ const bound = yjs.bind(TodoSchema)
47
45
  expect(typeof bound.factory).toBe("function")
48
46
  })
49
47
 
50
48
  it("preserves the schema reference", () => {
51
- const bound = bindYjs(SimpleSchema)
49
+ const bound = yjs.bind(SimpleSchema)
52
50
  expect(bound.schema).toBe(SimpleSchema)
53
51
  })
54
52
  })
@@ -59,10 +57,10 @@ describe("bindYjs", () => {
59
57
 
60
58
  describe("factory builder", () => {
61
59
  it("produces a working SubstrateFactory", () => {
62
- const bound = bindYjs(SimpleSchema)
60
+ const bound = yjs.bind(SimpleSchema)
63
61
  const factory = bound.factory({ peerId: "peer-1" })
64
62
 
65
- const substrate = factory.create(SimpleSchema)
63
+ const _substrate = factory.create(SimpleSchema)
66
64
 
67
65
  // Populate via the substrate's writable context
68
66
  const doc = createYjsDocFromFactory(factory, SimpleSchema)
@@ -76,7 +74,7 @@ describe("bindYjs", () => {
76
74
  })
77
75
 
78
76
  it("factory supports fromEntirety", () => {
79
- const bound = bindYjs(SimpleSchema)
77
+ const bound = yjs.bind(SimpleSchema)
80
78
  const factory = bound.factory({ peerId: "peer-1" })
81
79
 
82
80
  // Create and populate
@@ -95,7 +93,7 @@ describe("bindYjs", () => {
95
93
  })
96
94
 
97
95
  it("factory supports parseVersion", () => {
98
- const bound = bindYjs(SimpleSchema)
96
+ const bound = yjs.bind(SimpleSchema)
99
97
  const factory = bound.factory({ peerId: "peer-1" })
100
98
 
101
99
  const substrate = factory.create(SimpleSchema)
@@ -112,34 +110,34 @@ describe("bindYjs", () => {
112
110
 
113
111
  describe("deterministic clientID", () => {
114
112
  it("same peerId produces same clientID across multiple factory calls", () => {
115
- const bound = bindYjs(SimpleSchema)
113
+ const bound = yjs.bind(SimpleSchema)
116
114
  const factory = bound.factory({ peerId: "stable-peer-id" })
117
115
 
118
- const s1 = factory.create(SimpleSchema)
119
- const s2 = factory.create(SimpleSchema)
116
+ const _s1 = factory.create(SimpleSchema)
117
+ const _s2 = factory.create(SimpleSchema)
120
118
 
121
119
  // Both docs should have the same clientID
122
- const doc1 = yjs(createYjsDocFromFactory(factory, SimpleSchema))
123
- const doc2 = yjs(createYjsDocFromFactory(factory, SimpleSchema))
120
+ const doc1 = yjs.unwrap(createYjsDocFromFactory(factory, SimpleSchema))
121
+ const doc2 = yjs.unwrap(createYjsDocFromFactory(factory, SimpleSchema))
124
122
 
125
123
  expect(doc1.clientID).toBe(doc2.clientID)
126
124
  })
127
125
 
128
126
  it("different peerIds produce different clientIDs", () => {
129
- const bound = bindYjs(SimpleSchema)
127
+ const bound = yjs.bind(SimpleSchema)
130
128
  const factory1 = bound.factory({ peerId: "peer-alpha" })
131
129
  const factory2 = bound.factory({ peerId: "peer-beta" })
132
130
 
133
- const doc1 = yjs(createYjsDocFromFactory(factory1, SimpleSchema))
134
- const doc2 = yjs(createYjsDocFromFactory(factory2, SimpleSchema))
131
+ const doc1 = yjs.unwrap(createYjsDocFromFactory(factory1, SimpleSchema))
132
+ const doc2 = yjs.unwrap(createYjsDocFromFactory(factory2, SimpleSchema))
135
133
 
136
134
  expect(doc1.clientID).not.toBe(doc2.clientID)
137
135
  })
138
136
 
139
137
  it("clientID is a valid uint32", () => {
140
- const bound = bindYjs(SimpleSchema)
138
+ const bound = yjs.bind(SimpleSchema)
141
139
  const factory = bound.factory({ peerId: "test-peer-id-12345" })
142
- const doc = yjs(createYjsDocFromFactory(factory, SimpleSchema))
140
+ const doc = yjs.unwrap(createYjsDocFromFactory(factory, SimpleSchema))
143
141
 
144
142
  expect(typeof doc.clientID).toBe("number")
145
143
  expect(doc.clientID).toBeGreaterThanOrEqual(0)
@@ -151,13 +149,13 @@ describe("bindYjs", () => {
151
149
  const peerId = "deterministic-check-peer"
152
150
 
153
151
  // Simulate two separate "sessions" — both should hash to the same value
154
- const bound1 = bindYjs(SimpleSchema)
152
+ const bound1 = yjs.bind(SimpleSchema)
155
153
  const factory1 = bound1.factory({ peerId })
156
- const doc1 = yjs(createYjsDocFromFactory(factory1, SimpleSchema))
154
+ const doc1 = yjs.unwrap(createYjsDocFromFactory(factory1, SimpleSchema))
157
155
 
158
- const bound2 = bindYjs(SimpleSchema)
156
+ const bound2 = yjs.bind(SimpleSchema)
159
157
  const factory2 = bound2.factory({ peerId })
160
- const doc2 = yjs(createYjsDocFromFactory(factory2, SimpleSchema))
158
+ const doc2 = yjs.unwrap(createYjsDocFromFactory(factory2, SimpleSchema))
161
159
 
162
160
  expect(doc1.clientID).toBe(doc2.clientID)
163
161
  })
@@ -167,14 +165,14 @@ describe("bindYjs", () => {
167
165
  // yjs() escape hatch
168
166
  // -------------------------------------------------------------------------
169
167
 
170
- describe("yjs() escape hatch", () => {
168
+ describe("yjs.unwrap() escape hatch", () => {
171
169
  it("returns the underlying Y.Doc from a createYjsDoc ref", () => {
172
170
  const doc = createYjsDoc(SimpleSchema)
173
171
  change(doc, (d: any) => {
174
172
  d.title.insert(0, "Escape")
175
173
  d.count.set(0)
176
174
  })
177
- const yjsDoc = yjs(doc)
175
+ const yjsDoc = yjs.unwrap(doc)
178
176
 
179
177
  expect(yjsDoc).toBeInstanceOf(Y.Doc)
180
178
  expect(yjsDoc.getMap("root").get("count")).toBe(0)
@@ -186,7 +184,7 @@ describe("bindYjs", () => {
186
184
  d.title.insert(0, "Hello")
187
185
  d.count.set(42)
188
186
  })
189
- const yjsDoc = yjs(doc)
187
+ const yjsDoc = yjs.unwrap(doc)
190
188
  const rootMap = yjsDoc.getMap("root")
191
189
 
192
190
  expect((rootMap.get("title") as Y.Text).toJSON()).toBe("Hello")
@@ -194,7 +192,7 @@ describe("bindYjs", () => {
194
192
  })
195
193
 
196
194
  it("throws for non-Yjs refs (plain object)", () => {
197
- expect(() => yjs({})).toThrow("yjs() requires a ref")
195
+ expect(() => yjs.unwrap({})).toThrow("yjs.unwrap() requires a ref")
198
196
  })
199
197
 
200
198
  it("throws for non-Yjs refs (random object with properties)", () => {
@@ -202,12 +200,12 @@ describe("bindYjs", () => {
202
200
  title: () => "fake",
203
201
  count: () => 0,
204
202
  }
205
- expect(() => yjs(fake)).toThrow("yjs() requires a ref")
203
+ expect(() => yjs.unwrap(fake)).toThrow("yjs.unwrap() requires a ref")
206
204
  })
207
205
 
208
206
  it("mutations through escape hatch are visible via kyneta ref", () => {
209
207
  const doc = createYjsDoc(SimpleSchema)
210
- const yjsDoc = yjs(doc)
208
+ const yjsDoc = yjs.unwrap(doc)
211
209
 
212
210
  // Mutate via raw Yjs
213
211
  yjsDoc.getMap("root").set("count", 99)
@@ -219,7 +217,7 @@ describe("bindYjs", () => {
219
217
  change(doc, (d: any) => {
220
218
  d.title.insert(0, "Hello")
221
219
  })
222
- const yjsDoc = yjs(doc)
220
+ const yjsDoc = yjs.unwrap(doc)
223
221
 
224
222
  const text = yjsDoc.getMap("root").get("title") as Y.Text
225
223
  text.insert(5, " World")
@@ -234,8 +232,8 @@ describe("bindYjs", () => {
234
232
 
235
233
  import type { Schema as SchemaType, SubstrateFactory } from "@kyneta/schema"
236
234
  import {
237
- changefeed,
238
235
  interpret,
236
+ observation,
239
237
  readable,
240
238
  registerSubstrate,
241
239
  unwrap,
@@ -254,7 +252,7 @@ function createYjsDocFromFactory(
254
252
  const doc: any = (interpret as any)(schema, substrate.context())
255
253
  .with(readable)
256
254
  .with(writable)
257
- .with(changefeed)
255
+ .with(observation)
258
256
  .done()
259
257
 
260
258
  // Register for escape hatch — createYjsSubstrate already registered
@@ -1,24 +1,23 @@
1
1
  import { change, Schema, subscribe } from "@kyneta/schema"
2
- import { describe, expect, it, vi } from "vitest"
2
+ import { describe, expect, it } from "vitest"
3
3
  import * as Y from "yjs"
4
+ import { yjs } from "../bind-yjs.js"
4
5
  import { createYjsDoc, createYjsDocFromEntirety } from "../create.js"
5
6
  import { ensureContainers } from "../populate.js"
6
- import { yjsSubstrateFactory } from "../substrate.js"
7
7
  import { exportEntirety, exportSince, merge, version } from "../sync.js"
8
8
  import { YjsVersion } from "../version.js"
9
- import { yjs } from "../yjs-escape.js"
10
9
 
11
10
  // ===========================================================================
12
11
  // Schemas used across tests
13
12
  // ===========================================================================
14
13
 
15
- const SimpleSchema = Schema.doc({
16
- title: Schema.annotated("text"),
14
+ const SimpleSchema = Schema.struct({
15
+ title: Schema.text(),
17
16
  count: Schema.number(),
18
17
  items: Schema.list(Schema.string()),
19
18
  })
20
19
 
21
- const StructListSchema = Schema.doc({
20
+ const StructListSchema = Schema.struct({
22
21
  tasks: Schema.list(
23
22
  Schema.struct({
24
23
  name: Schema.string(),
@@ -27,8 +26,8 @@ const StructListSchema = Schema.doc({
27
26
  ),
28
27
  })
29
28
 
30
- const NestedSchema = Schema.doc({
31
- title: Schema.annotated("text"),
29
+ const NestedSchema = Schema.struct({
30
+ title: Schema.text(),
32
31
  meta: Schema.struct({
33
32
  author: Schema.string(),
34
33
  tags: Schema.list(Schema.string()),
@@ -179,12 +178,12 @@ describe("createYjsDoc", () => {
179
178
  expect(doc.count()).toBe(77)
180
179
  })
181
180
 
182
- it("yjs() escape hatch returns the same Y.Doc", () => {
181
+ it("yjs.unwrap() escape hatch returns the same Y.Doc", () => {
183
182
  const yjsDoc = new Y.Doc()
184
183
  ensureContainers(yjsDoc, SimpleSchema)
185
184
 
186
185
  const doc = createYjsDoc(SimpleSchema, yjsDoc)
187
- const escaped = yjs(doc)
186
+ const escaped = yjs.unwrap(doc)
188
187
 
189
188
  expect(escaped).toBe(yjsDoc)
190
189
  })
@@ -373,7 +372,7 @@ describe("sync primitives", () => {
373
372
  // Export delta and apply to doc2
374
373
  const delta = exportSince(doc1, v2Before)
375
374
  expect(delta).not.toBeNull()
376
- expect(delta!.encoding).toBe("binary")
375
+ expect(delta?.encoding).toBe("binary")
377
376
 
378
377
  merge(doc2, delta!)
379
378