-
Notifications
You must be signed in to change notification settings - Fork 446
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
3 changed files
with
139 additions
and
127 deletions.
There are no files selected for viewing
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,135 @@ | ||
// This contains the master OT functions for the database. They look like | ||
// ot-types style operational transform functions, but they're a bit different. | ||
// These functions understand versions and can deal with out of bound create & | ||
// delete operations. | ||
|
||
var otTypes = require('ottypes'); | ||
|
||
// Default validation function | ||
var defaultValidate = function() {}; | ||
|
||
// Returns an error string on failure. Rockin' it C style. | ||
exports.checkOpData = function(opData) { | ||
if (typeof opData !== 'object') return 'Missing opData'; | ||
if (typeof (opData.op || opData.create) !== 'object' && opData.del !== true) return 'Missing op1'; | ||
if (opData.create && typeof opData.create.type !== 'string') return 'Missing create type'; | ||
|
||
if ((opData.src != null) && typeof opData.src !== 'string') return 'Invalid src'; | ||
if ((opData.seq != null) && typeof opData.seq !== 'number') return 'Invalid seq'; | ||
if (!!opData.seq !== !!opData.src) return 'seq but not src'; | ||
}; | ||
|
||
exports.normalize = function(opData) { | ||
// I'd love to also normalize opData.op if it exists, but I don't know the | ||
// type of the operation. And I can't find that out until after transforming | ||
// the operation anyway. | ||
if (opData.create) { | ||
// Store the full URI of the type, not just its short name | ||
return opData.create.type = otTypes[opData.create.type].uri; | ||
} | ||
}; | ||
|
||
// This is the super apply function that takes in snapshot data (including the | ||
// type) and edits it in-place. Returns an error string or null for success. | ||
exports.apply = function(data, opData) { | ||
var err; | ||
|
||
if (typeof opData !== 'object') | ||
return 'Missing data'; | ||
if (!(typeof (opData.op || opData.create) === 'object' || opData.del === true)) | ||
return 'Missing op'; | ||
|
||
if ((data.v != null) && (opData.v != null) && data.v !== opData.v) | ||
return 'Version mismatch'; | ||
|
||
var validate = opData.validate || defaultValidate; | ||
var preValidate = opData.preValidate || defaultValidate; | ||
|
||
if (opData.create) { // Create operations | ||
if (data.type) return 'Document already exists'; | ||
|
||
// The document doesn't exist, although it might have once existed. | ||
var create = opData.create; | ||
var type = otTypes[create.type]; | ||
if (!type) return "Type not found"; | ||
|
||
if ((err = preValidate(opData, data))) return err; | ||
|
||
var snapshot = type.create(create.data); | ||
data.data = snapshot; | ||
data.type = type.uri; | ||
data.v++; | ||
|
||
if ((err = validate(opData, data))) return err; | ||
|
||
} else if (opData.del) { // Delete operations | ||
if ((err = preValidate(opData, data))) return err; | ||
|
||
opData.prev = {data:data.data, type:data.type}; | ||
delete data.data; | ||
delete data.type; | ||
data.v++; | ||
if ((err = validate(opData, data))) return err; | ||
|
||
} else { // Edit operations | ||
if (!data.type) return 'Document does not exist'; | ||
|
||
var op = opData.op; | ||
if (typeof op !== 'object') return 'Missing op'; | ||
var type = otTypes[data.type]; | ||
if (!type) return 'Type not found'; | ||
|
||
try { | ||
// This shattering stuff is a little bit dodgy. Its important because it | ||
// lets the OT type apply the operation incrementally, which means the | ||
// operation can be validated piecemeal. (Even though the entire | ||
// operation is accepted or rejected wholesale). Racer uses this, but I'm | ||
// still not entirely sure its the right API. | ||
var atomicOps = type.shatter ? type.shatter(op) : [op]; | ||
for (var i = 0; i < atomicOps.length; i++) { | ||
var atom = atomicOps[i]; | ||
opData.op = atom; | ||
if ((err = preValidate(opData, data))) return err; | ||
|
||
// !! The money line. | ||
data.data = type.apply(data.data, atom); | ||
|
||
if ((err = validate(opData, data))) return err; | ||
} | ||
// Make sure to restore the operation before returning. | ||
opData.op = op; | ||
|
||
} catch (err) { | ||
console.log(err.stack); | ||
return err.message; | ||
} | ||
data.v++; | ||
} | ||
}; | ||
|
||
exports.transform = function(type, opData, appliedOpData) { | ||
if (appliedOpData.del) { | ||
if (!opData.del) return 'Document was deleted'; | ||
|
||
if (opData.v != null) { | ||
opData.v++; | ||
} | ||
return; | ||
} | ||
|
||
if (appliedOpData.create) return 'Document created remotely'; | ||
|
||
if (!type) return 'Document does not exist'; | ||
|
||
if ((opData.v != null) && opData.v !== appliedOpData.v) | ||
return 'Version mismatch'; | ||
|
||
if (typeof type === 'string') { | ||
type = otTypes[type]; | ||
if (!type) return "Type not found"; | ||
} | ||
|
||
opData.op = type.transform(opData.op, appliedOpData.op, 'left'); | ||
if (opData.v != null) opData.v++; | ||
}; | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters