mobx-keystone-yjs 1.3.0 → 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.
@@ -0,0 +1,31 @@
1
+ import { modelSnapshotOutWithMetadata } from "mobx-keystone"
2
+ import * as Y from "yjs"
3
+ import { JsonValue } from "../jsonTypes"
4
+ import { YjsTextModel } from "./YjsTextModel"
5
+
6
+ export function convertYjsDataToJson(
7
+ yjsData: Y.Array<unknown> | Y.Map<unknown> | Y.Text | unknown
8
+ ): JsonValue {
9
+ if (yjsData instanceof Y.Array) {
10
+ return yjsData.map((v) => convertYjsDataToJson(v))
11
+ }
12
+
13
+ if (yjsData instanceof Y.Map) {
14
+ const obj: Record<string, JsonValue> = {}
15
+ yjsData.forEach((v, k) => {
16
+ obj[k] = convertYjsDataToJson(v)
17
+ })
18
+ return obj
19
+ }
20
+
21
+ if (yjsData instanceof Y.Text) {
22
+ const deltas = yjsData.toDelta()
23
+
24
+ return modelSnapshotOutWithMetadata(YjsTextModel, {
25
+ deltaList: deltas.length > 0 ? [{ $frozen: true, data: deltas }] : [],
26
+ }) as JsonValue
27
+ }
28
+
29
+ // assume it's a primitive
30
+ return yjsData as JsonValue
31
+ }
@@ -1,85 +1,92 @@
1
- import { Patch } from "mobx-keystone"
2
- import * as Y from "yjs"
3
- import { JsonArray, JsonObject, JsonValue } from "../jsonTypes"
4
- import { failure } from "../utils/error"
5
-
6
- export function convertYjsEventToPatches(event: Y.YEvent<any>): Patch[] {
7
- const patches: Patch[] = []
8
-
9
- if (event instanceof Y.YMapEvent) {
10
- const source = event.target as Y.Map<any>
11
-
12
- event.changes.keys.forEach((change, key) => {
13
- const path = [...event.path, key]
14
-
15
- switch (change.action) {
16
- case "add":
17
- patches.push({
18
- op: "add",
19
- path,
20
- value: toPlainValue(source.get(key)),
21
- })
22
- break
23
-
24
- case "update":
25
- patches.push({
26
- op: "replace",
27
- path,
28
- value: toPlainValue(source.get(key)),
29
- })
30
- break
31
-
32
- case "delete":
33
- patches.push({
34
- op: "remove",
35
- path,
36
- })
37
- break
38
-
39
- default:
40
- throw failure(`unsupported Yjs map event action: ${change.action}`)
41
- }
42
- })
43
- } else if (event instanceof Y.YArrayEvent) {
44
- let retain = 0
45
- event.changes.delta.forEach((change) => {
46
- if (change.retain) {
47
- retain += change.retain
48
- }
49
-
50
- if (change.delete) {
51
- // remove X items at retain position
52
- const path = [...event.path, retain]
53
- for (let i = 0; i < change.delete; i++) {
54
- patches.push({
55
- op: "remove",
56
- path,
57
- })
58
- }
59
- }
60
-
61
- if (change.insert) {
62
- const newValues = Array.isArray(change.insert) ? change.insert : [change.insert]
63
- newValues.forEach((v) => {
64
- const path = [...event.path, retain]
65
- patches.push({
66
- op: "add",
67
- path,
68
- value: toPlainValue(v),
69
- })
70
- retain++
71
- })
72
- }
73
- })
74
- }
75
-
76
- return patches
77
- }
78
-
79
- function toPlainValue(v: Y.Map<any> | Y.Array<any> | JsonValue) {
80
- if (v instanceof Y.Map || v instanceof Y.Array) {
81
- return v.toJSON() as JsonObject | JsonArray
82
- } else {
83
- return v
84
- }
85
- }
1
+ import { Patch } from "mobx-keystone"
2
+ import * as Y from "yjs"
3
+ import { JsonArray, JsonObject, JsonValue } from "../jsonTypes"
4
+ import { failure } from "../utils/error"
5
+
6
+ export function convertYjsEventToPatches(event: Y.YEvent<any>): Patch[] {
7
+ const patches: Patch[] = []
8
+
9
+ if (event instanceof Y.YMapEvent) {
10
+ const source = event.target as Y.Map<any>
11
+
12
+ event.changes.keys.forEach((change, key) => {
13
+ const path = [...event.path, key]
14
+
15
+ switch (change.action) {
16
+ case "add":
17
+ patches.push({
18
+ op: "add",
19
+ path,
20
+ value: toPlainValue(source.get(key)),
21
+ })
22
+ break
23
+
24
+ case "update":
25
+ patches.push({
26
+ op: "replace",
27
+ path,
28
+ value: toPlainValue(source.get(key)),
29
+ })
30
+ break
31
+
32
+ case "delete":
33
+ patches.push({
34
+ op: "remove",
35
+ path,
36
+ })
37
+ break
38
+
39
+ default:
40
+ throw failure(`unsupported Yjs map event action: ${change.action}`)
41
+ }
42
+ })
43
+ } else if (event instanceof Y.YArrayEvent) {
44
+ let retain = 0
45
+ event.changes.delta.forEach((change) => {
46
+ if (change.retain) {
47
+ retain += change.retain
48
+ }
49
+
50
+ if (change.delete) {
51
+ // remove X items at retain position
52
+ const path = [...event.path, retain]
53
+ for (let i = 0; i < change.delete; i++) {
54
+ patches.push({
55
+ op: "remove",
56
+ path,
57
+ })
58
+ }
59
+ }
60
+
61
+ if (change.insert) {
62
+ const newValues = Array.isArray(change.insert) ? change.insert : [change.insert]
63
+ newValues.forEach((v) => {
64
+ const path = [...event.path, retain]
65
+ patches.push({
66
+ op: "add",
67
+ path,
68
+ value: toPlainValue(v),
69
+ })
70
+ retain++
71
+ })
72
+ }
73
+ })
74
+ } else if (event instanceof Y.YTextEvent) {
75
+ const path = [...event.path, "deltaList", -1 /* last item */]
76
+ patches.push({
77
+ op: "add",
78
+ path,
79
+ value: { $frozen: true, data: event.delta },
80
+ })
81
+ }
82
+
83
+ return patches
84
+ }
85
+
86
+ function toPlainValue(v: Y.Map<any> | Y.Array<any> | JsonValue) {
87
+ if (v instanceof Y.Map || v instanceof Y.Array) {
88
+ return v.toJSON() as JsonObject | JsonArray
89
+ } else {
90
+ return v
91
+ }
92
+ }
@@ -0,0 +1,27 @@
1
+ import * as Y from "yjs"
2
+ import { failure } from "../utils/error"
3
+ import { getOrCreateYjsCollectionAtom } from "../utils/getOrCreateYjsCollectionAtom"
4
+
5
+ export function resolveYjsPath(yjsObject: unknown, path: readonly (string | number)[]): unknown {
6
+ let currentYjsObject: unknown = yjsObject
7
+
8
+ path.forEach((pathPart, i) => {
9
+ if (currentYjsObject instanceof Y.Map) {
10
+ getOrCreateYjsCollectionAtom(currentYjsObject).reportObserved()
11
+ const key = String(pathPart)
12
+ currentYjsObject = currentYjsObject.get(key)
13
+ } else if (currentYjsObject instanceof Y.Array) {
14
+ getOrCreateYjsCollectionAtom(currentYjsObject).reportObserved()
15
+ const key = Number(pathPart)
16
+ currentYjsObject = currentYjsObject.get(key)
17
+ } else {
18
+ throw failure(
19
+ `Y.Map or Y.Array was expected at path ${JSON.stringify(
20
+ path.slice(0, i)
21
+ )} in order to resolve path ${JSON.stringify(path)}, but got ${currentYjsObject} instead`
22
+ )
23
+ }
24
+ })
25
+
26
+ return currentYjsObject
27
+ }
@@ -1,11 +1,42 @@
1
- import { AnyType, createContext } from "mobx-keystone"
2
- import * as Y from "yjs"
3
-
4
- export interface YjsBindingContext {
5
- yjsDoc: Y.Doc
6
- yjsObject: Y.Map<unknown> | Y.Array<unknown>
7
- mobxKeystoneType: AnyType
8
- yjsOrigin: symbol
9
- }
10
-
11
- export const yjsBindingContext = createContext<YjsBindingContext | undefined>(undefined)
1
+ import { AnyType, createContext } from "mobx-keystone"
2
+ import * as Y from "yjs"
3
+
4
+ /**
5
+ * Context with info on how a mobx-keystone model is bound to a Y.js data structure.
6
+ */
7
+ export interface YjsBindingContext {
8
+ /**
9
+ * The Y.js document.
10
+ */
11
+ yjsDoc: Y.Doc
12
+
13
+ /**
14
+ * The bound Y.js data structure.
15
+ */
16
+ yjsObject: Y.Map<unknown> | Y.Array<unknown> | Y.Text
17
+
18
+ /**
19
+ * The mobx-keystone model type.
20
+ */
21
+ mobxKeystoneType: AnyType
22
+
23
+ /**
24
+ * The origin symbol used for transactions.
25
+ */
26
+ yjsOrigin: symbol
27
+
28
+ /**
29
+ * The bound mobx-keystone instance.
30
+ */
31
+ boundObject: unknown | undefined
32
+
33
+ /**
34
+ * Whether we are currently applying Y.js changes to the mobx-keystone model.
35
+ */
36
+ isApplyingYjsChangesToMobxKeystone: boolean
37
+ }
38
+
39
+ /**
40
+ * Context with info on how a mobx-keystone model is bound to a Y.js data structure.
41
+ */
42
+ export const yjsBindingContext = createContext<YjsBindingContext | undefined>(undefined)
package/src/index.ts CHANGED
@@ -1,9 +1,10 @@
1
- export { bindYjsToMobxKeystone } from "./binding/bindYjsToMobxKeystone"
2
- export {
3
- applyJsonArrayYArray,
4
- applyJsonObjectToYMap,
5
- convertJsonToYjsData,
6
- } from "./binding/convertJsonToYjsData"
7
- export { yjsBindingContext } from "./binding/yjsBindingContext"
8
- export type { YjsBindingContext } from "./binding/yjsBindingContext"
9
- export { MobxKeystoneYjsError } from "./utils/error"
1
+ export { YjsTextModel, yjsTextModelId } from "./binding/YjsTextModel"
2
+ export { bindYjsToMobxKeystone } from "./binding/bindYjsToMobxKeystone"
3
+ export {
4
+ applyJsonArrayToYArray,
5
+ applyJsonObjectToYMap,
6
+ convertJsonToYjsData,
7
+ } from "./binding/convertJsonToYjsData"
8
+ export { yjsBindingContext } from "./binding/yjsBindingContext"
9
+ export type { YjsBindingContext } from "./binding/yjsBindingContext"
10
+ export { MobxKeystoneYjsError } from "./utils/error"
@@ -0,0 +1,18 @@
1
+ import { IAtom, createAtom } from "mobx"
2
+ import * as Y from "yjs"
3
+
4
+ const yjsCollectionAtoms = new WeakMap<Y.Map<unknown> | Y.Array<unknown>, IAtom>()
5
+
6
+ /**
7
+ * @internal
8
+ */
9
+ export const getOrCreateYjsCollectionAtom = (
10
+ yjsCollection: Y.Map<unknown> | Y.Array<unknown>
11
+ ): IAtom => {
12
+ let atom = yjsCollectionAtoms.get(yjsCollection)
13
+ if (!atom) {
14
+ atom = createAtom(`yjsCollectionAtom`)
15
+ yjsCollectionAtoms.set(yjsCollection, atom)
16
+ }
17
+ return atom
18
+ }