@vuer-ai/vuer-rtc 0.8.2 → 0.8.4
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/CLAUDE.md +16 -52
- package/dist/client/textEditorHelpers.d.ts +23 -0
- package/dist/client/textEditorHelpers.d.ts.map +1 -0
- package/dist/client/textEditorHelpers.js +48 -0
- package/dist/client/textEditorHelpers.js.map +1 -0
- package/dist/operations/OperationTypes.d.ts +22 -16
- package/dist/operations/OperationTypes.d.ts.map +1 -1
- package/dist/operations/apply/text.d.ts.map +1 -1
- package/dist/operations/apply/text.js +18 -20
- package/dist/operations/apply/text.js.map +1 -1
- package/mprocs.log +1 -0
- package/package.json +1 -1
- package/src/operations/OperationTypes.ts +25 -16
- package/src/operations/apply/text.ts +20 -23
- package/tests/crdt/rope.test.ts +46 -0
package/CLAUDE.md
CHANGED
|
@@ -31,69 +31,33 @@ This ensures high-quality implementations and catches issues early.
|
|
|
31
31
|
|
|
32
32
|
### Deployment Process
|
|
33
33
|
|
|
34
|
-
**Publishing a new version requires careful coordination between npm publish and documentation site updates.**
|
|
35
|
-
|
|
36
|
-
#### Complete Deployment Workflow
|
|
37
|
-
|
|
38
34
|
```bash
|
|
39
|
-
# 1. Update package version
|
|
40
|
-
# Edit packages/vuer-rtc/package.json → bump version (e.g., 0.6.0)
|
|
35
|
+
# 1. Update package version in packages/vuer-rtc/package.json
|
|
41
36
|
|
|
42
|
-
# 2. Build and test
|
|
37
|
+
# 2. Build and test
|
|
43
38
|
pnpm build && pnpm test
|
|
44
39
|
|
|
45
|
-
# 3. Commit
|
|
46
|
-
git add -A && git commit -m "Release v0.
|
|
40
|
+
# 3. Commit and push
|
|
41
|
+
git add -A && git commit -m "Release v0.x.x: <summary>" && git push
|
|
47
42
|
|
|
48
43
|
# 4. Publish to npm
|
|
44
|
+
cd packages/vuer-rtc
|
|
49
45
|
pass otp npmjs && pnpm publish --otp $(pass otp npmjs)
|
|
46
|
+
cd ../..
|
|
50
47
|
|
|
51
|
-
# 5.
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
pnpm install # Updates pnpm-lock.yaml
|
|
55
|
-
|
|
56
|
-
# 6. Commit and deploy
|
|
57
|
-
git add -A && git commit -m "Update docs to v0.6.0"
|
|
58
|
-
cd ..
|
|
59
|
-
pnpm build # ← IMPORTANT: Rebuild with new version
|
|
60
|
-
pnpm run deploy # ← IMPORTANT: Uses workspace deploy script, pushes to main:netlify-production
|
|
61
|
-
|
|
62
|
-
# 7. WAIT for Netlify build (2-3 minutes)
|
|
63
|
-
# Do NOT check the site immediately - Netlify needs time to build and deploy
|
|
48
|
+
# 5. Deploy docs and server
|
|
49
|
+
pnpm build && pnpm run deploy # Netlify (main:netlify-production)
|
|
50
|
+
git push heroku main # Heroku server
|
|
64
51
|
|
|
65
|
-
#
|
|
66
|
-
curl https://rtc.vuer.ai | grep "v0.
|
|
67
|
-
# Or visit https://rtc.vuer.ai and check the version badge in the hero section
|
|
52
|
+
# 6. Verify after 2-3 minutes
|
|
53
|
+
curl https://rtc.vuer.ai | grep "v0.x.x"
|
|
68
54
|
```
|
|
69
55
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
-
|
|
74
|
-
|
|
75
|
-
// vuer-rtc-docs/vite.config.ts
|
|
76
|
-
alias: {
|
|
77
|
-
'#vuer-rtc-pkg': path.resolve(__dirname, '../packages/vuer-rtc/package.json')
|
|
78
|
-
}
|
|
79
|
-
```
|
|
80
|
-
- The VuerRTCBadge component imports version from this alias:
|
|
81
|
-
```typescript
|
|
82
|
-
import packageJson from '#vuer-rtc-pkg';
|
|
83
|
-
const PACKAGE_VERSION = packageJson.version;
|
|
84
|
-
```
|
|
85
|
-
- This means the site reads the version from the workspace, NOT from npm
|
|
86
|
-
- The lockfile change triggers Netlify rebuild, which picks up the updated local package.json
|
|
87
|
-
|
|
88
|
-
**Why to use `pnpm run deploy`:**
|
|
89
|
-
- The workspace has a deploy script: `"deploy": "git push origin main:netlify-production"`
|
|
90
|
-
- This pushes to the correct branch that triggers Netlify deployment
|
|
91
|
-
- Using manual `git push` may push to the wrong branch
|
|
92
|
-
|
|
93
|
-
**Why to wait:**
|
|
94
|
-
- Netlify build takes 2-3 minutes after push
|
|
95
|
-
- Checking the site immediately will show stale version
|
|
96
|
-
- Wait for the build to complete before verifying
|
|
56
|
+
**Notes:**
|
|
57
|
+
- Docs use `workspace:^` so they always point to local package
|
|
58
|
+
- Version badge reads from local package.json via Vite alias
|
|
59
|
+
- `pnpm run deploy` pushes to `main:netlify-production` branch
|
|
60
|
+
- Netlify triggers on any commit to netlify-production branch
|
|
97
61
|
|
|
98
62
|
## Text Architecture (3-Component Design)
|
|
99
63
|
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Text Editor Helpers
|
|
3
|
+
*
|
|
4
|
+
* Helper functions to create text operations from position-based editor input.
|
|
5
|
+
* These operations include position fields that are converted to CRDT format
|
|
6
|
+
* by the text.ts handlers.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Helper to create a text.insert operation from position-based input.
|
|
10
|
+
* The operation will be converted to CRDT format by the text.ts handler.
|
|
11
|
+
*/
|
|
12
|
+
export declare function textInsert(key: string, path: string, position: number, content: string): any;
|
|
13
|
+
/**
|
|
14
|
+
* Helper to create a text.delete operation from position-based input.
|
|
15
|
+
* The operation will be converted to CRDT format by the text.ts handler.
|
|
16
|
+
*/
|
|
17
|
+
export declare function textDelete(key: string, path: string, position: number, length: number): any;
|
|
18
|
+
/**
|
|
19
|
+
* Helper to create a text.replace operation from position-based input.
|
|
20
|
+
* The operation will be converted to CRDT format by the text.ts handler.
|
|
21
|
+
*/
|
|
22
|
+
export declare function textReplace(key: string, path: string, position: number, length: number, content: string): any;
|
|
23
|
+
//# sourceMappingURL=textEditorHelpers.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"textEditorHelpers.d.ts","sourceRoot":"","sources":["../../src/client/textEditorHelpers.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH;;;GAGG;AACH,wBAAgB,UAAU,CACxB,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,GACd,GAAG,CAQL;AAED;;;GAGG;AACH,wBAAgB,UAAU,CACxB,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,GACb,GAAG,CAQL;AAED;;;GAGG;AACH,wBAAgB,WAAW,CACzB,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,GACd,GAAG,CASL"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Text Editor Helpers
|
|
3
|
+
*
|
|
4
|
+
* Helper functions to create text operations from position-based editor input.
|
|
5
|
+
* These operations include position fields that are converted to CRDT format
|
|
6
|
+
* by the text.ts handlers.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Helper to create a text.insert operation from position-based input.
|
|
10
|
+
* The operation will be converted to CRDT format by the text.ts handler.
|
|
11
|
+
*/
|
|
12
|
+
export function textInsert(key, path, position, content) {
|
|
13
|
+
return {
|
|
14
|
+
ot: 'text.insert',
|
|
15
|
+
key,
|
|
16
|
+
path,
|
|
17
|
+
position,
|
|
18
|
+
value: [null, content], // [anchor, content] tuple
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Helper to create a text.delete operation from position-based input.
|
|
23
|
+
* The operation will be converted to CRDT format by the text.ts handler.
|
|
24
|
+
*/
|
|
25
|
+
export function textDelete(key, path, position, length) {
|
|
26
|
+
return {
|
|
27
|
+
ot: 'text.delete',
|
|
28
|
+
key,
|
|
29
|
+
path,
|
|
30
|
+
position,
|
|
31
|
+
length,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Helper to create a text.replace operation from position-based input.
|
|
36
|
+
* The operation will be converted to CRDT format by the text.ts handler.
|
|
37
|
+
*/
|
|
38
|
+
export function textReplace(key, path, position, length, content) {
|
|
39
|
+
return {
|
|
40
|
+
ot: 'text.replace',
|
|
41
|
+
key,
|
|
42
|
+
path,
|
|
43
|
+
position,
|
|
44
|
+
length,
|
|
45
|
+
value: [null, content], // [anchor, content] tuple
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=textEditorHelpers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"textEditorHelpers.js","sourceRoot":"","sources":["../../src/client/textEditorHelpers.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH;;;GAGG;AACH,MAAM,UAAU,UAAU,CACxB,GAAW,EACX,IAAY,EACZ,QAAgB,EAChB,OAAe;IAEf,OAAO;QACL,EAAE,EAAE,aAAa;QACjB,GAAG;QACH,IAAI;QACJ,QAAQ;QACR,KAAK,EAAE,CAAC,IAAI,EAAE,OAAO,CAAC,EAAG,0BAA0B;KACpD,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,UAAU,CACxB,GAAW,EACX,IAAY,EACZ,QAAgB,EAChB,MAAc;IAEd,OAAO;QACL,EAAE,EAAE,aAAa;QACjB,GAAG;QACH,IAAI;QACJ,QAAQ;QACR,MAAM;KACP,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,WAAW,CACzB,GAAW,EACX,IAAY,EACZ,QAAgB,EAChB,MAAc,EACd,OAAe;IAEf,OAAO;QACL,EAAE,EAAE,cAAc;QAClB,GAAG;QACH,IAAI;QACJ,QAAQ;QACR,MAAM;QACN,KAAK,EAAE,CAAC,IAAI,EAAE,OAAO,CAAC,EAAG,0BAA0B;KACpD,CAAC;AACJ,CAAC"}
|
|
@@ -81,25 +81,31 @@ export interface TextInitOp extends BaseOp {
|
|
|
81
81
|
}
|
|
82
82
|
/**
|
|
83
83
|
* Insert text - supports two formats:
|
|
84
|
-
* 1. Position-based (for local operations): { position, value }
|
|
85
|
-
* 2. CRDT metadata (for network sync): { id,
|
|
84
|
+
* 1. Position-based (for local operations): { position, value: string }
|
|
85
|
+
* 2. CRDT metadata (for network sync): { id, value: [anchor, content], seq, ts }
|
|
86
|
+
*
|
|
87
|
+
* The value field changes type based on format:
|
|
88
|
+
* - Position-based: value is string ("hello")
|
|
89
|
+
* - CRDT: value is [anchor, content] tuple (["client:42", "hello"])
|
|
86
90
|
*/
|
|
87
91
|
export interface TextInsertOp extends BaseOp {
|
|
88
92
|
key: string;
|
|
89
93
|
ot: 'text.insert';
|
|
90
94
|
path: string;
|
|
91
95
|
position?: number;
|
|
92
|
-
value?: string;
|
|
96
|
+
value?: string | [string | null, string];
|
|
93
97
|
id?: string;
|
|
94
|
-
content?: string;
|
|
95
|
-
parentId?: string | null;
|
|
96
98
|
seq?: number;
|
|
97
99
|
ts?: number;
|
|
100
|
+
_lastCharId?: string;
|
|
98
101
|
}
|
|
99
102
|
/**
|
|
100
103
|
* Delete text - supports two formats:
|
|
101
104
|
* 1. Position-based (for local operations): { position, length }
|
|
102
|
-
* 2. CRDT metadata (for network sync): {
|
|
105
|
+
* 2. CRDT metadata (for network sync): { rm }
|
|
106
|
+
*
|
|
107
|
+
* rm format: Array of [itemId, count] tuples
|
|
108
|
+
* Example: rm: [["client:42", 5], ["client:48", 3]]
|
|
103
109
|
*/
|
|
104
110
|
export interface TextDeleteOp extends BaseOp {
|
|
105
111
|
key: string;
|
|
@@ -107,12 +113,17 @@ export interface TextDeleteOp extends BaseOp {
|
|
|
107
113
|
path: string;
|
|
108
114
|
position?: number;
|
|
109
115
|
length?: number;
|
|
110
|
-
|
|
116
|
+
rm?: Array<[string, number]>;
|
|
111
117
|
}
|
|
112
118
|
/**
|
|
113
119
|
* Replace text - atomic delete + insert (for select-and-type)
|
|
114
|
-
*
|
|
115
|
-
*
|
|
120
|
+
*
|
|
121
|
+
* Position-based: { position, length, value: string }
|
|
122
|
+
* CRDT metadata: { rm, id, value: [anchor, content], seq, ts }
|
|
123
|
+
*
|
|
124
|
+
* The value field changes type based on format:
|
|
125
|
+
* - Position-based: value is string ("hello")
|
|
126
|
+
* - CRDT: value is [anchor, content] tuple (["client:42", "hello"])
|
|
116
127
|
*/
|
|
117
128
|
export interface TextReplaceOp extends BaseOp {
|
|
118
129
|
key: string;
|
|
@@ -120,14 +131,9 @@ export interface TextReplaceOp extends BaseOp {
|
|
|
120
131
|
path: string;
|
|
121
132
|
position?: number;
|
|
122
133
|
length?: number;
|
|
123
|
-
value?: string;
|
|
124
|
-
|
|
125
|
-
id: string;
|
|
126
|
-
length: number;
|
|
127
|
-
}>;
|
|
134
|
+
value?: string | [string | null, string];
|
|
135
|
+
rm?: Array<[string, number]>;
|
|
128
136
|
id?: string;
|
|
129
|
-
content?: string;
|
|
130
|
-
parentId?: string | null;
|
|
131
137
|
seq?: number;
|
|
132
138
|
ts?: number;
|
|
133
139
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"OperationTypes.d.ts","sourceRoot":"","sources":["../../src/operations/OperationTypes.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAE3D;;GAEG;AACH,MAAM,WAAW,WAAW;IAE1B,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,WAAW,CAAC;IACnB,EAAE,EAAE,MAAM,CAAC;IACX,EAAE,EAAE,MAAM,CAAC;IAGX,GAAG,EAAE,SAAS,EAAE,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,MAAM;IACrB,GAAG,EAAE,MAAM,CAAC;IACZ,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;CACd;AAMD,MAAM,WAAW,WAAY,SAAQ,MAAM;IACzC,GAAG,EAAE,MAAM,CAAC;IACZ,EAAE,EAAE,YAAY,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,WAAY,SAAQ,MAAM;IACzC,GAAG,EAAE,MAAM,CAAC;IACZ,EAAE,EAAE,YAAY,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,gBAAiB,SAAQ,MAAM;IAC9C,GAAG,EAAE,MAAM,CAAC;IACZ,EAAE,EAAE,iBAAiB,CAAC;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,WAAY,SAAQ,MAAM;IACzC,GAAG,EAAE,MAAM,CAAC;IACZ,EAAE,EAAE,YAAY,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,WAAY,SAAQ,MAAM;IACzC,GAAG,EAAE,MAAM,CAAC;IACZ,EAAE,EAAE,YAAY,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;CACf;AAMD,MAAM,WAAW,WAAY,SAAQ,MAAM;IACzC,GAAG,EAAE,MAAM,CAAC;IACZ,EAAE,EAAE,YAAY,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,cAAe,SAAQ,MAAM;IAC5C,GAAG,EAAE,MAAM,CAAC;IACZ,EAAE,EAAE,eAAe,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAMD;;;GAGG;AACH,MAAM,WAAW,UAAW,SAAQ,MAAM;IACxC,GAAG,EAAE,MAAM,CAAC;IACZ,EAAE,EAAE,WAAW,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED
|
|
1
|
+
{"version":3,"file":"OperationTypes.d.ts","sourceRoot":"","sources":["../../src/operations/OperationTypes.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"text.d.ts","sourceRoot":"","sources":["../../../src/operations/apply/text.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EACV,UAAU,EAEV,UAAU,EACV,YAAY,EACZ,YAAY,EACZ,aAAa,EACd,MAAM,sBAAsB,CAAC;AAC9B,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AACzC,OAAO,EAUL,QAAQ,EAET,MAAM,oBAAoB,CAAC;AAwD5B;;GAEG;AACH,wBAAgB,QAAQ,CACtB,KAAK,EAAE,UAAU,EACjB,EAAE,EAAE,UAAU,EACd,IAAI,EAAE,MAAM,GACX,IAAI,CAWN;AAED;;GAEG;AACH,wBAAgB,UAAU,CACxB,KAAK,EAAE,UAAU,EACjB,EAAE,EAAE,YAAY,EAChB,IAAI,EAAE,MAAM,GACX,IAAI,CA6BN;AAED;;GAEG;AACH,wBAAgB,UAAU,CACxB,KAAK,EAAE,UAAU,EACjB,EAAE,EAAE,YAAY,EAChB,IAAI,EAAE,MAAM,GACX,IAAI,
|
|
1
|
+
{"version":3,"file":"text.d.ts","sourceRoot":"","sources":["../../../src/operations/apply/text.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EACV,UAAU,EAEV,UAAU,EACV,YAAY,EACZ,YAAY,EACZ,aAAa,EACd,MAAM,sBAAsB,CAAC;AAC9B,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AACzC,OAAO,EAUL,QAAQ,EAET,MAAM,oBAAoB,CAAC;AAwD5B;;GAEG;AACH,wBAAgB,QAAQ,CACtB,KAAK,EAAE,UAAU,EACjB,EAAE,EAAE,UAAU,EACd,IAAI,EAAE,MAAM,GACX,IAAI,CAWN;AAED;;GAEG;AACH,wBAAgB,UAAU,CACxB,KAAK,EAAE,UAAU,EACjB,EAAE,EAAE,YAAY,EAChB,IAAI,EAAE,MAAM,GACX,IAAI,CA6BN;AAED;;GAEG;AACH,wBAAgB,UAAU,CACxB,KAAK,EAAE,UAAU,EACjB,EAAE,EAAE,YAAY,EAChB,IAAI,EAAE,MAAM,GACX,IAAI,CAmBN;AAED;;GAEG;AACH,wBAAgB,WAAW,CACzB,KAAK,EAAE,UAAU,EACjB,EAAE,EAAE,aAAa,EACjB,IAAI,EAAE,MAAM,GACX,IAAI,CAgCN;AAED;;GAEG;AACH,wBAAgB,eAAe,CAC7B,KAAK,EAAE,UAAU,EACjB,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,GACX,QAAQ,GAAG,SAAS,CAItB"}
|
|
@@ -83,26 +83,26 @@ export function TextInsert(draft, op, meta) {
|
|
|
83
83
|
if (!node)
|
|
84
84
|
return;
|
|
85
85
|
const rope = getOrCreateRope(node, op.path, meta.client);
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
//
|
|
86
|
+
// Check if value is already a tuple (CRDT format)
|
|
87
|
+
const isTupleFormat = Array.isArray(op.value) && op.value.length === 2;
|
|
88
|
+
if (op.id !== undefined && isTupleFormat && op.seq !== undefined) {
|
|
89
|
+
// CRDT metadata format (from network) - value is already [anchor, content] tuple
|
|
90
90
|
apply(rope, {
|
|
91
91
|
ot: 'insert',
|
|
92
92
|
id: op.id,
|
|
93
|
-
value, //
|
|
93
|
+
value: op.value, // Already tuple format
|
|
94
94
|
seq: op.seq,
|
|
95
95
|
ts: op.ts ?? Date.now() / 1000,
|
|
96
96
|
});
|
|
97
97
|
}
|
|
98
|
-
else if (op.position !== undefined && op.value
|
|
98
|
+
else if (op.position !== undefined && typeof op.value === 'string') {
|
|
99
99
|
// Position-based format (local edit) → convert to CRDT metadata in-place
|
|
100
100
|
const crdtOp = insertWithSplit(rope, op.position, op.value, meta.client);
|
|
101
101
|
op.id = crdtOp.id;
|
|
102
|
-
|
|
103
|
-
op.value = crdtOp.value;
|
|
102
|
+
op.value = crdtOp.value; // Store as tuple [anchor, content]
|
|
104
103
|
op.seq = crdtOp.seq;
|
|
105
104
|
op.ts = crdtOp.ts;
|
|
105
|
+
op._lastCharId = crdtOp._lastCharId; // Preserve multi-char metadata
|
|
106
106
|
}
|
|
107
107
|
// node[path] is the mutated TextRope (React will call .toString())
|
|
108
108
|
}
|
|
@@ -116,11 +116,10 @@ export function TextDelete(draft, op, meta) {
|
|
|
116
116
|
const rope = getRope(node, op.path);
|
|
117
117
|
if (!rope)
|
|
118
118
|
return;
|
|
119
|
-
|
|
120
|
-
if (deletionsArray !== undefined) {
|
|
119
|
+
if (op.rm !== undefined) {
|
|
121
120
|
// CRDT metadata format (replay from journal)
|
|
122
121
|
// rm is already in tuple format: [[id, len], ...]
|
|
123
|
-
applyDelete(rope, { ot: 'delete', rm:
|
|
122
|
+
applyDelete(rope, { ot: 'delete', rm: op.rm });
|
|
124
123
|
}
|
|
125
124
|
else if (op.position !== undefined && op.length !== undefined) {
|
|
126
125
|
// Position-based format (local edit) → convert to CRDT metadata in-place
|
|
@@ -138,28 +137,27 @@ export function TextReplace(draft, op, meta) {
|
|
|
138
137
|
if (!node)
|
|
139
138
|
return;
|
|
140
139
|
const rope = getOrCreateRope(node, op.path, meta.client);
|
|
141
|
-
|
|
142
|
-
const
|
|
143
|
-
if (
|
|
140
|
+
// Check if value is already a tuple (CRDT format)
|
|
141
|
+
const isTupleFormat = Array.isArray(op.value) && op.value.length === 2;
|
|
142
|
+
if (op.rm !== undefined && op.id !== undefined && isTupleFormat && op.seq !== undefined) {
|
|
144
143
|
// CRDT metadata format (replay from journal)
|
|
145
144
|
// Value is already [anchor, content] tuple
|
|
146
145
|
applyReplace(rope, {
|
|
147
146
|
ot: 'replace',
|
|
148
|
-
rm:
|
|
147
|
+
rm: op.rm,
|
|
149
148
|
id: op.id,
|
|
150
|
-
value: value
|
|
149
|
+
value: op.value, // Already tuple format
|
|
151
150
|
seq: op.seq,
|
|
152
151
|
ts: op.ts ?? Date.now() / 1000,
|
|
153
152
|
});
|
|
154
153
|
}
|
|
155
|
-
else if (op.position !== undefined && op.length !== undefined && op.value
|
|
154
|
+
else if (op.position !== undefined && op.length !== undefined && typeof op.value === 'string') {
|
|
156
155
|
// Position-based format (local edit) → convert to CRDT metadata in-place
|
|
157
156
|
const crdtOp = replace(rope, op.position, op.length, op.value, meta.client);
|
|
158
|
-
//
|
|
157
|
+
// Store CRDT metadata in tuple format
|
|
159
158
|
op.rm = crdtOp.rm;
|
|
160
159
|
op.id = crdtOp.id;
|
|
161
|
-
|
|
162
|
-
op.value = crdtOp.value;
|
|
160
|
+
op.value = crdtOp.value; // Store as tuple [anchor, content]
|
|
163
161
|
op.seq = crdtOp.seq;
|
|
164
162
|
op.ts = crdtOp.ts;
|
|
165
163
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"text.js","sourceRoot":"","sources":["../../../src/operations/apply/text.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAWH,OAAO,EACL,MAAM,EAEN,MAAM,EACN,eAAe,EACf,MAAM,EACN,OAAO,EACP,KAAK,EACL,WAAW,EACX,YAAY,EACZ,QAAQ,EACR,WAAW,GACZ,MAAM,oBAAoB,CAAC;AAE5B;;;;;;;GAOG;AACH,SAAS,eAAe,CAAC,IAAe,EAAE,IAAY,EAAE,OAAe;IACrE,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC;IAEzB,kCAAkC;IAClC,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAC9B,MAAM,IAAI,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC;QAC7B,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACrB,MAAM,CAAC,IAAI,EAAE,CAAC,EAAE,KAAK,CAAC,CAAC;QACzB,CAAC;QACD,IAAI,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;QAClB,OAAO,IAAI,CAAC;IACd,CAAC;IAED,qBAAqB;IACrB,IAAI,KAAK,YAAY,QAAQ,EAAE,CAAC;QAC9B,OAAO,KAAK,CAAC;IACf,CAAC;IAED,+EAA+E;IAC/E,IAAI,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAChD,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,WAAW,CAAC,KAAK,CAAC,CAAC;YAChC,+DAA+D;YAC/D,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;YACvB,IAAI,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;YAClB,OAAO,IAAI,CAAC;QACd,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,4DAA4D;YAC5D,OAAO,CAAC,IAAI,CAAC,iCAAiC,IAAI,GAAG,EAAE,CAAC,CAAC,CAAC;QAC5D,CAAC;IACH,CAAC;IAED,uCAAuC;IACvC,MAAM,IAAI,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC;IAC7B,IAAI,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;IAClB,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;GAEG;AACH,SAAS,OAAO,CAAC,IAAe,EAAE,IAAY;IAC5C,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC;IACzB,OAAO,KAAK,YAAY,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;AACvD,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,QAAQ,CACtB,KAAiB,EACjB,EAAc,EACd,IAAY;IAEZ,MAAM,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC;IACjC,IAAI,CAAC,IAAI;QAAE,OAAO;IAElB,MAAM,IAAI,GAAG,eAAe,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;IAEzD,IAAI,EAAE,CAAC,KAAK,IAAI,EAAE,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACpC,MAAM,CAAC,IAAI,EAAE,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,CAAC;IAC5B,CAAC;IAED,0CAA0C;AAC5C,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,UAAU,CACxB,KAAiB,EACjB,EAAgB,EAChB,IAAY;IAEZ,MAAM,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC;IACjC,IAAI,CAAC,IAAI;QAAE,OAAO;IAElB,MAAM,IAAI,GAAG,eAAe,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;IAEzD,MAAM,KAAK,
|
|
1
|
+
{"version":3,"file":"text.js","sourceRoot":"","sources":["../../../src/operations/apply/text.ts"],"names":[],"mappings":"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"}
|
package/mprocs.log
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
ERROR [mprocs::error] Error: channel closed
|
package/package.json
CHANGED
|
@@ -108,8 +108,12 @@ export interface TextInitOp extends BaseOp {
|
|
|
108
108
|
|
|
109
109
|
/**
|
|
110
110
|
* Insert text - supports two formats:
|
|
111
|
-
* 1. Position-based (for local operations): { position, value }
|
|
112
|
-
* 2. CRDT metadata (for network sync): { id,
|
|
111
|
+
* 1. Position-based (for local operations): { position, value: string }
|
|
112
|
+
* 2. CRDT metadata (for network sync): { id, value: [anchor, content], seq, ts }
|
|
113
|
+
*
|
|
114
|
+
* The value field changes type based on format:
|
|
115
|
+
* - Position-based: value is string ("hello")
|
|
116
|
+
* - CRDT: value is [anchor, content] tuple (["client:42", "hello"])
|
|
113
117
|
*/
|
|
114
118
|
export interface TextInsertOp extends BaseOp {
|
|
115
119
|
key: string;
|
|
@@ -117,19 +121,21 @@ export interface TextInsertOp extends BaseOp {
|
|
|
117
121
|
path: string;
|
|
118
122
|
// Position-based format (local convenience)
|
|
119
123
|
position?: number;
|
|
120
|
-
value?: string;
|
|
124
|
+
value?: string | [string | null, string]; // string for position-based, tuple for CRDT
|
|
121
125
|
// CRDT metadata format (network sync)
|
|
122
|
-
id?: string; // ItemId string format: "
|
|
123
|
-
content?: string;
|
|
124
|
-
parentId?: string | null; // ItemId string format
|
|
126
|
+
id?: string; // ItemId string format: "client:seq"
|
|
125
127
|
seq?: number; // Lamport timestamp
|
|
126
128
|
ts?: number; // Wall-clock time (seconds) for tie-breaking
|
|
129
|
+
_lastCharId?: string; // Last character ID (for multi-char inserts)
|
|
127
130
|
}
|
|
128
131
|
|
|
129
132
|
/**
|
|
130
133
|
* Delete text - supports two formats:
|
|
131
134
|
* 1. Position-based (for local operations): { position, length }
|
|
132
|
-
* 2. CRDT metadata (for network sync): {
|
|
135
|
+
* 2. CRDT metadata (for network sync): { rm }
|
|
136
|
+
*
|
|
137
|
+
* rm format: Array of [itemId, count] tuples
|
|
138
|
+
* Example: rm: [["client:42", 5], ["client:48", 3]]
|
|
133
139
|
*/
|
|
134
140
|
export interface TextDeleteOp extends BaseOp {
|
|
135
141
|
key: string;
|
|
@@ -139,13 +145,18 @@ export interface TextDeleteOp extends BaseOp {
|
|
|
139
145
|
position?: number;
|
|
140
146
|
length?: number;
|
|
141
147
|
// CRDT metadata format (network sync) - compressed tuple format
|
|
142
|
-
|
|
148
|
+
rm?: Array<[string, number]>; // [[itemId, length], ...]
|
|
143
149
|
}
|
|
144
150
|
|
|
145
151
|
/**
|
|
146
152
|
* Replace text - atomic delete + insert (for select-and-type)
|
|
147
|
-
*
|
|
148
|
-
*
|
|
153
|
+
*
|
|
154
|
+
* Position-based: { position, length, value: string }
|
|
155
|
+
* CRDT metadata: { rm, id, value: [anchor, content], seq, ts }
|
|
156
|
+
*
|
|
157
|
+
* The value field changes type based on format:
|
|
158
|
+
* - Position-based: value is string ("hello")
|
|
159
|
+
* - CRDT: value is [anchor, content] tuple (["client:42", "hello"])
|
|
149
160
|
*/
|
|
150
161
|
export interface TextReplaceOp extends BaseOp {
|
|
151
162
|
key: string;
|
|
@@ -154,13 +165,11 @@ export interface TextReplaceOp extends BaseOp {
|
|
|
154
165
|
// Position-based format (local convenience)
|
|
155
166
|
position?: number;
|
|
156
167
|
length?: number; // chars to delete
|
|
157
|
-
value?: string;
|
|
168
|
+
value?: string | [string | null, string]; // string for position-based, tuple for CRDT
|
|
158
169
|
// CRDT metadata format (network sync)
|
|
159
|
-
|
|
160
|
-
id?: string; // ItemId string format
|
|
161
|
-
|
|
162
|
-
parentId?: string | null; // ItemId string format
|
|
163
|
-
seq?: number;
|
|
170
|
+
rm?: Array<[string, number]>; // [[itemId, length], ...] deletions
|
|
171
|
+
id?: string; // ItemId string format: "client:seq"
|
|
172
|
+
seq?: number; // Lamport timestamp
|
|
164
173
|
ts?: number; // Wall-clock time (seconds) for tie-breaking
|
|
165
174
|
}
|
|
166
175
|
|
|
@@ -123,26 +123,26 @@ export function TextInsert(
|
|
|
123
123
|
|
|
124
124
|
const rope = getOrCreateRope(node, op.path, meta.client);
|
|
125
125
|
|
|
126
|
-
|
|
126
|
+
// Check if value is already a tuple (CRDT format)
|
|
127
|
+
const isTupleFormat = Array.isArray(op.value) && op.value.length === 2;
|
|
127
128
|
|
|
128
|
-
if (op.id !== undefined &&
|
|
129
|
-
// CRDT metadata format (
|
|
130
|
-
// Value is already [anchor, content] tuple
|
|
129
|
+
if (op.id !== undefined && isTupleFormat && op.seq !== undefined) {
|
|
130
|
+
// CRDT metadata format (from network) - value is already [anchor, content] tuple
|
|
131
131
|
apply(rope, {
|
|
132
132
|
ot: 'insert',
|
|
133
133
|
id: op.id,
|
|
134
|
-
value, //
|
|
134
|
+
value: op.value as [string | null, string], // Already tuple format
|
|
135
135
|
seq: op.seq,
|
|
136
136
|
ts: op.ts ?? Date.now() / 1000,
|
|
137
137
|
});
|
|
138
|
-
} else if (op.position !== undefined && op.value
|
|
138
|
+
} else if (op.position !== undefined && typeof op.value === 'string') {
|
|
139
139
|
// Position-based format (local edit) → convert to CRDT metadata in-place
|
|
140
140
|
const crdtOp = insertWithSplit(rope, op.position, op.value, meta.client);
|
|
141
141
|
op.id = crdtOp.id;
|
|
142
|
-
|
|
143
|
-
(op as any).value = crdtOp.value;
|
|
142
|
+
op.value = crdtOp.value; // Store as tuple [anchor, content]
|
|
144
143
|
op.seq = crdtOp.seq;
|
|
145
144
|
op.ts = crdtOp.ts;
|
|
145
|
+
(op as any)._lastCharId = (crdtOp as any)._lastCharId; // Preserve multi-char metadata
|
|
146
146
|
}
|
|
147
147
|
|
|
148
148
|
// node[path] is the mutated TextRope (React will call .toString())
|
|
@@ -162,17 +162,15 @@ export function TextDelete(
|
|
|
162
162
|
const rope = getRope(node, op.path);
|
|
163
163
|
if (!rope) return;
|
|
164
164
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
if (deletionsArray !== undefined) {
|
|
165
|
+
if (op.rm !== undefined) {
|
|
168
166
|
// CRDT metadata format (replay from journal)
|
|
169
167
|
// rm is already in tuple format: [[id, len], ...]
|
|
170
|
-
applyDelete(rope, { ot: 'delete', rm:
|
|
168
|
+
applyDelete(rope, { ot: 'delete', rm: op.rm });
|
|
171
169
|
} else if (op.position !== undefined && op.length !== undefined) {
|
|
172
170
|
// Position-based format (local edit) → convert to CRDT metadata in-place
|
|
173
171
|
const crdtOp = remove(rope, op.position, op.length);
|
|
174
172
|
// rm is already in tuple format from remove()
|
|
175
|
-
|
|
173
|
+
op.rm = crdtOp.rm;
|
|
176
174
|
}
|
|
177
175
|
|
|
178
176
|
// node[path] is the mutated TextRope
|
|
@@ -191,28 +189,27 @@ export function TextReplace(
|
|
|
191
189
|
|
|
192
190
|
const rope = getOrCreateRope(node, op.path, meta.client);
|
|
193
191
|
|
|
194
|
-
|
|
195
|
-
const
|
|
192
|
+
// Check if value is already a tuple (CRDT format)
|
|
193
|
+
const isTupleFormat = Array.isArray(op.value) && op.value.length === 2;
|
|
196
194
|
|
|
197
|
-
if (
|
|
195
|
+
if (op.rm !== undefined && op.id !== undefined && isTupleFormat && op.seq !== undefined) {
|
|
198
196
|
// CRDT metadata format (replay from journal)
|
|
199
197
|
// Value is already [anchor, content] tuple
|
|
200
198
|
applyReplace(rope, {
|
|
201
199
|
ot: 'replace',
|
|
202
|
-
rm:
|
|
200
|
+
rm: op.rm,
|
|
203
201
|
id: op.id,
|
|
204
|
-
value: value
|
|
202
|
+
value: op.value as [string | null, string], // Already tuple format
|
|
205
203
|
seq: op.seq,
|
|
206
204
|
ts: op.ts ?? Date.now() / 1000,
|
|
207
205
|
});
|
|
208
|
-
} else if (op.position !== undefined && op.length !== undefined && op.value
|
|
206
|
+
} else if (op.position !== undefined && op.length !== undefined && typeof op.value === 'string') {
|
|
209
207
|
// Position-based format (local edit) → convert to CRDT metadata in-place
|
|
210
208
|
const crdtOp = replace(rope, op.position, op.length, op.value, meta.client);
|
|
211
|
-
//
|
|
212
|
-
|
|
209
|
+
// Store CRDT metadata in tuple format
|
|
210
|
+
op.rm = crdtOp.rm;
|
|
213
211
|
op.id = crdtOp.id;
|
|
214
|
-
|
|
215
|
-
(op as any).value = crdtOp.value;
|
|
212
|
+
op.value = crdtOp.value; // Store as tuple [anchor, content]
|
|
216
213
|
op.seq = crdtOp.seq;
|
|
217
214
|
op.ts = crdtOp.ts;
|
|
218
215
|
}
|
package/tests/crdt/rope.test.ts
CHANGED
|
@@ -235,6 +235,52 @@ describe('TextRope', () => {
|
|
|
235
235
|
// Verify the text is correct after the edit
|
|
236
236
|
expect(getText(alice)).toContain('for in in range');
|
|
237
237
|
});
|
|
238
|
+
|
|
239
|
+
it('should order items with same anchor consistently (YATA LIFO)', () => {
|
|
240
|
+
// Reproduces the scenario from message-log-1772434922593.json
|
|
241
|
+
// where items with same anchor should use consistent ordering
|
|
242
|
+
const alice = create('Alice');
|
|
243
|
+
const bob = create('Bob');
|
|
244
|
+
|
|
245
|
+
// Create a shared anchor item "X"
|
|
246
|
+
const anchorOp = insert(bob, 0, 'X');
|
|
247
|
+
apply(alice, anchorOp);
|
|
248
|
+
|
|
249
|
+
// Bob inserts "B" with anchor "X", with seq 39
|
|
250
|
+
const bobOp: any = {
|
|
251
|
+
ot: 'insert',
|
|
252
|
+
id: 'Bob:1',
|
|
253
|
+
value: ['Bob:0', 'B'],
|
|
254
|
+
seq: 39,
|
|
255
|
+
ts: Date.now() / 1000,
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
// Alice inserts "A" with anchor "X", with seq 54 (later)
|
|
259
|
+
const aliceOp: any = {
|
|
260
|
+
ot: 'insert',
|
|
261
|
+
id: 'Alice:0',
|
|
262
|
+
value: ['Bob:0', 'A'],
|
|
263
|
+
seq: 54,
|
|
264
|
+
ts: Date.now() / 1000 + 0.001, // Slightly later timestamp
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
// Apply operations locally first
|
|
268
|
+
apply(bob, bobOp);
|
|
269
|
+
apply(alice, aliceOp);
|
|
270
|
+
|
|
271
|
+
// Exchange operations - convergence test
|
|
272
|
+
apply(alice, bobOp);
|
|
273
|
+
apply(bob, aliceOp);
|
|
274
|
+
|
|
275
|
+
// Both should converge (same final text)
|
|
276
|
+
const aliceText = getText(alice);
|
|
277
|
+
const bobText = getText(bob);
|
|
278
|
+
|
|
279
|
+
expect(aliceText).toBe(bobText); // Convergence guarantee
|
|
280
|
+
// The actual order is determined by YATA algorithm with LIFO semantics
|
|
281
|
+
// Later seq (Alice:54) comes before earlier seq (Bob:39) at same position
|
|
282
|
+
expect(aliceText).toBe('XAB');
|
|
283
|
+
});
|
|
238
284
|
});
|
|
239
285
|
|
|
240
286
|
describe('Merge', () => {
|