@kyneta/yjs-schema 1.1.0 → 1.3.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.
@@ -1,10 +1,20 @@
1
- import { change, RawPath, Schema, subscribe } from "@kyneta/schema"
2
- import { describe, expect, it, vi } from "vitest"
1
+ import {
2
+ change,
3
+ createDoc,
4
+ createRef,
5
+ exportEntirety,
6
+ exportSince,
7
+ merge,
8
+ RawPath,
9
+ Schema,
10
+ subscribe,
11
+ version,
12
+ } from "@kyneta/schema"
13
+ import { describe, expect, it } from "vitest"
3
14
  import * as Y from "yjs"
4
- import { createYjsDoc, createYjsDocFromEntirety } from "../create.js"
15
+ import { yjs } from "../bind-yjs.js"
5
16
  import { ensureContainers } from "../populate.js"
6
17
  import { createYjsSubstrate, yjsSubstrateFactory } from "../substrate.js"
7
- import { exportEntirety, exportSince, merge, version } from "../sync.js"
8
18
  import { YjsVersion } from "../version.js"
9
19
 
10
20
  // ===========================================================================
@@ -15,13 +25,13 @@ import { YjsVersion } from "../version.js"
15
25
  // Schemas used across tests
16
26
  // ===========================================================================
17
27
 
