@kyneta/yjs-schema 1.6.0 → 1.7.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.
- package/dist/index.d.ts +17 -61
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +151 -202
- package/dist/index.js.map +1 -1
- package/package.json +6 -8
- package/src/__tests__/create.test.ts +1 -1
- package/src/__tests__/materialize.test.ts +227 -0
- package/src/__tests__/position.test.ts +7 -7
- package/src/__tests__/record-text-spike.test.ts +5 -5
- package/src/__tests__/structural-merge.test.ts +20 -20
- package/src/__tests__/substrate.test.ts +45 -0
- package/src/bind-yjs.ts +3 -5
- package/src/change-mapping.ts +62 -35
- package/src/index.ts +1 -2
- package/src/materialize.ts +109 -0
- package/src/populate.ts +23 -37
- package/src/substrate.ts +62 -52
- package/src/yjs-extract.ts +52 -0
- package/src/yjs-resolve.ts +30 -95
- package/src/__tests__/reader.test.ts +0 -685
- package/src/reader.ts +0 -174
package/src/reader.ts
DELETED
|
@@ -1,174 +0,0 @@
|
|
|
1
|
-
// store-reader — YjsReader implementation.
|
|
2
|
-
//
|
|
3
|
-
// Implements Reader via schema-guided live navigation of the
|
|
4
|
-
// Yjs shared type tree. Each read operation resolves the shared type
|
|
5
|
-
// at the given path using resolveYjsType, then extracts the
|
|
6
|
-
// appropriate value based on `instanceof` discrimination.
|
|
7
|
-
//
|
|
8
|
-
// Y.Text → .toJSON() (string), Y.Map → .toJSON() (plain object),
|
|
9
|
-
// Y.Array → .toJSON() (plain array), plain values → as-is.
|
|
10
|
-
//
|
|
11
|
-
// Richtext: when the schema at the resolved path is "richtext" and
|
|
12
|
-
// the resolved value is a Y.Text, we call .toDelta() and convert to
|
|
13
|
-
// RichTextDelta (array of { text, marks? } spans) instead of .toJSON().
|
|
14
|
-
//
|
|
15
|
-
// Identity-keying: when a SchemaBinding is provided, resolveYjsType
|
|
16
|
-
// navigates Y.Map children using identity hashes instead of field names.
|
|
17
|
-
|
|
18
|
-
import type {
|
|
19
|
-
Path,
|
|
20
|
-
Reader,
|
|
21
|
-
RichTextDelta,
|
|
22
|
-
RichTextSpan,
|
|
23
|
-
SchemaBinding,
|
|
24
|
-
Schema as SchemaNode,
|
|
25
|
-
} from "@kyneta/schema"
|
|
26
|
-
import { KIND } from "@kyneta/schema"
|
|
27
|
-
import * as Y from "yjs"
|
|
28
|
-
import { resolveYjsType } from "./yjs-resolve.js"
|
|
29
|
-
|
|
30
|
-
// ---------------------------------------------------------------------------
|
|
31
|
-
// Value extraction
|
|
32
|
-
// ---------------------------------------------------------------------------
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Extract a plain value from a Yjs shared type or return a plain value as-is.
|
|
36
|
-
*
|
|
37
|
-
* - Y.Text → `.toJSON()` (string)
|
|
38
|
-
* - Y.Map → `.toJSON()` (plain object snapshot — for product/map reads)
|
|
39
|
-
* - Y.Array → `.toJSON()` (plain array snapshot)
|
|
40
|
-
* - Plain values (string, number, boolean, null) → returned as-is
|
|
41
|
-
*/
|
|
42
|
-
function extractValue(resolved: unknown): unknown {
|
|
43
|
-
if (resolved instanceof Y.Text) {
|
|
44
|
-
return resolved.toJSON()
|
|
45
|
-
}
|
|
46
|
-
if (resolved instanceof Y.Map) {
|
|
47
|
-
return resolved.toJSON()
|
|
48
|
-
}
|
|
49
|
-
if (resolved instanceof Y.Array) {
|
|
50
|
-
return resolved.toJSON()
|
|
51
|
-
}
|
|
52
|
-
// Plain scalar value (string, number, boolean, null, etc.)
|
|
53
|
-
return resolved
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
// ---------------------------------------------------------------------------
|
|
57
|
-
// Rich text delta conversion
|
|
58
|
-
// ---------------------------------------------------------------------------
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
* Convert a Y.Text's delta (Quill format) to a kyneta RichTextDelta.
|
|
62
|
-
*
|
|
63
|
-
* Yjs `.toDelta()` returns `{ insert: string, attributes?: Record<string, any> }[]`.
|
|
64
|
-
* Kyneta RichTextDelta is `{ text: string, marks?: MarkMap }[]`.
|
|
65
|
-
*/
|
|
66
|
-
function yTextToRichTextDelta(ytext: Y.Text): RichTextDelta {
|
|
67
|
-
const delta = ytext.toDelta() as Array<{
|
|
68
|
-
insert: string
|
|
69
|
-
attributes?: Record<string, unknown>
|
|
70
|
-
}>
|
|
71
|
-
const spans: RichTextSpan[] = []
|
|
72
|
-
for (const d of delta) {
|
|
73
|
-
if (typeof d.insert !== "string") continue
|
|
74
|
-
const span: RichTextSpan =
|
|
75
|
-
d.attributes && Object.keys(d.attributes).length > 0
|
|
76
|
-
? { text: d.insert, marks: d.attributes }
|
|
77
|
-
: { text: d.insert }
|
|
78
|
-
spans.push(span)
|
|
79
|
-
}
|
|
80
|
-
return spans
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
// ---------------------------------------------------------------------------
|
|
84
|
-
// yjsReader
|
|
85
|
-
// ---------------------------------------------------------------------------
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* Creates a Reader that navigates the Yjs shared type tree live,
|
|
89
|
-
* using the schema as a type witness to determine navigation at each
|
|
90
|
-
* path segment.
|
|
91
|
-
*
|
|
92
|
-
* The reader is a live view — mutations to the underlying Y.Doc
|
|
93
|
-
* (via `doc.transact()`, or `Y.applyUpdate()`) are immediately
|
|
94
|
-
* visible through the reader.
|
|
95
|
-
*
|
|
96
|
-
* Internally obtains the root map via `doc.getMap("root")`.
|
|
97
|
-
*
|
|
98
|
-
* @param doc - The Y.Doc to read from.
|
|
99
|
-
* @param schema - The root schema for the document.
|
|
100
|
-
* @param binding - Optional SchemaBinding for identity-keyed navigation.
|
|
101
|
-
*/
|
|
102
|
-
export function yjsReader(
|
|
103
|
-
doc: Y.Doc,
|
|
104
|
-
schema: SchemaNode,
|
|
105
|
-
binding?: SchemaBinding,
|
|
106
|
-
): Reader {
|
|
107
|
-
const rootMap = doc.getMap("root")
|
|
108
|
-
|
|
109
|
-
return {
|
|
110
|
-
read(path: Path): unknown {
|
|
111
|
-
if (path.length === 0) {
|
|
112
|
-
// Root read — return the full root map as JSON
|
|
113
|
-
return rootMap.toJSON()
|
|
114
|
-
}
|
|
115
|
-
const { resolved, schema: nodeSchema } = resolveYjsType(
|
|
116
|
-
rootMap,
|
|
117
|
-
schema,
|
|
118
|
-
path,
|
|
119
|
-
binding,
|
|
120
|
-
)
|
|
121
|
-
// Richtext: Y.Text at a richtext schema position → RichTextDelta
|
|
122
|
-
if (nodeSchema[KIND] === "richtext" && resolved instanceof Y.Text) {
|
|
123
|
-
return yTextToRichTextDelta(resolved)
|
|
124
|
-
}
|
|
125
|
-
return extractValue(resolved)
|
|
126
|
-
},
|
|
127
|
-
|
|
128
|
-
arrayLength(path: Path): number {
|
|
129
|
-
const { resolved } = resolveYjsType(rootMap, schema, path, binding)
|
|
130
|
-
if (resolved instanceof Y.Array) {
|
|
131
|
-
return resolved.length
|
|
132
|
-
}
|
|
133
|
-
// Graceful fallback for plain array values
|
|
134
|
-
if (Array.isArray(resolved)) {
|
|
135
|
-
return resolved.length
|
|
136
|
-
}
|
|
137
|
-
return 0
|
|
138
|
-
},
|
|
139
|
-
|
|
140
|
-
keys(path: Path): string[] {
|
|
141
|
-
const { resolved } = resolveYjsType(rootMap, schema, path, binding)
|
|
142
|
-
if (resolved instanceof Y.Map) {
|
|
143
|
-
return Array.from(resolved.keys())
|
|
144
|
-
}
|
|
145
|
-
// Graceful fallback for plain object values
|
|
146
|
-
if (
|
|
147
|
-
resolved !== null &&
|
|
148
|
-
resolved !== undefined &&
|
|
149
|
-
typeof resolved === "object" &&
|
|
150
|
-
!Array.isArray(resolved)
|
|
151
|
-
) {
|
|
152
|
-
return Object.keys(resolved as Record<string, unknown>)
|
|
153
|
-
}
|
|
154
|
-
return []
|
|
155
|
-
},
|
|
156
|
-
|
|
157
|
-
hasKey(path: Path, key: string): boolean {
|
|
158
|
-
const { resolved } = resolveYjsType(rootMap, schema, path, binding)
|
|
159
|
-
if (resolved instanceof Y.Map) {
|
|
160
|
-
return resolved.has(key)
|
|
161
|
-
}
|
|
162
|
-
// Graceful fallback for plain object values
|
|
163
|
-
if (
|
|
164
|
-
resolved !== null &&
|
|
165
|
-
resolved !== undefined &&
|
|
166
|
-
typeof resolved === "object" &&
|
|
167
|
-
!Array.isArray(resolved)
|
|
168
|
-
) {
|
|
169
|
-
return key in (resolved as Record<string, unknown>)
|
|
170
|
-
}
|
|
171
|
-
return false
|
|
172
|
-
},
|
|
173
|
-
}
|
|
174
|
-
}
|