@kyneta/yjs-schema 1.6.1 → 1.8.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 +2 -0
- package/dist/index.d.ts +17 -61
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +222 -207
- package/dist/index.js.map +1 -1
- package/package.json +6 -8
- package/src/__tests__/create.test.ts +11 -0
- package/src/__tests__/eager-write-coherence.test.ts +321 -0
- package/src/__tests__/materialize.test.ts +227 -0
- package/src/__tests__/position.test.ts +7 -7
- package/src/__tests__/structural-merge.test.ts +18 -18
- package/src/__tests__/substrate.test.ts +56 -3
- package/src/bind-yjs.ts +3 -5
- package/src/change-mapping.ts +73 -48
- package/src/index.ts +1 -2
- package/src/materialize.ts +109 -0
- package/src/populate.ts +35 -37
- package/src/substrate.ts +277 -111
- 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/yjs-resolve.ts
CHANGED
|
@@ -1,35 +1,29 @@
|
|
|
1
1
|
// yjs-resolve — Yjs-specific path resolution.
|
|
2
2
|
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
// resolveContainer works for Loro — but uses `instanceof` for
|
|
9
|
-
// runtime type discrimination instead of Loro's `.kind()` method.
|
|
3
|
+
// `stepIntoYjs` is the per-step substrate dispatch; `resolveYjsType`
|
|
4
|
+
// applies the core `foldPath` primitive (from `@kyneta/schema`) around
|
|
5
|
+
// it. The semantic invariants of the fold — identity-keying at
|
|
6
|
+
// product-field boundaries, sum-boundary short-circuit — live in
|
|
7
|
+
// `fold-path.ts`, not here.
|
|
10
8
|
//
|
|
11
9
|
// Root container strategy: All schema fields are children of a single
|
|
12
10
|
// root `Y.Map` obtained via `doc.getMap("root")`. This root map holds
|
|
13
11
|
// shared types (Y.Text, Y.Array, Y.Map) and plain values uniformly.
|
|
14
12
|
// Using a single root Y.Map enables one `observeDeep` call that
|
|
15
13
|
// 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.
|
|
21
14
|
|
|
22
|
-
import
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
15
|
+
import {
|
|
16
|
+
foldPath,
|
|
17
|
+
type Path,
|
|
18
|
+
type PathFoldResult,
|
|
19
|
+
type PathStepper,
|
|
20
|
+
type SchemaBinding,
|
|
21
|
+
type Schema as SchemaNode,
|
|
27
22
|
} from "@kyneta/schema"
|
|
28
|
-
import { advanceSchema, KIND } from "@kyneta/schema"
|
|
29
23
|
import * as Y from "yjs"
|
|
30
24
|
|
|
31
25
|
// ---------------------------------------------------------------------------
|
|
32
|
-
// stepIntoYjs —
|
|
26
|
+
// stepIntoYjs — per-step substrate dispatch (PathStepper for Yjs)
|
|
33
27
|
// ---------------------------------------------------------------------------
|
|
34
28
|
|
|
35
29
|
/**
|
|
@@ -41,15 +35,16 @@ import * as Y from "yjs"
|
|
|
41
35
|
* - `Y.Text` → terminal (cannot step further)
|
|
42
36
|
* - Plain value → terminal (return `undefined`)
|
|
43
37
|
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
38
|
+
* `_nextSchema` is part of the `PathStepper` contract for Loro's root
|
|
39
|
+
* dispatch but is unused here — Yjs's `instanceof` dispatch doesn't
|
|
40
|
+
* need to look ahead at the next schema kind.
|
|
47
41
|
*/
|
|
48
|
-
export
|
|
49
|
-
current
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
42
|
+
export const stepIntoYjs: PathStepper = (
|
|
43
|
+
current,
|
|
44
|
+
_nextSchema,
|
|
45
|
+
segment,
|
|
46
|
+
identity,
|
|
47
|
+
) => {
|
|
53
48
|
const resolved = segment.resolve()
|
|
54
49
|
|
|
55
50
|
if (current instanceof Y.Map) {
|
|
@@ -69,85 +64,25 @@ export function stepIntoYjs(
|
|
|
69
64
|
}
|
|
70
65
|
|
|
71
66
|
// ---------------------------------------------------------------------------
|
|
72
|
-
// resolveYjsType — full path resolution via
|
|
67
|
+
// resolveYjsType — full path resolution via foldPath
|
|
73
68
|
// ---------------------------------------------------------------------------
|
|
74
69
|
|
|
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
|
-
|
|
87
70
|
/**
|
|
88
71
|
* Resolve a Yjs shared type (or plain value) at the given path.
|
|
89
72
|
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
73
|
+
* Thin wrapper around `foldPath(stepIntoYjs, ...)`. Returns the
|
|
74
|
+
* `PathFoldResult` shape from core — `{ resolved, schema }`.
|
|
92
75
|
*
|
|
93
|
-
* When a `binding` is provided,
|
|
94
|
-
*
|
|
95
|
-
* identity hash is used instead of the field name at every product-field
|
|
96
|
-
* boundary (root and nested).
|
|
76
|
+
* When a `binding` is provided, every product-field boundary uses the
|
|
77
|
+
* identity hash from `binding.forward` instead of the field name.
|
|
97
78
|
*
|
|
98
|
-
*
|
|
99
|
-
* the terminal position. For an empty path, returns the root map and
|
|
100
|
-
* root schema.
|
|
101
|
-
*
|
|
102
|
-
* @param rootMap - The root `Y.Map` obtained via `doc.getMap("root")`
|
|
103
|
-
* @param rootSchema - The root document schema
|
|
104
|
-
* @param path - The path to resolve
|
|
105
|
-
* @param binding - Optional SchemaBinding for identity-keyed navigation.
|
|
79
|
+
* For an empty path, returns the root map and root schema.
|
|
106
80
|
*/
|
|
107
81
|
export function resolveYjsType(
|
|
108
82
|
rootMap: Y.Map<any>,
|
|
109
83
|
rootSchema: SchemaNode,
|
|
110
84
|
path: Path,
|
|
111
85
|
binding?: SchemaBinding,
|
|
112
|
-
):
|
|
113
|
-
|
|
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 = ""
|
|
119
|
-
|
|
120
|
-
for (let i = 0; i < path.length; i++) {
|
|
121
|
-
const seg = path.segments[i]
|
|
122
|
-
if (!seg) throw new Error(`Missing segment at index ${i}`)
|
|
123
|
-
const nextSchema = advanceSchema(schema, seg)
|
|
124
|
-
|
|
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)
|
|
135
|
-
schema = nextSchema
|
|
136
|
-
|
|
137
|
-
// Sum variants are always PlainSchema — no CRDT containers inside.
|
|
138
|
-
// Once we land on a sum, resolve remaining segments via plain JS
|
|
139
|
-
// property access on the (JSON) value.
|
|
140
|
-
if (schema[KIND] === "sum" && i + 1 < path.length) {
|
|
141
|
-
for (let j = i + 1; j < path.length; j++) {
|
|
142
|
-
const remaining = path.segments[j]
|
|
143
|
-
if (!remaining) throw new Error(`Missing segment at index ${j}`)
|
|
144
|
-
current = (current as Record<string, unknown>)?.[
|
|
145
|
-
remaining.resolve() as string
|
|
146
|
-
]
|
|
147
|
-
}
|
|
148
|
-
return { resolved: current, schema }
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
return { resolved: current, schema }
|
|
86
|
+
): PathFoldResult {
|
|
87
|
+
return foldPath(rootMap, rootSchema, path, stepIntoYjs, binding)
|
|
153
88
|
}
|