18
- const SimpleSchema = Schema.doc({
19
- title: Schema.annotated("text"),
28
+ const SimpleSchema = Schema.struct({
29
+ title: Schema.text(),
20
30
  count: Schema.number(),
21
31
  items: Schema.list(Schema.string()),
22
32
  })
23
33
 
24
- const StructListSchema = Schema.doc({
34
+ const StructListSchema = Schema.struct({
25
35
  tasks: Schema.list(
26
36
  Schema.struct({
27
37
  name: Schema.string(),
@@ -30,8 +40,8 @@ const StructListSchema = Schema.doc({
30
40
  ),
31
41
  })
32
42
 
33
- const FullSchema = Schema.doc({
34
- title: Schema.annotated("text"),
43
+ const FullSchema = Schema.struct({
44
+ title: Schema.text(),
35
45
  count: Schema.number(),
36
46
  active: Schema.boolean(),
37
47
  items: Schema.list(Schema.string()),
@@ -66,7 +76,7 @@ describe("YjsSubstrate", () => {
66
76
  })
67
77
 
68
78
  it("creates a substrate and populates via change()", () => {
69
- const doc = createYjsDoc(SimpleSchema)
79
+ const doc = createDoc(yjs.bind(SimpleSchema))
70
80
  change(doc, (d: any) => {
71
81
  d.title.insert(0, "Hello")
72
82
  d.count.set(42)
@@ -81,7 +91,7 @@ describe("YjsSubstrate", () => {
81
91
  })
82
92
 
83
93
  it("creates a substrate with partial values (unset fields stay empty)", () => {
84
- const doc = createYjsDoc(SimpleSchema)
94
+ const doc = createDoc(yjs.bind(SimpleSchema))
85
95
  change(doc, (d: any) => {
86
96
  d.title.insert(0, "Partial")
87
97
  })
@@ -91,7 +101,7 @@ describe("YjsSubstrate", () => {
91
101
  })
92
102
 
93
103
  it("creates a substrate with nested struct values via change()", () => {
94
- const doc = createYjsDoc(FullSchema)
104
+ const doc = createDoc(yjs.bind(FullSchema))
95
105
  change(doc, (d: any) => {
96
106
  d.meta.author.set("Alice")
97
107
  })
@@ -99,7 +109,7 @@ describe("YjsSubstrate", () => {
99
109
  })
100
110
 
101
111
  it("creates a substrate with struct list values via change()", () => {
102
- const doc = createYjsDoc(StructListSchema)
112
+ const doc = createDoc(yjs.bind(StructListSchema))
103
113
  // Separate change() calls for list pushes to preserve order
104
114
  change(doc, (d: any) => d.tasks.push({ name: "Task 1", done: false }))
105
115
  change(doc, (d: any) => d.tasks.push({ name: "Task 2", done: true }))
@@ -114,7 +124,7 @@ describe("YjsSubstrate", () => {
114
124
 
115
125
  describe("write round-trip", () => {
116
126
  it("text insert round-trips through prepare/flush", () => {
117
- const doc = createYjsDoc(SimpleSchema)
127
+ const doc = createDoc(yjs.bind(SimpleSchema))
118
128
  change(doc, (d: any) => {
119
129
  d.title.insert(0, "Hello")
120
130
  })
@@ -122,7 +132,7 @@ describe("YjsSubstrate", () => {
122
132
  })
123
133
 
124
134
  it("scalar set round-trips through prepare/flush", () => {
125
- const doc = createYjsDoc(SimpleSchema)
135
+ const doc = createDoc(yjs.bind(SimpleSchema))
126
136
  change(doc, (d: any) => {
127
137
  d.count.set(42)
128
138
  })
@@ -130,7 +140,7 @@ describe("YjsSubstrate", () => {
130
140
  })
131
141
 
132
142
  it("list push round-trips through prepare/flush", () => {
133
- const doc = createYjsDoc(SimpleSchema)
143
+ const doc = createDoc(yjs.bind(SimpleSchema))
134
144
  change(doc, (d: any) => {
135
145
  d.items.push("a")
136
146
  })
@@ -148,7 +158,7 @@ describe("YjsSubstrate", () => {
148
158
 
149
159
  describe("version tracking", () => {
150
160
  it("version advances after mutations", () => {
151
- const doc = createYjsDoc(SimpleSchema)
161
+ const doc = createDoc(yjs.bind(SimpleSchema))
152
162
  const v1 = version(doc)
153
163
 
154
164
  change(doc, (d: any) => {
@@ -161,7 +171,7 @@ describe("YjsSubstrate", () => {
161
171
  })
162
172
 
163
173
  it("version serialize/parse round-trips", () => {
164
- const doc = createYjsDoc(SimpleSchema)
174
+ const doc = createDoc(yjs.bind(SimpleSchema))
165
175
  change(doc, (d: any) => {
166
176
  d.title.insert(0, "Test")
167
177
  d.count.set(5)
@@ -180,7 +190,7 @@ describe("YjsSubstrate", () => {
180
190
 
181
191
  describe("export/import snapshot", () => {
182
192
  it("exports a binary payload", () => {
183
- const doc = createYjsDoc(SimpleSchema)
193
+ const doc = createDoc(yjs.bind(SimpleSchema))
184
194
  change(doc, (d: any) => {
185
195
  d.title.insert(0, "Snapshot")
186
196
  })
@@ -190,7 +200,7 @@ describe("YjsSubstrate", () => {
190
200
  })
191
201
 
192
202
  it("reconstructs equivalent state from snapshot", () => {
193
- const doc1 = createYjsDoc(SimpleSchema)
203
+ const doc1 = createDoc(yjs.bind(SimpleSchema))
194
204
  change(doc1, (d: any) => {
195
205
  d.title.insert(0, "Hello")
196
206
  d.count.set(42)
@@ -203,7 +213,7 @@ describe("YjsSubstrate", () => {
203
213
  })
204
214
 
205
215
  const payload = exportEntirety(doc1)
206
- const doc2 = createYjsDocFromEntirety(SimpleSchema, payload)
216
+ const doc2 = createDoc(yjs.bind(SimpleSchema), payload)
207
217
 
208
218
  expect(doc2.title()).toBe("Hello World")
209
219
  expect(doc2.count()).toBe(42)
@@ -217,11 +227,11 @@ describe("YjsSubstrate", () => {
217
227
 
218
228
  describe("delta sync", () => {
219
229
  it("exportSince → merge syncs state", () => {
220
- const doc1 = createYjsDoc(SimpleSchema)
230
+ const doc1 = createDoc(yjs.bind(SimpleSchema))
221
231
  change(doc1, (d: any) => {
222
232
  d.title.insert(0, "Start")
223
233
  })
224
- const doc2 = createYjsDocFromEntirety(SimpleSchema, exportEntirety(doc1))
234
+ const doc2 = createDoc(yjs.bind(SimpleSchema), exportEntirety(doc1))
225
235
 
226
236
  const v1Before = version(doc1)
227
237
 
@@ -239,8 +249,8 @@ describe("YjsSubstrate", () => {
239
249
  })
240
250
 
241
251
  it("concurrent sync — two substrates converge after bidirectional sync", () => {
242
- const doc1 = createYjsDoc(SimpleSchema)
243
- const doc2 = createYjsDocFromEntirety(SimpleSchema, exportEntirety(doc1))
252
+ const doc1 = createDoc(yjs.bind(SimpleSchema))
253
+ const doc2 = createDoc(yjs.bind(SimpleSchema), exportEntirety(doc1))
244
254
 
245
255
  const v1Before = version(doc1)
246
256
  const v2Before = version(doc2)
@@ -285,11 +295,11 @@ describe("YjsSubstrate", () => {
285
295
 
286
296
  describe("changefeed", () => {
287
297
  it("fires on merge", () => {
288
- const doc1 = createYjsDoc(SimpleSchema)
298
+ const doc1 = createDoc(yjs.bind(SimpleSchema))
289
299
  change(doc1, (d: any) => {
290
300
  d.title.insert(0, "A")
291
301
  })
292
- const doc2 = createYjsDocFromEntirety(SimpleSchema, exportEntirety(doc1))
302
+ const doc2 = createDoc(yjs.bind(SimpleSchema), exportEntirety(doc1))
293
303
 
294
304
  const v2Before = version(doc2)
295
305
 
@@ -312,7 +322,10 @@ describe("YjsSubstrate", () => {
312
322
  it("fires on external Y.Doc mutation (raw Yjs API)", () => {
313
323
  const yjsDoc = new Y.Doc()
314
324
  ensureContainers(yjsDoc, SimpleSchema)
315
- const doc = createYjsDoc(SimpleSchema, yjsDoc)
325
+ const doc = createRef(
326
+ SimpleSchema,
327
+ createYjsSubstrate(yjsDoc, SimpleSchema),
328
+ )
316
329
 
317
330
  const received: any[] = []
318
331
  subscribe(doc, (changeset: any) => {
@@ -328,7 +341,7 @@ describe("YjsSubstrate", () => {
328
341
  })
329
342
 
330
343
  it("no double-fire on kyneta local writes", () => {
331
- const doc = createYjsDoc(SimpleSchema)
344
+ const doc = createDoc(yjs.bind(SimpleSchema))
332
345
 
333
346
  const received: any[] = []
334
347
  subscribe(doc, (changeset: any) => {
@@ -345,18 +358,15 @@ describe("YjsSubstrate", () => {
345
358
  })
346
359
 
347
360
  it("nested struct field changefeed fires on merge", () => {
348
- const doc1 = createYjsDoc(StructListSchema)
349
- const doc2 = createYjsDocFromEntirety(
350
- StructListSchema,
351
- exportEntirety(doc1),
352
- )
361
+ const doc1 = createDoc(yjs.bind(StructListSchema))
362
+ const _doc2 = createDoc(yjs.bind(StructListSchema), exportEntirety(doc1))
353
363
 
354
364
  // Add a struct item on doc1, sync to doc2
355
365
  change(doc1, (d: any) => {
356
366
  d.tasks.push({ name: "Buy milk", done: false })
357
367
  })
358
368
  const snap = exportEntirety(doc1)
359
- const doc2b = createYjsDocFromEntirety(StructListSchema, snap)
369
+ const doc2b = createDoc(yjs.bind(StructListSchema), snap)
360
370
 
361
371
  const taskB = [...doc2b.tasks][0] as any
362
372
  expect(taskB.done()).toBe(false)
@@ -387,16 +397,13 @@ describe("YjsSubstrate", () => {
387
397
  })
388
398
 
389
399
  it("multi-key struct update fires per-field changefeeds on merge", () => {
390
- const doc1 = createYjsDoc(StructListSchema)
400
+ const doc1 = createDoc(yjs.bind(StructListSchema))
391
401
 
392
402
  // Add a struct item, sync to doc2
393
403
  change(doc1, (d: any) => {
394
404
  d.tasks.push({ name: "Buy milk", done: false })
395
405
  })
396
- const doc2 = createYjsDocFromEntirety(
397
- StructListSchema,
398
- exportEntirety(doc1),
399
- )
406
+ const doc2 = createDoc(yjs.bind(StructListSchema), exportEntirety(doc1))
400
407
 
401
408
  const taskB = [...doc2.tasks][0] as any
402
409
  const v2 = version(doc2)
@@ -438,7 +445,7 @@ describe("YjsSubstrate", () => {
438
445
 
439
446
  describe("transaction support", () => {
440
447
  it("multi-op change() is atomic", () => {
441
- const doc = createYjsDoc(SimpleSchema)
448
+ const doc = createDoc(yjs.bind(SimpleSchema))
442
449
 
443
450
  const received: any[] = []
444
451
  subscribe(doc, (changeset: any) => {
@@ -474,7 +481,7 @@ describe("YjsSubstrate", () => {
474
481
 
475
482
  describe("nested structure", () => {
476
483
  it("push struct into list, read back via navigation", () => {
477
- const doc = createYjsDoc(StructListSchema)
484
+ const doc = createDoc(yjs.bind(StructListSchema))
478
485
 
479
486
  change(doc, (d: any) => {
480
487
  d.tasks.push({ name: "Task 1", done: false })
@@ -494,7 +501,7 @@ describe("YjsSubstrate", () => {
494
501
  })
495
502
 
496
503
  it("nested struct write round-trip", () => {
497
- const doc = createYjsDoc(FullSchema)
504
+ const doc = createDoc(yjs.bind(FullSchema))
498
505
  change(doc, (d: any) => {
499
506
  d.meta.author.set("Alice")
500
507
  })
@@ -512,29 +519,26 @@ describe("YjsSubstrate", () => {
512
519
  // Counter annotation throws
513
520
  // -------------------------------------------------------------------------
514
521
 
515
- describe("unsupported annotations", () => {
516
- it("counter annotation throws clear error at construction", () => {
517
- const CounterSchema = Schema.doc({
518
- count: Schema.annotated("counter"),
522
+ describe("unsupported kinds", () => {
523
+ it("counter throws clear error at construction", () => {
524
+ const CounterSchema = Schema.struct({
525
+ count: Schema.counter(),
519
526
  })
520
527
 
521
528
  expect(() => yjsSubstrateFactory.create(CounterSchema)).toThrow("counter")
522
529
  })
523
530
 
524
- it("movable annotation throws clear error at construction", () => {
525
- const MovableSchema = Schema.doc({
526
- items: Schema.annotated("movable", Schema.list(Schema.string())),
531
+ it("movableList throws clear error at construction", () => {
532
+ const MovableSchema = Schema.struct({
533
+ items: Schema.movableList(Schema.string()),
527
534
  })
528
535
 
529
536
  expect(() => yjsSubstrateFactory.create(MovableSchema)).toThrow("movable")
530
537
  })
531
538
 
532
- it("tree annotation throws clear error at construction", () => {
533
- const TreeSchema = Schema.doc({
534
- tree: Schema.annotated(
535
- "tree",
536
- Schema.struct({ label: Schema.string() }),
537
- ),
539
+ it("tree throws clear error at construction", () => {
540
+ const TreeSchema = Schema.struct({
541
+ tree: Schema.tree(Schema.struct({ label: Schema.string() })),
538
542
  })
539
543
 
540
544
  expect(() => yjsSubstrateFactory.create(TreeSchema)).toThrow("tree")
@@ -556,7 +560,7 @@ describe("YjsSubstrate", () => {
556
560
  })
557
561
 
558
562
  it("reconstructs from snapshot with correct state", () => {
559
- const doc = createYjsDoc(SimpleSchema)
563
+ const doc = createDoc(yjs.bind(SimpleSchema))
560
564
  change(doc, (d: any) => {
561
565
  d.title.insert(0, "Snapshot Test")
562
566
  d.count.set(77)
@@ -564,7 +568,7 @@ describe("YjsSubstrate", () => {
564
568
  })
565
569
 
566
570
  const payload = exportEntirety(doc)
567
- const doc2 = createYjsDocFromEntirety(SimpleSchema, payload)
571
+ const doc2 = createDoc(yjs.bind(SimpleSchema), payload)
568
572
 
569
573
  expect(doc2.title()).toBe("Snapshot Test")
570
574
  expect(doc2.count()).toBe(77)
@@ -187,6 +187,7 @@ describe("YjsVersion", () => {
187
187
  const fake = {
188
188
  serialize: () => "fake",
189
189
  compare: () => "equal" as const,
190
+ meet: () => fake,
190
191
  }
191
192
  expect(() => v.compare(fake)).toThrow(
192
193
  "YjsVersion can only be compared with another YjsVersion",
@@ -215,4 +216,78 @@ describe("YjsVersion", () => {
215
216
  expect(late.compare(earlyParsed)).toBe("ahead")
216
217
  })
217
218
  })
219
+
220
+ // -------------------------------------------------------------------------
221
+ // meet
222
+ // -------------------------------------------------------------------------
223
+
224
+ describe("YjsVersion.meet()", () => {
225
+ it("meet of concurrent versions produces component-wise minimum", () => {
226
+ // Create two docs with independent edits
227
+ const doc1 = new Y.Doc()
228
+ const doc2 = new Y.Doc()
229
+
230
+ doc1.getMap("root").set("a", 1)
231
+ doc1.getMap("root").set("b", 2)
232
+ doc2.getMap("root").set("c", 3)
233
+
234
+ const v1 = new YjsVersion(Y.encodeStateVector(doc1))
235
+ const v2 = new YjsVersion(Y.encodeStateVector(doc2))
236
+
237
+ // meet of concurrent versions — result ≤ both
238
+ const meet = v1.meet(v2) as YjsVersion
239
+ expect(meet.compare(v1)).not.toBe("ahead")
240
+ expect(meet.compare(v2)).not.toBe("ahead")
241
+ })
242
+
243
+ it("meet of identical versions returns an equal version", () => {
244
+ const doc = new Y.Doc()
245
+ doc.getMap("root").set("x", 1)
246
+ const v = new YjsVersion(Y.encodeStateVector(doc))
247
+
248
+ const meet = v.meet(v) as YjsVersion
249
+ expect(meet.compare(v)).toBe("equal")
250
+ })
251
+
252
+ it("meet round-trips through Yjs decode correctly", () => {
253
+ // The custom encodeStateVector must produce bytes that Yjs can decode
254
+ const doc1 = new Y.Doc()
255
+ const doc2 = new Y.Doc()
256
+
257
+ doc1.getMap("root").set("x", 1)
258
+ doc1.getMap("root").set("y", 2)
259
+
260
+ // Sync doc1 → doc2, then doc2 makes independent edits
261
+ Y.applyUpdate(doc2, Y.encodeStateAsUpdate(doc1))
262
+ doc2.getMap("root").set("z", 3)
263
+
264
+ const v1 = new YjsVersion(Y.encodeStateVector(doc1))
265
+ const v2 = new YjsVersion(Y.encodeStateVector(doc2))
266
+
267
+ // v1 is behind v2 (v2 has all of v1's ops plus its own)
268
+ expect(v1.compare(v2)).toBe("behind")
269
+
270
+ // meet(v1, v2) should equal v1 (the behind one)
271
+ const meet = v1.meet(v2) as YjsVersion
272
+ expect(meet.compare(v1)).toBe("equal")
273
+
274
+ // The meet's state vector bytes can be decoded by Yjs
275
+ const decoded = Y.decodeStateVector(meet.sv)
276
+ expect(decoded.size).toBeGreaterThan(0)
277
+ })
278
+
279
+ it("meet of two behind-ahead versions gives the behind one", () => {
280
+ const doc = new Y.Doc()
281
+ doc.getMap("root").set("a", 1)
282
+ const early = new YjsVersion(Y.encodeStateVector(doc))
283
+
284
+ doc.getMap("root").set("b", 2)
285
+ const late = new YjsVersion(Y.encodeStateVector(doc))
286
+
287
+ expect(early.compare(late)).toBe("behind")
288
+
289
+ const meet = early.meet(late) as YjsVersion
290
+ expect(meet.compare(early)).toBe("equal")
291
+ })
292
+ })
218
293
  })
package/src/bind-yjs.ts CHANGED
@@ -1,34 +1,39 @@
1
- // bind-yjs — bindYjs() convenience wrapper for Yjs CRDT substrate.
1
+ // bind-yjs — Yjs CRDT substrate namespace and factory.
2
2
  //
3
- // Binds a schema to the Yjs substrate with causal merge strategy.
4
- // The factory builder accepts { peerId } and returns a SubstrateFactory
5
- // that sets doc.clientID on every new Y.Doc, ensuring deterministic
6
- // peer identity across all documents in an exchange.
3
+ // Provides the `yjs` substrate namespace (`yjs.bind()`, `yjs.replica()`)
4
+ // and the internal factory builder that injects a deterministic numeric
5
+ // Yjs clientID derived from the exchange's peerId.
7
6
  //
8
7
  // Yjs clientID is a uint32 number. We use FNV-1a hash truncated to
9
8
  // 32 bits, mirroring the Loro binding's hashPeerId pattern but
10
9
  // targeting Yjs's number type (not Loro's bigint/53-bit PeerID).
11
10
  //
12
11
  // Usage:
13
- // import { bindYjs } from "@kyneta/yjs-schema"
12
+ // import { yjs } from "@kyneta/yjs-schema"
14
13
  //
15
- // const TodoDoc = bindYjs(Schema.doc({
16
- // title: Schema.annotated("text"),
14
+ // const TodoDoc = yjs.bind(Schema.struct({
15
+ // title: Schema.text(),
17
16
  // items: Schema.list(Schema.struct({ name: Schema.string() })),
18
17
  // }))
19
18
  //
20
19
  // const doc = exchange.get("my-doc", TodoDoc)
21
20
 
22
21
  import type {
23
- BoundSchema,
22
+ CrdtStrategy,
24
23
  Replica,
25
24
  Schema as SchemaNode,
26
25
  Substrate,
27
26
  SubstrateFactory,
27
+ SubstrateNamespace,
28
28
  SubstratePayload,
29
29
  } from "@kyneta/schema"
30
- import { BACKING_DOC, bind, STRUCTURAL_YJS_CLIENT_ID } from "@kyneta/schema"
30
+ import {
31
+ BACKING_DOC,
32
+ createSubstrateNamespace,
33
+ STRUCTURAL_YJS_CLIENT_ID,
34
+ } from "@kyneta/schema"
31
35
  import * as Y from "yjs"
36
+ import type { YjsNativeMap } from "./native-map.js"
32
37
  import { ensureContainers } from "./populate.js"
33
38
  import {
34
39
  createYjsReplica,
@@ -127,42 +132,36 @@ function createYjsFactory(peerId: string): SubstrateFactory<YjsVersion> {
127
132
  }
128
133
 
129
134
  // ---------------------------------------------------------------------------
130
- // bindYjs — the convenience wrapper
135
+ // yjs — the Yjs CRDT substrate namespace
131
136
  // ---------------------------------------------------------------------------
132
137
 
133
138
  /**
134
- * Bind a schema to the Yjs CRDT substrate with causal merge strategy.
135
- *
136
- * This is the recommended way to declare a Yjs-backed document type.
137
- * The factory builder injects a deterministic numeric Yjs clientID derived
138
- * from the exchange's string peerId, ensuring consistent change attribution
139
- * across all documents and sessions.
140
- *
141
- * **Unsupported annotations:** Yjs has no native counter, movable list,
142
- * or tree types. Schemas passed to `bindYjs` must not contain
143
- * `Schema.annotated("counter")`, `Schema.annotated("movable")`, or
144
- * `Schema.annotated("tree")`. These will throw at construction time.
139
+ * The Yjs CRDT substrate namespace.
145
140
  *
146
- * @example
147
- * ```ts
148
- * import { bindYjs } from "@kyneta/yjs-schema"
149
- * import { Schema } from "@kyneta/schema"
141
+ * - `yjs.bind(schema)` — collaborative sync (default)
142
+ * - `yjs.bind(schema, "ephemeral")` — ephemeral/presence broadcast
143
+ * - `yjs.replica()` collaborative replication (default)
144
+ * - `yjs.replica("ephemeral")` ephemeral replication
150
145
  *
151
- * const TodoDoc = bindYjs(Schema.doc({
152
- * title: Schema.annotated("text"),
153
- * items: Schema.list(Schema.struct({
154
- * name: Schema.string(),
155
- * done: Schema.boolean(),
156
- * })),
157
- * }))
146
+ * Strategy is constrained to `CrdtStrategy` (`"collaborative" | "ephemeral"`).
147
+ * Passing `"authoritative"` is a compile error.
158
148
  *
159
- * const doc = exchange.get("my-todos", TodoDoc)
160
- * ```
149
+ * To access the underlying Y.Doc, use `unwrap(ref)` from `@kyneta/schema`.
161
150
  */
162
- export function bindYjs<S extends SchemaNode>(schema: S): BoundSchema<S> {
163
- return bind({
164
- schema,
165
- factory: ctx => createYjsFactory(ctx.peerId),
166
- strategy: "causal",
151
+ /** The closed set of capability tags that the Yjs substrate supports. */
152
+ export type YjsCaps = "text" | "json"
153
+
154
+ export const yjs: SubstrateNamespace<CrdtStrategy, YjsCaps, YjsNativeMap> =
155
+ createSubstrateNamespace<CrdtStrategy, YjsCaps, YjsNativeMap>({
156
+ strategies: {
157
+ collaborative: {
158
+ factory: ctx => createYjsFactory(ctx.peerId),
159
+ replicaFactory: yjsReplicaFactory,
160
+ },
161
+ ephemeral: {
162
+ factory: ctx => createYjsFactory(ctx.peerId),
163
+ replicaFactory: yjsReplicaFactory,
164
+ },
165
+ },
166
+ defaultStrategy: "collaborative",
167
167
  })
168
- }