>
> - I'm not sure if I want to make this OT type invertable. If we don't,

finding the data that was actually deleted will be difficult
>

What are the costs of making it invertable? i.e. are there particular
reasons not to?
I guess for proper invertability, you'd need to copy all the data being
moved?



> - Any concurrent edits on the data that conflicted might would be
> lost, because we'd replace a move with a delete-reinsert
>

Could the move get transformed into a no-op (or, a move to another place)?
As that would permit concurrent edits.



> But we could implement this much more cleanly if move operations
> specify a fallback location for conflicting data to get moved into. If
> that fallback location was a list, we can guarantee we won't have any
> cascaded conflicts. To resolve the conflict, the user can just move or
> delete the conflicted object like normal.
>

Out of interest, is this something that conflicting 'set' operations would
want too?
e.g. (Set 'c' {v: 1}) & (Set 'c' {v: 2}) seems to have similar loss
semantics,
should _recovery then be [{set: 'c', data: {v: 1}}]


> Some observations:
> >> - This will almost never actually happen in real life.
> >> - Even if we turn the operation will transform into a 'replace', we
> >> could add a flag to the operation to mark that it was unintentionally
> >> overwritten, and then the process which is making the corresponding
> >> changes on disk could see that and do some special
> >> application-specific behaviour instead of actually deleting the file.
> >
> >
> > Indeed - your structure could instead be:
> > { "name": "librope",
> >   "package": {
> >     "src": {
> >       "rope.c": "somelonguniqueid",
> >       "rope.h": "anotherfileid",
> >   }},
> >   data: {
> >     "somelonguniqueid": { "text": "#include <rope.h>\n ..." },
> >     "anotherfileid": { "text": "// This is a cool header"  },
> >   }}
> >
> > Then deletion is some separate offline index/cleanup, and resolving
> > conflicts is simpler.
> > (although 'move' is less useful here as there are no child properties).
>
> Yeah - thats definitely usable. It kind of moves the mess around your
> plate a little. Its very similar to the idea of just not allowing
> move-to-object operations at all, which would force every
> dictionary-like structure to be managed using lists with manual
> (custom!) dedup in the case of conflicts. Either way, application
> writers would need to do some manual garbage collection.


Yep, I was assuming everyone wants manual garbage collection anyway :)
I realized though that moving folders messes this up a bit;
either the folders are also files (possible, but then every N-deep folder
traversal does N id lookups)
or they are objects (e.g. "src": {"js": { "file1": id1, "file2": id2,
...}}) in which case you're back to the original problem.


> Does move distinguish between PUT vs. PATCH?
> > I would assume PUT (x: {a: 1})  -> z and PUT (y: {b: 2}) -> z would end
> up
> > in z: {b:2}
> > but if the second operation received was a PATCH operation, they would be
> > merged rather than replaced.
> > For file system moves they'd probably be PUT, but their might be
> instances
> > where a PATCH is useful.
>
> I was just thinking of move as a PUT operation. When is PATCH useful?
> In what use cases do you actually want two object structures to get
> merged?
>

Only thing I could think of was batch file moves - i.e. moving a set of
string-id'd files from one directory to another,
you'd want to PATCH move {'f1.js': {...}, 'f2.js': {...}} from src -> dest
but then it's a bit weird, as you'd want PATCH on the top level and PUT
underneath, and it'd probably be better off as a list of PUT moves.
Otherwise, I couldn't think of one, unless a client wanted to get really
abstract and e.g. batch a 'local edits' object which then get 'applied'
by a PATCH move onto the persisted object, but that also sounds like
something that could be done better in other ways.

Reply via email to