@kyneta/yjs-schema 1.0.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/LICENSE +21 -0
- package/README.md +182 -0
- package/dist/index.d.ts +351 -0
- package/dist/index.js +865 -0
- package/dist/index.js.map +1 -0
- package/package.json +48 -0
- package/src/__tests__/bind-yjs.test.ts +266 -0
- package/src/__tests__/create.test.ts +632 -0
- package/src/__tests__/record-text-spike.test.ts +429 -0
- package/src/__tests__/store-reader.test.ts +722 -0
- package/src/__tests__/substrate.test.ts +604 -0
- package/src/__tests__/version.test.ts +227 -0
- package/src/bind-yjs.ts +147 -0
- package/src/change-mapping.ts +612 -0
- package/src/create.ts +172 -0
- package/src/index.ts +83 -0
- package/src/populate.ts +208 -0
- package/src/store-reader.ts +123 -0
- package/src/substrate.ts +252 -0
- package/src/sync.ts +107 -0
- package/src/version.ts +138 -0
- package/src/yjs-escape.ts +100 -0
- package/src/yjs-resolve.ts +108 -0
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
// yjs-resolve — Yjs-specific path resolution.
|
|
2
|
+
//
|
|
3
|
+
// Implements stepIntoYjs and resolveYjsType for schema-guided
|
|
4
|
+
// navigation of the Yjs shared type tree.
|
|
5
|
+
//
|
|
6
|
+
// resolveYjsType is a left-fold over path segments, accumulating
|
|
7
|
+
// (currentType, currentSchema) at each step. This mirrors how
|
|
8
|
+
// resolveContainer works for Loro — but uses `instanceof` for
|
|
9
|
+
// runtime type discrimination instead of Loro's `.kind()` method.
|
|
10
|
+
//
|
|
11
|
+
// Root container strategy: All schema fields are children of a single
|
|
12
|
+
// root `Y.Map` obtained via `doc.getMap("root")`. This root map holds
|
|
13
|
+
// shared types (Y.Text, Y.Array, Y.Map) and plain values uniformly.
|
|
14
|
+
// Using a single root Y.Map enables one `observeDeep` call that
|
|
15
|
+
// captures all mutations with correct relative paths.
|
|
16
|
+
|
|
17
|
+
import { advanceSchema } from "@kyneta/schema"
|
|
18
|
+
import type { Path, Segment } from "@kyneta/schema"
|
|
19
|
+
import type { Schema as SchemaNode } from "@kyneta/schema"
|
|
20
|
+
import * as Y from "yjs"
|
|
21
|
+
|
|
22
|
+
// ---------------------------------------------------------------------------
|
|
23
|
+
// stepIntoYjs — single step of the fold
|
|
24
|
+
// ---------------------------------------------------------------------------
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Navigate one step deeper into the Yjs shared type tree.
|
|
28
|
+
*
|
|
29
|
+
* Uses `instanceof` for runtime type discrimination:
|
|
30
|
+
* - `Y.Map` → `.get(key)`
|
|
31
|
+
* - `Y.Array` → `.get(index)`
|
|
32
|
+
* - `Y.Text` → terminal (cannot step further)
|
|
33
|
+
* - Plain value → terminal (return `undefined`)
|
|
34
|
+
*
|
|
35
|
+
* @param current - The current position (a Yjs shared type or plain value)
|
|
36
|
+
* @param segment - The path segment to follow
|
|
37
|
+
*/
|
|
38
|
+
export function stepIntoYjs(
|
|
39
|
+
current: unknown,
|
|
40
|
+
segment: Segment,
|
|
41
|
+
): unknown {
|
|
42
|
+
const resolved = segment.resolve()
|
|
43
|
+
|
|
44
|
+
if (current instanceof Y.Map) {
|
|
45
|
+
return current.get(resolved as string)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (current instanceof Y.Array) {
|
|
49
|
+
return current.get(resolved as number)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (current instanceof Y.Text) {
|
|
53
|
+
throw new Error(
|
|
54
|
+
`yjs-resolve: cannot step into Y.Text`,
|
|
55
|
+
)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Plain value — terminal, cannot step further
|
|
59
|
+
return undefined
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// ---------------------------------------------------------------------------
|
|
63
|
+
// resolveYjsType — full path resolution via left-fold
|
|
64
|
+
// ---------------------------------------------------------------------------
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Resolve a Yjs shared type (or plain value) at the given path.
|
|
68
|
+
*
|
|
69
|
+
* Left-folds over path segments using `advanceSchema` for pure schema
|
|
70
|
+
* descent and `stepIntoYjs` for Yjs-specific navigation.
|
|
71
|
+
*
|
|
72
|
+
* Returns the Yjs shared type or plain value at the terminal position.
|
|
73
|
+
* For an empty path, returns the root map itself.
|
|
74
|
+
*
|
|
75
|
+
* @param rootMap - The root `Y.Map` obtained via `doc.getMap("root")`
|
|
76
|
+
* @param rootSchema - The root document schema
|
|
77
|
+
* @param path - The path to resolve
|
|
78
|
+
*/
|
|
79
|
+
export function resolveYjsType(
|
|
80
|
+
rootMap: Y.Map<any>,
|
|
81
|
+
rootSchema: SchemaNode,
|
|
82
|
+
path: Path,
|
|
83
|
+
): unknown {
|
|
84
|
+
let current: unknown = rootMap
|
|
85
|
+
let schema = rootSchema
|
|
86
|
+
|
|
87
|
+
// Unwrap the root annotation (e.g. annotated("doc", product))
|
|
88
|
+
// to reach the product schema whose fields are the root map's children.
|
|
89
|
+
let rootProduct = rootSchema
|
|
90
|
+
while (
|
|
91
|
+
rootProduct._kind === "annotated" &&
|
|
92
|
+
rootProduct.schema !== undefined
|
|
93
|
+
) {
|
|
94
|
+
rootProduct = rootProduct.schema
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
for (let i = 0; i < path.length; i++) {
|
|
98
|
+
const seg = path.segments[i]!
|
|
99
|
+
const nextSchema = advanceSchema(schema, seg)
|
|
100
|
+
|
|
101
|
+
// For the first segment, we step into the root map directly.
|
|
102
|
+
// For subsequent segments, we use stepIntoYjs on the current value.
|
|
103
|
+
current = stepIntoYjs(current, seg)
|
|
104
|
+
schema = nextSchema
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return current
|
|
108
|
+
}
|