@kyneta/yjs-schema 1.3.1 → 1.4.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/README.md +28 -25
- package/dist/index.d.ts +185 -86
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +1159 -860
- package/dist/index.js.map +1 -1
- package/package.json +7 -7
- package/src/__tests__/bind-constraints.test.ts +39 -30
- package/src/__tests__/bind-yjs.test.ts +53 -16
- package/src/__tests__/position.test.ts +376 -0
- package/src/__tests__/structural-merge.test.ts +111 -54
- package/src/__tests__/substrate.test.ts +18 -0
- package/src/__tests__/version.test.ts +87 -0
- package/src/bind-yjs.ts +44 -37
- package/src/change-mapping.ts +219 -25
- package/src/index.ts +3 -1
- package/src/populate.ts +59 -12
- package/src/position.ts +45 -0
- package/src/reader.ts +62 -6
- package/src/substrate.ts +99 -11
- package/src/version.ts +135 -33
- package/src/yjs-resolve.ts +59 -12
package/src/yjs-resolve.ts
CHANGED
|
@@ -13,8 +13,18 @@
|
|
|
13
13
|
// shared types (Y.Text, Y.Array, Y.Map) and plain values uniformly.
|
|
14
14
|
// Using a single root Y.Map enables one `observeDeep` call that
|
|
15
15
|
// captures all mutations with correct relative paths.
|
|
16
|
+
//
|
|
17
|
+
// Identity-keying: when a SchemaBinding is provided, every product-field
|
|
18
|
+
// boundary uses the identity hash (from binding.forward) instead of the
|
|
19
|
+
// field name as the Y.Map key. The binding is threaded through
|
|
20
|
+
// resolveYjsType and stepIntoYjs.
|
|
16
21
|
|
|
17
|
-
import type {
|
|
22
|
+
import type {
|
|
23
|
+
Path,
|
|
24
|
+
SchemaBinding,
|
|
25
|
+
Schema as SchemaNode,
|
|
26
|
+
Segment,
|
|
27
|
+
} from "@kyneta/schema"
|
|
18
28
|
import { advanceSchema } from "@kyneta/schema"
|
|
19
29
|
import * as Y from "yjs"
|
|
20
30
|
|
|
@@ -26,19 +36,24 @@ import * as Y from "yjs"
|
|
|
26
36
|
* Navigate one step deeper into the Yjs shared type tree.
|
|
27
37
|
*
|
|
28
38
|
* Uses `instanceof` for runtime type discrimination:
|
|
29
|
-
* - `Y.Map` → `.get(key)`
|
|
39
|
+
* - `Y.Map` → `.get(key)` — uses the identity hash when provided
|
|
30
40
|
* - `Y.Array` → `.get(index)`
|
|
31
41
|
* - `Y.Text` → terminal (cannot step further)
|
|
32
42
|
* - Plain value → terminal (return `undefined`)
|
|
33
43
|
*
|
|
34
44
|
* @param current - The current position (a Yjs shared type or plain value)
|
|
35
45
|
* @param segment - The path segment to follow
|
|
46
|
+
* @param identity - Optional identity hash to use instead of the segment's resolved value
|
|
36
47
|
*/
|
|
37
|
-
export function stepIntoYjs(
|
|
48
|
+
export function stepIntoYjs(
|
|
49
|
+
current: unknown,
|
|
50
|
+
segment: Segment,
|
|
51
|
+
identity?: string,
|
|
52
|
+
): unknown {
|
|
38
53
|
const resolved = segment.resolve()
|
|
39
54
|
|
|
40
55
|
if (current instanceof Y.Map) {
|
|
41
|
-
return current.get(resolved as string)
|
|
56
|
+
return current.get(identity ?? (resolved as string))
|
|
42
57
|
}
|
|
43
58
|
|
|
44
59
|
if (current instanceof Y.Array) {
|
|
@@ -57,36 +72,68 @@ export function stepIntoYjs(current: unknown, segment: Segment): unknown {
|
|
|
57
72
|
// resolveYjsType — full path resolution via left-fold
|
|
58
73
|
// ---------------------------------------------------------------------------
|
|
59
74
|
|
|
75
|
+
/**
|
|
76
|
+
* Result of resolving a Yjs shared type at a path.
|
|
77
|
+
*
|
|
78
|
+
* Includes both the resolved Yjs value and the schema at that position,
|
|
79
|
+
* enabling callers to distinguish between schema kinds that map to the
|
|
80
|
+
* same Yjs type (e.g. "text" vs "richtext" both use Y.Text).
|
|
81
|
+
*/
|
|
82
|
+
export interface ResolvedYjs {
|
|
83
|
+
readonly resolved: unknown
|
|
84
|
+
readonly schema: SchemaNode
|
|
85
|
+
}
|
|
86
|
+
|
|
60
87
|
/**
|
|
61
88
|
* Resolve a Yjs shared type (or plain value) at the given path.
|
|
62
89
|
*
|
|
63
90
|
* Left-folds over path segments using `advanceSchema` for pure schema
|
|
64
91
|
* descent and `stepIntoYjs` for Yjs-specific navigation.
|
|
65
92
|
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
93
|
+
* When a `binding` is provided, each step computes the absolute schema
|
|
94
|
+
* path and looks up the identity hash from `binding.forward`. This
|
|
95
|
+
* identity hash is used instead of the field name at every product-field
|
|
96
|
+
* boundary (root and nested).
|
|
97
|
+
*
|
|
98
|
+
* Returns both the Yjs shared type (or plain value) and the schema at
|
|
99
|
+
* the terminal position. For an empty path, returns the root map and
|
|
100
|
+
* root schema.
|
|
68
101
|
*
|
|
69
102
|
* @param rootMap - The root `Y.Map` obtained via `doc.getMap("root")`
|
|
70
103
|
* @param rootSchema - The root document schema
|
|
71
104
|
* @param path - The path to resolve
|
|
105
|
+
* @param binding - Optional SchemaBinding for identity-keyed navigation.
|
|
72
106
|
*/
|
|
73
107
|
export function resolveYjsType(
|
|
74
108
|
rootMap: Y.Map<any>,
|
|
75
109
|
rootSchema: SchemaNode,
|
|
76
110
|
path: Path,
|
|
77
|
-
|
|
111
|
+
binding?: SchemaBinding,
|
|
112
|
+
): ResolvedYjs {
|
|
78
113
|
let current: unknown = rootMap
|
|
79
114
|
let schema = rootSchema
|
|
115
|
+
// Track the accumulated absolute schema path for identity lookup.
|
|
116
|
+
// Only string (key) segments contribute — index segments are structural
|
|
117
|
+
// and don't participate in identity-keying.
|
|
118
|
+
let absPath = ""
|
|
80
119
|
|
|
81
120
|
for (let i = 0; i < path.length; i++) {
|
|
82
|
-
const seg = path.segments[i]
|
|
121
|
+
const seg = path.segments[i]
|
|
122
|
+
if (!seg) throw new Error(`Missing segment at index ${i}`)
|
|
83
123
|
const nextSchema = advanceSchema(schema, seg)
|
|
84
124
|
|
|
85
|
-
//
|
|
86
|
-
//
|
|
87
|
-
|
|
125
|
+
// Compute identity for this step if binding is provided and the
|
|
126
|
+
// segment is a key (field name at a product boundary).
|
|
127
|
+
let identity: string | undefined
|
|
128
|
+
if (binding && seg.role === "key") {
|
|
129
|
+
const segStr = seg.resolve() as string
|
|
130
|
+
absPath = absPath ? `${absPath}.${segStr}` : segStr
|
|
131
|
+
identity = binding.forward.get(absPath) as string | undefined
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
current = stepIntoYjs(current, seg, identity)
|
|
88
135
|
schema = nextSchema
|
|
89
136
|
}
|
|
90
137
|
|
|
91
|
-
return current
|
|
138
|
+
return { resolved: current, schema }
|
|
92
139
|
}
|