From ea9d8d286dc8c53c84f26e1e22cb715f87cc2ae2 Mon Sep 17 00:00:00 2001 From: Greg Kubisa Date: Thu, 21 Jun 2018 18:42:09 +0100 Subject: [PATCH] Specify more functions Some were used by ShareDB and missing from the spec, others are required for the new undo/redo feature. --- README.md | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 4229e25..e0681c7 100644 --- a/README.md +++ b/README.md @@ -54,17 +54,23 @@ example, `require('ot-text').type.name` contains the text type's name. - **uri**: *(Optional, will be required soon)* A canonical location for this type. The spec for the OT type should be at this address. Remember kids, Tim Berners-Lee says [cool URLs don't change](http://www.w3.org/Provider/Style/URI.html). - **create([initialData]) -> snapshot**: A function to create the initial document snapshot. Create may accept initial snapshot data as its only argument. Either the return value must be a valid target for `JSON.stringify` or you must specify *serialize* and *deserialize* functions (described below). - **apply(snapshot, op) -> snapshot'**: Apply an operation to a document snapshot. Returns the changed snapshot. For performance, old document must not be used after this function call, so apply may reuse and return the current snapshot object. -- **transform(op1, op2, side) -> op1'**: Transform op1 by op2. Return the new op1. Side is either `'left'` or `'right'`. It exists to break ties, for example if two operations insert at the same position in a string. Both op1 and op2 must not be modified by transform. +- **transform(op1, op2, side) -> op1'**: Transform op1 by op2. Return the new op1. Side is either `'left'` or `'right'`. It exists to break ties, for example if two operations insert at the same position in a string. Both op1 and op2 must not be modified by `transform`. Transform must conform to Transform Property 1. That is, apply(apply(snapshot, op1), transform(op2, op1, 'left')) == apply(apply(snapshot, op2), transform(op1, op2, 'right')). -- **compose(op1, op2) -> op**: *(optional)* Compose op1 and op2 to produce a new operation. The new operation must subsume the behaviour of op1 and op2. Specifically, apply(apply(snapshot, op1), op2) == apply(snapshot, compose(op1, op2)). Note: transforming by a composed operation is *NOT* guaranteed to produce the same result as transforming by each operation in order. This function is optional, but unless you have a good reason to do otherwise, you should provide a compose function for your type. ### Optional properties -- **invert(op) -> op'**: *(optional)* Invert the given operation. The original operation must not be edited in the process. If supplied, apply(apply(snapshot, op), invert(op)) == snapshot. +- **transformX(op1, op2) -> [ op1', op2' ]**: *(optional)* Transform op1 by op2, and op2 by op1. Both op1 and op2 must not be modified by `transformX`. This function can be defined to improve performance, if the 2 transformations can be performed more efficiently together, than separately using the standard `transform`. transformX(op1, op2) == [ transform(op1, op2, 'left'), transform(op2, op1, 'right') ]. +- **compose(op1, op2) -> op**: *(optional)* Compose op1 and op2 to produce a new operation. The new operation must subsume the behaviour of op1 and op2. Specifically, apply(apply(snapshot, op1), op2) == apply(snapshot, compose(op1, op2)). Note: transforming by a composed operation is *NOT* guaranteed to produce the same result as transforming by each operation in order. This function is optional, but unless you have a good reason to do otherwise, you should provide a compose function for your type. +- **composeSimilar(op1, op2) -> op OR null**: *(optional)* If op1 and op2 are "similar", then it's the same as `compose`. If op1 and op2 are not "similar", returns null. This function can be used to conditionally compose operations based on some criteria, eg edits at the same position in a text document. This way the "similar" operations could for example by undone as a single unit, instead of one by one. +- **invert(op) -> op'**: *(optional)* Invert the given operation. The original operation must not be edited in the process. apply(apply(snapshot, op), invert(op)) == snapshot. +- **applyAndInvert(snapshot, op) -> [ snapshot', op' ]**: *(optional)* Apply op to snapshot and return a new or modified snapshot and an inverted op. This function may be defined to make a type invertible even of `op` itself does not contain enough metadata to implement `invert` as a separate function. applyAndInvert(snapshot, op) == [ apply(snapshot, op), invert(op) ]. +- **diff(snapshot1, snapshot2[, hint]) -> op**: *(optional)* Return an op which can be applied to snapshot1 to produce snapshot2. `hint` is optional and may be used to assit the diff algorithm, for example it could be a suggested editing position for a text type. apply(snapshot1, diff(snapshot1, snapshot2, hint)) == snapshot2. +- **diffX(snapshot1, snapshot2[, hint]) -> [ op1, op2 ]**: *(optional)* Returns ops which can be applied to one snapshot to produce the other. This function may be implemented to improve performance, if 2 diffs can be computed more efficiently together, than by 2 separate calls to `diff`. diffX(snapshot1, snapshot2, hint) == [ diffX(snapshot2, snapshot1, hint), diffX(snapshot1, snapshot2, hint) ]. - **normalize(op) -> op'**: *(optional)* Normalize an operation, converting it to a canonical representation. normalize(normalize(op)) == normalize(op). - **transformCursor(cursor, op, isOwnOp) -> cursor'**: *(optional)* transform the specified cursor by the provided operation, so that the cursor moves forward or backward as content is added or removed before (or at) the cursor position. isOwnOp defines how the cursor should be transformed against content inserted *at* the cursor position. If isOwnOp is true, the cursor is moved after the content inserted at the original cursor position. If isOwnOp is false, the cursor remains before the content inserted at the original cursor position. - **serialize(snapshot) -> data**: *(optional)* convert the document snapshot data into a form that may be passed to JSON.stringify. If you have a *serialize* function, you must have a *deserialize* function. - **deserialize(data) -> snapshot**: *(optional)* convert data generated by *serialize* back into its internal snapshot format. deserialize(serialize(snapshot)) == snapshot. If you have a *deserialize* function, you must have a *serialize* function. +- **isNoop(op) -> boolean**: *(optional)* return true, if `op` is a no-op. Specifically, if isNoop(op) == true, then apply(snapshot, op) == snapshot. > Do I need serialize and deserialize? Maybe JSON.stringify is sufficiently customizable..?