Merge pull request #35293 from overleaf/mj-zod-history-ot

[real-time] Allow history-ot operations in real-time

GitOrigin-RevId: 4b1b9e4c1a214edf49c5eface46a4d309ba41b0d
This commit is contained in:
Mathias Jakobsen
2026-09-17 11:31:49 +00:00
committed by Copybot
parent 9d57689112
commit d8f167dec2
12 changed files with 330 additions and 64 deletions
@@ -0,0 +1,89 @@
// @ts-check
'use strict'
const { z, zz } = require('@overleaf/validation-tools')
const rawTrackingProps = z.strictObject({
type: z.enum(['insert', 'delete']),
userId: zz.objectId(),
ts: z.iso.datetime(),
})
const rawClearTrackingProps = z.strictObject({
type: z.literal('none'),
})
const rawInsertOp = z.union([
z.strictObject({
i: z.string(),
commentIds: z.array(zz.objectId()).optional(),
tracking: rawTrackingProps.optional(),
}),
z.string(),
])
const rawRemoveOp = z.number().int().max(-1)
const rawRetainOp = z.union([
z.strictObject({
r: z.number().int().min(1),
commentIds: z.array(zz.objectId()).optional(),
tracking: rawTrackingProps.or(rawClearTrackingProps).optional(),
}),
z.number().int().min(1),
])
const rawScanOp = z.union([rawInsertOp, rawRemoveOp, rawRetainOp])
const rawTextOperation = z.strictObject({
textOperation: z.array(rawScanOp).min(1),
contentHash: z.string().optional(),
})
const rawRange = z.strictObject({
pos: z.number().int().min(0),
length: z.number().int().min(0),
})
const rawAddCommentOperation = z.strictObject({
commentId: zz.objectId(),
ranges: z.array(rawRange).min(1),
resolved: z.boolean().optional(),
})
const rawSetCommentStateOperation = z.strictObject({
commentId: zz.objectId(),
resolved: z.boolean(),
})
const rawDeleteCommentOperation = z.strictObject({
deleteComment: zz.objectId(),
})
const rawEditNoOperation = z.strictObject({
noOp: z.literal(true),
})
const rawEditOperation = z.union([
rawTextOperation,
rawAddCommentOperation,
rawDeleteCommentOperation,
rawSetCommentStateOperation,
rawEditNoOperation,
])
module.exports = {
rawTrackingProps,
rawClearTrackingProps,
rawInsertOp,
rawRemoveOp,
rawRetainOp,
rawScanOp,
rawTextOperation,
rawRange,
rawAddCommentOperation,
rawSetCommentStateOperation,
rawDeleteCommentOperation,
rawEditNoOperation,
rawEditOperation,
}
+26 -54
View File
@@ -1,6 +1,8 @@
import Blob from './blob'
import TrackingProps from './file_data/tracking_props'
import ClearTrackingProps from './file_data/clear_tracking_props'
import type Blob from './blob'
import type TrackingProps from './file_data/tracking_props'
import type ClearTrackingProps from './file_data/clear_tracking_props'
import type { z } from '@overleaf/validation-tools'
import type * as schemas from './schemas'
export type BlobStore = {
getBlob(hash: string): Promise<Blob | null>
@@ -17,10 +19,7 @@ export type RangesBlob = {
trackedChanges: TrackedChangeRawData[]
}
export type RawRange = {
pos: number
length: number
}
export type RawRange = z.infer<typeof schemas.rawRange>
export type CommentRawData = {
id: string
@@ -33,15 +32,11 @@ export type TrackedChangeRawData = {
tracking: TrackingPropsRawData
}
export type TrackingPropsRawData = {
type: 'insert' | 'delete'
userId: string
ts: string
}
export type TrackingPropsRawData = z.infer<typeof schemas.rawTrackingProps>
export type ClearTrackingPropsRawData = {
type: 'none'
}
export type ClearTrackingPropsRawData = z.infer<
typeof schemas.rawClearTrackingProps
>
export type TrackingDirective = TrackingProps | ClearTrackingProps
@@ -112,55 +107,32 @@ export type RawHollowStringFileData = { stringLength: number }
export type RawV2DocVersions = Record<string, { pathname: string; v: number }>
export type RawInsertOp =
| {
i: string
commentIds?: string[]
tracking?: TrackingPropsRawData
}
| string
export type RawInsertOp = z.infer<typeof schemas.rawInsertOp>
export type RawRemoveOp = number
export type RawRetainOp =
| {
r: number
commentIds?: string[]
tracking?: TrackingPropsRawData | ClearTrackingPropsRawData
}
| number
export type RawRemoveOp = z.infer<typeof schemas.rawRemoveOp>
export type RawRetainOp = z.infer<typeof schemas.rawRetainOp>
export type RawScanOp = RawInsertOp | RawRemoveOp | RawRetainOp
export type RawScanOp = z.infer<typeof schemas.rawScanOp>
export type RawTextOperation = {
textOperation: RawScanOp[]
contentHash?: string
}
export type RawTextOperation = z.infer<typeof schemas.rawTextOperation>
export type RawAddCommentOperation = {
commentId: string
ranges: RawRange[]
resolved?: boolean
}
export type RawAddCommentOperation = z.infer<
typeof schemas.rawAddCommentOperation
>
export type RawDeleteCommentOperation = { deleteComment: string }
export type RawDeleteCommentOperation = z.infer<
typeof schemas.rawDeleteCommentOperation
>
export type RawSetCommentStateOperation = {
commentId: string
resolved: boolean
}
export type RawSetCommentStateOperation = z.infer<
typeof schemas.rawSetCommentStateOperation
>
export type RawEditNoOperation = {
noOp: true
}
export type RawEditNoOperation = z.infer<typeof schemas.rawEditNoOperation>
export type RawEditFileOperation = RawEditOperation & { pathname: string }
export type RawEditOperation =
| RawTextOperation
| RawAddCommentOperation
| RawDeleteCommentOperation
| RawSetCommentStateOperation
| RawEditNoOperation
export type RawEditOperation = z.infer<typeof schemas.rawEditOperation>
export type LinkedFileData = {
importedAt: string
@@ -26,6 +26,7 @@
},
"dependencies": {
"@overleaf/o-error": "workspace:*",
"@overleaf/validation-tools": "workspace:*",
"check-types": "^5.1.0",
"lodash": "^4.18.1",
"p-map": "^4.0.0",