1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 | var types = require('./types').map;
|
7 | var ShareDBError = require('./error');
|
8 | var util = require('./util');
|
9 |
|
10 | var ERROR_CODE = ShareDBError.CODES;
|
11 |
|
12 |
|
13 | exports.checkOp = function(op) {
|
14 | if (op == null || typeof op !== 'object') {
|
15 | return new ShareDBError(ERROR_CODE.ERR_OT_OP_BADLY_FORMED, 'Op must be an object');
|
16 | }
|
17 |
|
18 | if (op.create != null) {
|
19 | if (typeof op.create !== 'object') {
|
20 | return new ShareDBError(ERROR_CODE.ERR_OT_OP_BADLY_FORMED, 'Create data must be an object');
|
21 | }
|
22 | var typeName = op.create.type;
|
23 | if (typeof typeName !== 'string') {
|
24 | return new ShareDBError(ERROR_CODE.ERR_OT_OP_BADLY_FORMED, 'Missing create type');
|
25 | }
|
26 | var type = types[typeName];
|
27 | if (type == null || typeof type !== 'object') {
|
28 | return new ShareDBError(ERROR_CODE.ERR_DOC_TYPE_NOT_RECOGNIZED, 'Unknown type');
|
29 | }
|
30 | } else if (op.del != null) {
|
31 | if (op.del !== true) return new ShareDBError(ERROR_CODE.ERR_OT_OP_BADLY_FORMED, 'del value must be true');
|
32 | } else if (op.op == null) {
|
33 | return new ShareDBError(ERROR_CODE.ERR_OT_OP_BADLY_FORMED, 'Missing op, create, or del');
|
34 | }
|
35 |
|
36 | if (op.src != null && typeof op.src !== 'string') {
|
37 | return new ShareDBError(ERROR_CODE.ERR_OT_OP_BADLY_FORMED, 'src must be a string');
|
38 | }
|
39 | if (op.seq != null && typeof op.seq !== 'number') {
|
40 | return new ShareDBError(ERROR_CODE.ERR_OT_OP_BADLY_FORMED, 'seq must be a string');
|
41 | }
|
42 | if (
|
43 | (op.src == null && op.seq != null) ||
|
44 | (op.src != null && op.seq == null)
|
45 | ) {
|
46 | return new ShareDBError(ERROR_CODE.ERR_OT_OP_BADLY_FORMED, 'Both src and seq must be set together');
|
47 | }
|
48 |
|
49 | if (op.m != null && typeof op.m !== 'object') {
|
50 | return new ShareDBError(ERROR_CODE.ERR_OT_OP_BADLY_FORMED, 'op.m must be an object or null');
|
51 | }
|
52 | };
|
53 |
|
54 |
|
55 | exports.normalizeType = function(typeName) {
|
56 | return types[typeName] && types[typeName].uri;
|
57 | };
|
58 |
|
59 |
|
60 |
|
61 | exports.apply = function(snapshot, op) {
|
62 | if (typeof snapshot !== 'object') {
|
63 | return new ShareDBError(ERROR_CODE.ERR_APPLY_SNAPSHOT_NOT_PROVIDED, 'Missing snapshot');
|
64 | }
|
65 | if (snapshot.v != null && op.v != null && snapshot.v !== op.v) {
|
66 | return new ShareDBError(ERROR_CODE.ERR_APPLY_OP_VERSION_DOES_NOT_MATCH_SNAPSHOT, 'Version mismatch');
|
67 | }
|
68 |
|
69 |
|
70 | if (op.create) {
|
71 | if (snapshot.type) return new ShareDBError(ERROR_CODE.ERR_DOC_ALREADY_CREATED, 'Document already exists');
|
72 |
|
73 |
|
74 | var create = op.create;
|
75 | var type = types[create.type];
|
76 | if (!type) return new ShareDBError(ERROR_CODE.ERR_DOC_TYPE_NOT_RECOGNIZED, 'Unknown type');
|
77 |
|
78 | try {
|
79 | snapshot.data = type.create(create.data);
|
80 | snapshot.type = type.uri;
|
81 | snapshot.v++;
|
82 | } catch (err) {
|
83 | return err;
|
84 | }
|
85 |
|
86 |
|
87 | } else if (op.del) {
|
88 | snapshot.data = undefined;
|
89 | snapshot.type = null;
|
90 | snapshot.v++;
|
91 |
|
92 |
|
93 | } else if (op.op) {
|
94 | var err = applyOpEdit(snapshot, op.op);
|
95 | if (err) return err;
|
96 | snapshot.v++;
|
97 |
|
98 |
|
99 | } else {
|
100 | snapshot.v++;
|
101 | }
|
102 | };
|
103 |
|
104 | function applyOpEdit(snapshot, edit) {
|
105 | if (!snapshot.type) return new ShareDBError(ERROR_CODE.ERR_DOC_DOES_NOT_EXIST, 'Document does not exist');
|
106 |
|
107 | if (edit == null) return new ShareDBError(ERROR_CODE.ERR_OT_OP_NOT_PROVIDED, 'Missing op');
|
108 | var type = types[snapshot.type];
|
109 | if (!type) return new ShareDBError(ERROR_CODE.ERR_DOC_TYPE_NOT_RECOGNIZED, 'Unknown type');
|
110 |
|
111 | try {
|
112 | snapshot.data = type.apply(snapshot.data, edit);
|
113 | } catch (err) {
|
114 | return err;
|
115 | }
|
116 | }
|
117 |
|
118 | exports.transform = function(type, op, appliedOp) {
|
119 |
|
120 |
|
121 | if (op.v != null && op.v !== appliedOp.v) {
|
122 | return new ShareDBError(ERROR_CODE.ERR_OP_VERSION_MISMATCH_DURING_TRANSFORM, 'Version mismatch');
|
123 | }
|
124 |
|
125 | if (appliedOp.del) {
|
126 | if (op.create || op.op) {
|
127 | return new ShareDBError(ERROR_CODE.ERR_DOC_WAS_DELETED, 'Document was deleted');
|
128 | }
|
129 | } else if (
|
130 | (appliedOp.create && (op.op || op.create || op.del)) ||
|
131 | (appliedOp.op && op.create)
|
132 | ) {
|
133 |
|
134 |
|
135 | return new ShareDBError(ERROR_CODE.ERR_DOC_ALREADY_CREATED, 'Document was created remotely');
|
136 | } else if (appliedOp.op && op.op) {
|
137 |
|
138 | if (!type) return new ShareDBError(ERROR_CODE.ERR_DOC_DOES_NOT_EXIST, 'Document does not exist');
|
139 |
|
140 | if (typeof type === 'string') {
|
141 | type = types[type];
|
142 | if (!type) return new ShareDBError(ERROR_CODE.ERR_DOC_TYPE_NOT_RECOGNIZED, 'Unknown type');
|
143 | }
|
144 |
|
145 | try {
|
146 | op.op = type.transform(op.op, appliedOp.op, 'left');
|
147 | } catch (err) {
|
148 | return err;
|
149 | }
|
150 | }
|
151 |
|
152 | if (op.v != null) op.v++;
|
153 | };
|
154 |
|
155 |
|
156 |
|
157 |
|
158 |
|
159 |
|
160 |
|
161 |
|
162 | exports.applyOps = function(snapshot, ops) {
|
163 | var type = null;
|
164 |
|
165 | if (snapshot.type) {
|
166 | type = types[snapshot.type];
|
167 | if (!type) return new ShareDBError(ERROR_CODE.ERR_DOC_TYPE_NOT_RECOGNIZED, 'Unknown type');
|
168 | }
|
169 |
|
170 | for (var index = 0; index < ops.length; index++) {
|
171 | var op = ops[index];
|
172 |
|
173 | snapshot.v = op.v + 1;
|
174 |
|
175 | if (op.create) {
|
176 | type = types[op.create.type];
|
177 | if (!type) return new ShareDBError(ERROR_CODE.ERR_DOC_TYPE_NOT_RECOGNIZED, 'Unknown type');
|
178 | snapshot.data = type.create(op.create.data);
|
179 | snapshot.type = type.uri;
|
180 | } else if (op.del) {
|
181 | snapshot.data = undefined;
|
182 | type = null;
|
183 | snapshot.type = null;
|
184 | } else {
|
185 | snapshot.data = type.apply(snapshot.data, op.op);
|
186 | }
|
187 | }
|
188 | };
|
189 |
|
190 | exports.transformPresence = function(presence, op, isOwnOp) {
|
191 | var opError = this.checkOp(op);
|
192 | if (opError) return opError;
|
193 |
|
194 | var type = presence.t;
|
195 | if (typeof type === 'string') {
|
196 | type = types[type];
|
197 | }
|
198 | if (!type) return {code: ERROR_CODE.ERR_DOC_TYPE_NOT_RECOGNIZED, message: 'Unknown type'};
|
199 | if (!util.supportsPresence(type)) {
|
200 | return {code: ERROR_CODE.ERR_TYPE_DOES_NOT_SUPPORT_PRESENCE, message: 'Type does not support presence'};
|
201 | }
|
202 |
|
203 | if (op.create || op.del) {
|
204 | presence.p = null;
|
205 | presence.v++;
|
206 | return;
|
207 | }
|
208 |
|
209 | try {
|
210 | presence.p = presence.p === null ?
|
211 | null :
|
212 | type.transformPresence(presence.p, op.op, isOwnOp);
|
213 | } catch (error) {
|
214 | return {code: ERROR_CODE.ERR_PRESENCE_TRANSFORM_FAILED, message: error.message || error};
|
215 | }
|
216 |
|
217 | presence.v++;
|
218 | };
|