ocean-brain 0.6.3 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/mcp.js +79 -24
- package/package.json +2 -1
- package/server/client/dist/_redirects +1 -0
- package/server/client/dist/assets/{Calendar-DbPut8EU.js → Calendar-BuHuea3u.js} +1 -1
- package/server/client/dist/assets/{Callout-DfX-gIUB.js → Callout-B-Y15DKd.js} +1 -1
- package/server/client/dist/assets/{Graph-yHE9m5fy.js → Graph-BddGuIWA.js} +1 -1
- package/server/client/dist/assets/{Image.es-CtMc7BW4.js → Image.es-DWOPKZ3F.js} +1 -1
- package/server/client/dist/assets/Note-Dl6-yoQO.js +22 -0
- package/server/client/dist/assets/{Plus.es-BGVIIQtM.js → Plus.es-2XIq0eEW.js} +1 -1
- package/server/client/dist/assets/{Reminders-CvEJf0eQ.js → Reminders-06CF__C2.js} +1 -1
- package/server/client/dist/assets/{Search-CKZhztuV.js → Search-DtLyJR_o.js} +1 -1
- package/server/client/dist/assets/{SurfaceCard-Bnfo5EOl.js → SurfaceCard-C7ZAzggM.js} +1 -1
- package/server/client/dist/assets/{Tag-C4oAwjs0.js → Tag-KGPO2bT9.js} +1 -1
- package/server/client/dist/assets/{TagNotes-Rl1ZSJP6.js → TagNotes-CkmcnqM6.js} +1 -1
- package/server/client/dist/assets/{Trash.es-BcwUz2yM.js → Trash.es-CerlaxH9.js} +1 -1
- package/server/client/dist/assets/{ViewNotes-B9Yc0A87.js → ViewNotes-Khdg4Ynn.js} +1 -1
- package/server/client/dist/assets/{ViewSectionTableRenderer-C5yJBO7C.js → ViewSectionTableRenderer-BMlCUDvj.js} +1 -1
- package/server/client/dist/assets/{Views-Bqq0pUEE.js → Views-BLUk_24_.js} +1 -1
- package/server/client/dist/assets/image.api-C6RG15TF.js +17 -0
- package/server/client/dist/assets/{index-CDjYEIVf.css → index-Biw4vuiN.css} +1 -1
- package/server/client/dist/assets/{index-BQDR6hIA.js → index-CbhzIgIR.js} +1 -1
- package/server/client/dist/assets/{index-B43ulIPo.js → index-_FcT3AuV.js} +77 -43
- package/server/client/dist/assets/{manage-image-detail-WARala-v.js → manage-image-detail-CImm-cQJ.js} +1 -1
- package/server/client/dist/assets/{manage-image-Bs4Kmevx.js → manage-image-mHebal-S.js} +1 -1
- package/server/client/dist/assets/{mcp-C0Z3vKBa.js → mcp-CHthNSpj.js} +1 -1
- package/server/client/dist/assets/{placeholder-D080guzn.js → placeholder-BMXsrVZ1.js} +1 -1
- package/server/client/dist/assets/{properties-DbnmfbT5.js → properties-DRB3IoO1.js} +1 -1
- package/server/client/dist/assets/{trash-D5ZiZfTk.js → trash-Dw2FQsut.js} +1 -1
- package/server/client/dist/assets/{useReminderMutate-h7d_S_X2.js → useReminderMutate-C3GYwXa0.js} +1 -1
- package/server/client/dist/index.html +2 -2
- package/server/dist/features/note/graphql/note.query.resolver.js +88 -47
- package/server/dist/features/note/graphql/note.query.resolver.js.map +1 -1
- package/server/dist/features/note/graphql/note.type-defs.js +32 -0
- package/server/dist/features/note/graphql/note.type-defs.js.map +1 -1
- package/server/dist/features/note/http/mcp.js +32 -0
- package/server/dist/features/note/http/mcp.js.map +1 -1
- package/server/dist/features/note/services/markdown-intent-write.js +3 -3
- package/server/dist/features/note/services/markdown-intent-write.js.map +1 -1
- package/server/dist/features/note/services/markdown-patch.js +34 -4
- package/server/dist/features/note/services/markdown-patch.js.map +1 -1
- package/server/dist/features/note/services/snapshot.js +159 -0
- package/server/dist/features/note/services/snapshot.js.map +1 -1
- package/server/dist/modules/blocknote.js +171 -17
- package/server/dist/modules/blocknote.js.map +1 -1
- package/server/dist/routes/mcp.js +5 -0
- package/server/dist/routes/mcp.js.map +1 -1
- package/server/client/dist/assets/Note-BECn-AoI.js +0 -21
- package/server/client/dist/assets/Note-W4f6H9K0.css +0 -1
- package/server/client/dist/assets/image.api-CuGTgzyQ.js +0 -17
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/features/note/services/markdown-patch.ts"],"sourcesContent":["import crypto from 'crypto';\n\nconst HASH_ALGORITHM = 'sha256';\nconst EXCERPT_CONTEXT_CHARS = 80;\nconst DIFF_CONTEXT_LINES = 3;\n\nexport interface MarkdownPatchNoteSnapshot {\n id: string;\n title: string;\n updatedAt: string;\n markdown: string;\n}\n\nexport interface ExactTextMarkdownPatchSelector {\n type: 'exact_text';\n text: string;\n before?: string;\n after?: string;\n}\n\nexport interface MatchCandidateMarkdownPatchSelector {\n type: 'match_candidate';\n text: string;\n matchIndex: number;\n lineStart: number;\n matchSha256: string;\n surroundingHash: string;\n positionHint?: 'first' | 'last';\n}\n\nexport type MarkdownPatchSelector = ExactTextMarkdownPatchSelector | MatchCandidateMarkdownPatchSelector;\n\nexport interface ReplaceMarkdownPatchOperation {\n type: 'replace';\n replacement: string;\n}\n\nexport interface InsertBeforeMarkdownPatchOperation {\n type: 'insert_before';\n insertion: string;\n}\n\nexport interface InsertAfterMarkdownPatchOperation {\n type: 'insert_after';\n insertion: string;\n}\n\nexport type MarkdownPatchOperation =\n | ReplaceMarkdownPatchOperation\n | InsertBeforeMarkdownPatchOperation\n | InsertAfterMarkdownPatchOperation;\n\nexport interface MarkdownChangePolicy {\n allowNoop?: boolean;\n maxChangedChars?: number;\n maxChangedLines?: number;\n preserveReferences?: boolean | 'warn';\n preserveTags?: boolean | 'warn';\n}\n\nexport interface PreviewMarkdownPatchInput {\n note: MarkdownPatchNoteSnapshot;\n expectedUpdatedAt?: string;\n baseMarkdownSha256?: string;\n intent: string;\n selector: MarkdownPatchSelector;\n operation: MarkdownPatchOperation;\n policy?: MarkdownChangePolicy;\n}\n\nexport interface MarkdownAppendPlacementEnd {\n type: 'end';\n}\n\nexport interface MarkdownAppendPlacementAfterHeading {\n type: 'after_heading';\n heading: string;\n level?: number;\n}\n\nexport type MarkdownAppendPlacement = MarkdownAppendPlacementEnd | MarkdownAppendPlacementAfterHeading;\n\nexport interface PreviewMarkdownAppendInput {\n note: MarkdownPatchNoteSnapshot;\n expectedUpdatedAt?: string;\n baseMarkdownSha256?: string;\n intent: string;\n insertion: string;\n placement?: MarkdownAppendPlacement;\n separator?: '\\n\\n' | '\\n';\n policy?: MarkdownChangePolicy;\n}\n\nexport interface PreviewMarkdownReplaceInput {\n note: MarkdownPatchNoteSnapshot;\n expectedUpdatedAt?: string;\n baseMarkdownSha256?: string;\n intent: string;\n replacement: string;\n policy?: MarkdownChangePolicy;\n}\n\nexport interface MarkdownPatchMatch {\n matchIndex: number;\n lineStart: number;\n lineEnd: number;\n text: string;\n beforeExcerpt?: string;\n afterExcerpt?: string;\n matchSha256: string;\n surroundingHash: string;\n positionHint?: 'first' | 'last';\n}\n\nexport interface MarkdownChangeDryRun {\n status: 'dry_run';\n note: {\n id: string;\n title: string;\n updatedAt: string;\n };\n match?: {\n count: 1;\n lineStart: number;\n lineEnd: number;\n selectorType: string;\n matchedTextSha256: string;\n surroundingHash: string;\n };\n placement?: MarkdownAppendPlacement;\n proposed: {\n changedLineCount: number;\n changedCharCount: number;\n beforeMarkdownSha256: string;\n afterMarkdownSha256: string;\n diff: string;\n };\n warnings: string[];\n}\n\nexport interface MarkdownChangePlan extends MarkdownChangeDryRun {\n afterMarkdown: string;\n}\n\nexport interface MarkdownPatchNeedsDisambiguation {\n status: 'needs_disambiguation';\n reason: 'TARGET_AMBIGUOUS';\n matches: MarkdownPatchMatch[];\n}\n\nexport interface MarkdownChangeFailure {\n status: 'failed';\n reason:\n | 'ANCHOR_MISMATCH'\n | 'BASELINE_MISMATCH'\n | 'CANDIDATE_MISMATCH'\n | 'CHANGE_LIMIT_EXCEEDED'\n | 'EMPTY_INSERTION'\n | 'EMPTY_REPLACEMENT'\n | 'EMPTY_TARGET'\n | 'HEADING_AMBIGUOUS'\n | 'HEADING_NOT_FOUND'\n | 'INVALID_HEADING_LEVEL'\n | 'INVALID_PROPERTY_INPUT'\n | 'MARKDOWN_IMPORT_LOSSY'\n | 'MISSING_BASELINE'\n | 'NOOP'\n | 'REFERENCE_STRUCTURE_DECREASED'\n | 'REFERENCE_TOKEN_DECREASED'\n | 'TAG_TOKEN_DECREASED'\n | 'TARGET_NOT_FOUND'\n | 'UNSUPPORTED_MARKDOWN_STRUCTURE';\n message: string;\n}\n\nexport type MarkdownPatchPreviewResult =\n | MarkdownChangeDryRun\n | MarkdownPatchNeedsDisambiguation\n | MarkdownChangeFailure;\n\nexport type MarkdownPatchPlanResult = MarkdownChangePlan | MarkdownPatchNeedsDisambiguation | MarkdownChangeFailure;\nexport type MarkdownAppendPlanResult = MarkdownChangePlan | MarkdownChangeFailure;\nexport type MarkdownReplacePlanResult = MarkdownChangePlan | MarkdownChangeFailure;\n\ninterface RawTextMatch {\n matchIndex: number;\n start: number;\n end: number;\n text: string;\n}\n\ninterface HeadingMatch {\n heading: string;\n level: number;\n lineIndex: number;\n start: number;\n nextSectionStart: number;\n}\n\nexport const calculateMarkdownSha256 = (value: string) => {\n return crypto.createHash(HASH_ALGORITHM).update(value, 'utf8').digest('hex');\n};\n\nconst countNewlines = (value: string) => {\n let count = 0;\n\n for (const character of value) {\n if (character === '\\n') {\n count += 1;\n }\n }\n\n return count;\n};\n\nconst getLineRange = (markdown: string, match: RawTextMatch) => {\n const lineStart = countNewlines(markdown.slice(0, match.start)) + 1;\n const lineEnd = lineStart + countNewlines(match.text);\n\n return {\n lineStart,\n lineEnd,\n };\n};\n\nconst buildExcerpt = (markdown: string, start: number, end: number) => {\n const excerpt = markdown.slice(start, end).trim();\n\n return excerpt.length > 0 ? excerpt : undefined;\n};\n\nconst toPatchMatch = (markdown: string, match: RawTextMatch, totalMatches: number): MarkdownPatchMatch => {\n const { lineStart, lineEnd } = getLineRange(markdown, match);\n const beforeStart = Math.max(0, match.start - EXCERPT_CONTEXT_CHARS);\n const afterEnd = Math.min(markdown.length, match.end + EXCERPT_CONTEXT_CHARS);\n const beforeText = markdown.slice(beforeStart, match.start);\n const afterText = markdown.slice(match.end, afterEnd);\n\n return {\n matchIndex: match.matchIndex,\n lineStart,\n lineEnd,\n text: match.text,\n beforeExcerpt: buildExcerpt(markdown, beforeStart, match.start),\n afterExcerpt: buildExcerpt(markdown, match.end, afterEnd),\n matchSha256: calculateMarkdownSha256(match.text),\n surroundingHash: calculateMarkdownSha256(JSON.stringify({ beforeText, targetText: match.text, afterText })),\n ...(match.matchIndex === 0 && totalMatches > 1 ? { positionHint: 'first' } : {}),\n ...(match.matchIndex === totalMatches - 1 && totalMatches > 1 ? { positionHint: 'last' } : {}),\n };\n};\n\nconst findExactTextMatches = (markdown: string, text: string) => {\n const matches: RawTextMatch[] = [];\n let searchStart = 0;\n\n while (searchStart <= markdown.length) {\n const start = markdown.indexOf(text, searchStart);\n\n if (start === -1) {\n break;\n }\n\n matches.push({\n matchIndex: matches.length,\n start,\n end: start + text.length,\n text,\n });\n searchStart = start + text.length;\n }\n\n return matches;\n};\n\nconst splitLines = (value: string) => {\n return value.split('\\n');\n};\n\nconst findSharedLineEdges = (beforeLines: string[], afterLines: string[]) => {\n let prefix = 0;\n const maxPrefix = Math.min(beforeLines.length, afterLines.length);\n\n while (prefix < maxPrefix && beforeLines[prefix] === afterLines[prefix]) {\n prefix += 1;\n }\n\n let suffix = 0;\n const maxSuffix = Math.min(beforeLines.length - prefix, afterLines.length - prefix);\n\n while (\n suffix < maxSuffix &&\n beforeLines[beforeLines.length - 1 - suffix] === afterLines[afterLines.length - 1 - suffix]\n ) {\n suffix += 1;\n }\n\n return {\n prefix,\n suffix,\n };\n};\n\nconst formatDiffRange = (lineIndex: number, lineCount: number) => {\n const lineNumber = lineIndex + 1;\n\n return lineCount === 1 ? String(lineNumber) : `${lineNumber},${lineCount}`;\n};\n\nexport const createUnifiedMarkdownDiff = (\n beforeMarkdown: string,\n afterMarkdown: string,\n contextLines: number | null = DIFF_CONTEXT_LINES,\n) => {\n const beforeLines = splitLines(beforeMarkdown);\n const afterLines = splitLines(afterMarkdown);\n const { prefix, suffix } = findSharedLineEdges(beforeLines, afterLines);\n const changedBeforeStart = prefix;\n const changedBeforeEnd = beforeLines.length - suffix;\n const changedAfterStart = prefix;\n const changedAfterEnd = afterLines.length - suffix;\n const displayBeforeStart = contextLines === null ? 0 : Math.max(0, changedBeforeStart - contextLines);\n const displayBeforeEnd =\n contextLines === null ? beforeLines.length : Math.min(beforeLines.length, changedBeforeEnd + contextLines);\n const displayAfterStart = contextLines === null ? 0 : Math.max(0, changedAfterStart - contextLines);\n const displayAfterEnd =\n contextLines === null ? afterLines.length : Math.min(afterLines.length, changedAfterEnd + contextLines);\n const diffLines = [\n '--- before.md',\n '+++ after.md',\n `@@ -${formatDiffRange(displayBeforeStart, displayBeforeEnd - displayBeforeStart)} +${formatDiffRange(displayAfterStart, displayAfterEnd - displayAfterStart)} @@`,\n ];\n\n for (let index = displayBeforeStart; index < changedBeforeStart; index += 1) {\n diffLines.push(` ${beforeLines[index]}`);\n }\n\n for (let index = changedBeforeStart; index < changedBeforeEnd; index += 1) {\n diffLines.push(`-${beforeLines[index]}`);\n }\n\n for (let index = changedAfterStart; index < changedAfterEnd; index += 1) {\n diffLines.push(`+${afterLines[index]}`);\n }\n\n for (let index = changedBeforeEnd; index < displayBeforeEnd; index += 1) {\n diffLines.push(` ${beforeLines[index]}`);\n }\n\n return diffLines.join('\\n');\n};\n\nconst countChangedLines = (beforeMarkdown: string, afterMarkdown: string) => {\n const beforeLines = splitLines(beforeMarkdown);\n const afterLines = splitLines(afterMarkdown);\n const { prefix, suffix } = findSharedLineEdges(beforeLines, afterLines);\n const removedLineCount = beforeLines.length - prefix - suffix;\n const addedLineCount = afterLines.length - prefix - suffix;\n\n return Math.max(removedLineCount, addedLineCount);\n};\n\nconst isMarkdownTokenWhitespace = (character: string) => /\\s/.test(character);\n\nexport const countExplicitTagTokens = (value: string) => {\n let count = 0;\n\n for (let index = 0; index < value.length; index += 1) {\n if (value[index] !== '[' || (value[index + 1] !== '@' && value[index + 1] !== '#')) {\n continue;\n }\n\n let cursor = index + 2;\n let isValid = cursor < value.length;\n\n while (cursor < value.length && value[cursor] !== ']') {\n if (value[cursor] === '[' || isMarkdownTokenWhitespace(value[cursor])) {\n isValid = false;\n break;\n }\n\n cursor += 1;\n }\n\n if (isValid && cursor < value.length && cursor > index + 2) {\n count += 1;\n index = cursor;\n continue;\n }\n\n if (!isValid) {\n index = Math.max(index, cursor - 1);\n }\n }\n\n return count;\n};\n\nexport const countMarkdownReferenceTokens = (value: string) => {\n let count = 0;\n let cursor = 0;\n\n while (cursor < value.length) {\n const start = value.indexOf('[[', cursor);\n\n if (start === -1) {\n break;\n }\n\n const end = value.indexOf(']]', start + 2);\n\n if (end === -1) {\n break;\n }\n\n if (end > start + 2) {\n count += 1;\n }\n\n cursor = end + 2;\n }\n\n return count;\n};\n\nconst parseVersionTimestamp = (value: string) => {\n const timestamp = /^\\d+$/.test(value) ? Number(value) : Date.parse(value);\n\n return Number.isFinite(timestamp) ? timestamp : null;\n};\n\nconst noteVersionsMatch = (expectedUpdatedAt: string, currentUpdatedAt: string) => {\n const expectedTimestamp = parseVersionTimestamp(expectedUpdatedAt);\n const currentTimestamp = parseVersionTimestamp(currentUpdatedAt);\n\n return expectedTimestamp !== null && currentTimestamp !== null && expectedTimestamp === currentTimestamp;\n};\n\nconst getPreservationFailure = (\n beforeMarkdown: string,\n afterMarkdown: string,\n policy: MarkdownChangePolicy | undefined,\n): MarkdownChangeFailure | null => {\n const beforeTagCount = countExplicitTagTokens(beforeMarkdown);\n const afterTagCount = countExplicitTagTokens(afterMarkdown);\n\n if (policy?.preserveTags === true && afterTagCount < beforeTagCount) {\n return {\n status: 'failed',\n reason: 'TAG_TOKEN_DECREASED',\n message: 'The patch would reduce explicit tag tokens.',\n };\n }\n\n const beforeReferenceCount = countMarkdownReferenceTokens(beforeMarkdown);\n const afterReferenceCount = countMarkdownReferenceTokens(afterMarkdown);\n\n if (policy?.preserveReferences === true && afterReferenceCount < beforeReferenceCount) {\n return {\n status: 'failed',\n reason: 'REFERENCE_TOKEN_DECREASED',\n message: 'The patch would reduce note reference tokens.',\n };\n }\n\n return null;\n};\n\nconst collectWarnings = (\n beforeMarkdown: string,\n afterMarkdown: string,\n policy: MarkdownChangePolicy | undefined,\n): string[] => {\n const warnings: string[] = [];\n const beforeTagCount = countExplicitTagTokens(beforeMarkdown);\n const afterTagCount = countExplicitTagTokens(afterMarkdown);\n const shouldWarnTags = policy?.preserveTags === undefined || policy.preserveTags === 'warn';\n\n if (shouldWarnTags && afterTagCount < beforeTagCount) {\n warnings.push('TAG_TOKEN_COUNT_DECREASED');\n }\n\n const beforeReferenceCount = countMarkdownReferenceTokens(beforeMarkdown);\n const afterReferenceCount = countMarkdownReferenceTokens(afterMarkdown);\n const shouldWarnReferences = policy?.preserveReferences === undefined || policy.preserveReferences === 'warn';\n\n if (shouldWarnReferences && afterReferenceCount < beforeReferenceCount) {\n warnings.push('REFERENCE_TOKEN_COUNT_DECREASED');\n }\n\n return warnings;\n};\n\nconst validateBaseline = (\n note: MarkdownPatchNoteSnapshot,\n expectedUpdatedAt: string | undefined,\n baseMarkdownSha256: string | undefined,\n): MarkdownChangeFailure | null => {\n const beforeMarkdownSha256 = calculateMarkdownSha256(note.markdown);\n\n if (!expectedUpdatedAt && !baseMarkdownSha256) {\n return {\n status: 'failed',\n reason: 'MISSING_BASELINE',\n message: 'expectedUpdatedAt or baseMarkdownSha256 is required for markdown writes.',\n };\n }\n\n if (\n (expectedUpdatedAt && !noteVersionsMatch(expectedUpdatedAt, note.updatedAt)) ||\n (baseMarkdownSha256 && baseMarkdownSha256 !== beforeMarkdownSha256)\n ) {\n return {\n status: 'failed',\n reason: 'BASELINE_MISMATCH',\n message: 'The markdown write baseline does not match the current note.',\n };\n }\n\n return null;\n};\n\nconst validateChangeLimits = (\n beforeMarkdown: string,\n afterMarkdown: string,\n changedCharCount: number,\n policy: MarkdownChangePolicy | undefined,\n): MarkdownChangeFailure | null => {\n const changedLineCount = countChangedLines(beforeMarkdown, afterMarkdown);\n\n if (\n (policy?.maxChangedLines !== undefined && changedLineCount > policy.maxChangedLines) ||\n (policy?.maxChangedChars !== undefined && changedCharCount > policy.maxChangedChars)\n ) {\n return {\n status: 'failed',\n reason: 'CHANGE_LIMIT_EXCEEDED',\n message: 'The markdown write exceeds the configured change limit.',\n };\n }\n\n return null;\n};\n\nconst createChangePlan = ({\n note,\n afterMarkdown,\n changedCharCount,\n policy,\n diffContextLines = DIFF_CONTEXT_LINES,\n match,\n placement,\n}: {\n note: MarkdownPatchNoteSnapshot;\n afterMarkdown: string;\n changedCharCount: number;\n policy: MarkdownChangePolicy | undefined;\n diffContextLines?: number | null;\n match?: MarkdownChangeDryRun['match'];\n placement?: MarkdownAppendPlacement;\n}): MarkdownChangePlan | MarkdownChangeFailure => {\n if (!policy?.allowNoop && afterMarkdown === note.markdown) {\n return {\n status: 'failed',\n reason: 'NOOP',\n message: 'The markdown write would not change the note.',\n };\n }\n\n const preservationFailure = getPreservationFailure(note.markdown, afterMarkdown, policy);\n\n if (preservationFailure) {\n return preservationFailure;\n }\n\n const limitFailure = validateChangeLimits(note.markdown, afterMarkdown, changedCharCount, policy);\n\n if (limitFailure) {\n return limitFailure;\n }\n\n return {\n status: 'dry_run',\n note: {\n id: note.id,\n title: note.title,\n updatedAt: note.updatedAt,\n },\n ...(match ? { match } : {}),\n ...(placement ? { placement } : {}),\n proposed: {\n changedLineCount: countChangedLines(note.markdown, afterMarkdown),\n changedCharCount,\n beforeMarkdownSha256: calculateMarkdownSha256(note.markdown),\n afterMarkdownSha256: calculateMarkdownSha256(afterMarkdown),\n diff: createUnifiedMarkdownDiff(note.markdown, afterMarkdown, diffContextLines),\n },\n warnings: collectWarnings(note.markdown, afterMarkdown, policy),\n afterMarkdown,\n };\n};\n\nconst matchHasAnchors = (markdown: string, match: RawTextMatch, selector: ExactTextMarkdownPatchSelector) => {\n if (selector.before !== undefined && !markdown.slice(0, match.start).endsWith(selector.before)) {\n return false;\n }\n\n if (selector.after !== undefined && !markdown.slice(match.end).startsWith(selector.after)) {\n return false;\n }\n\n return true;\n};\n\nconst selectExactTextMatch = (\n markdown: string,\n selector: ExactTextMarkdownPatchSelector,\n):\n | { match: RawTextMatch; patchMatch: MarkdownPatchMatch }\n | MarkdownPatchNeedsDisambiguation\n | MarkdownChangeFailure => {\n if (selector.text.length === 0) {\n return {\n status: 'failed',\n reason: 'EMPTY_TARGET',\n message: 'Exact text selector must not be empty.',\n };\n }\n\n const rawMatches = findExactTextMatches(markdown, selector.text);\n\n if (rawMatches.length === 0) {\n return {\n status: 'failed',\n reason: 'TARGET_NOT_FOUND',\n message: 'No exact text match was found in the current note.',\n };\n }\n\n const anchoredMatches = rawMatches.filter((match) => matchHasAnchors(markdown, match, selector));\n\n if (anchoredMatches.length === 0) {\n return {\n status: 'failed',\n reason: 'ANCHOR_MISMATCH',\n message: 'Exact text matches were found, but before/after anchors did not match.',\n };\n }\n\n if (anchoredMatches.length > 1) {\n return {\n status: 'needs_disambiguation',\n reason: 'TARGET_AMBIGUOUS',\n matches: anchoredMatches.map((match) => toPatchMatch(markdown, match, rawMatches.length)),\n };\n }\n\n const match = anchoredMatches[0];\n\n return {\n match,\n patchMatch: toPatchMatch(markdown, match, rawMatches.length),\n };\n};\n\nconst selectMatchCandidate = (\n markdown: string,\n selector: MatchCandidateMarkdownPatchSelector,\n): { match: RawTextMatch; patchMatch: MarkdownPatchMatch } | MarkdownChangeFailure => {\n if (selector.text.length === 0) {\n return {\n status: 'failed',\n reason: 'EMPTY_TARGET',\n message: 'Match candidate selector text must not be empty.',\n };\n }\n\n const rawMatches = findExactTextMatches(markdown, selector.text);\n\n if (rawMatches.length === 0) {\n return {\n status: 'failed',\n reason: 'TARGET_NOT_FOUND',\n message: 'No match candidate text was found in the current note.',\n };\n }\n\n const match = rawMatches.find((candidate) => candidate.matchIndex === selector.matchIndex);\n\n if (!match) {\n return {\n status: 'failed',\n reason: 'CANDIDATE_MISMATCH',\n message: 'The selected match index does not exist in the current note.',\n };\n }\n\n const patchMatch = toPatchMatch(markdown, match, rawMatches.length);\n const positionMatches = selector.positionHint === undefined || patchMatch.positionHint === selector.positionHint;\n\n if (\n patchMatch.lineStart !== selector.lineStart ||\n patchMatch.matchSha256 !== selector.matchSha256 ||\n patchMatch.surroundingHash !== selector.surroundingHash ||\n !positionMatches\n ) {\n return {\n status: 'failed',\n reason: 'CANDIDATE_MISMATCH',\n message: 'The selected match candidate no longer matches the current note context.',\n };\n }\n\n return {\n match,\n patchMatch,\n };\n};\n\nconst applyPatchOperation = (markdown: string, match: RawTextMatch, operation: MarkdownPatchOperation) => {\n if (operation.type === 'replace') {\n return {\n markdown: `${markdown.slice(0, match.start)}${operation.replacement}${markdown.slice(match.end)}`,\n changedCharCount: match.text.length + operation.replacement.length,\n };\n }\n\n if (operation.type === 'insert_before') {\n return {\n markdown: `${markdown.slice(0, match.start)}${operation.insertion}${markdown.slice(match.start)}`,\n changedCharCount: operation.insertion.length,\n };\n }\n\n return {\n markdown: `${markdown.slice(0, match.end)}${operation.insertion}${markdown.slice(match.end)}`,\n changedCharCount: operation.insertion.length,\n };\n};\n\nexport const buildMarkdownPatchPlan = (input: PreviewMarkdownPatchInput): MarkdownPatchPlanResult => {\n const baselineFailure = validateBaseline(input.note, input.expectedUpdatedAt, input.baseMarkdownSha256);\n\n if (baselineFailure) {\n return baselineFailure;\n }\n\n const selected =\n input.selector.type === 'exact_text'\n ? selectExactTextMatch(input.note.markdown, input.selector)\n : selectMatchCandidate(input.note.markdown, input.selector);\n\n if ('status' in selected) {\n return selected;\n }\n\n const patched = applyPatchOperation(input.note.markdown, selected.match, input.operation);\n const plan = createChangePlan({\n note: input.note,\n afterMarkdown: patched.markdown,\n changedCharCount: patched.changedCharCount,\n policy: input.policy,\n match: {\n count: 1,\n lineStart: selected.patchMatch.lineStart,\n lineEnd: selected.patchMatch.lineEnd,\n selectorType: input.selector.type,\n matchedTextSha256: selected.patchMatch.matchSha256,\n surroundingHash: selected.patchMatch.surroundingHash,\n },\n });\n\n return plan;\n};\n\nconst parseHeadingLine = (line: string) => {\n const match = /^(#{1,6})\\s+(.+?)\\s*#*\\s*$/.exec(line);\n\n if (!match) {\n return null;\n }\n\n return {\n level: match[1].length,\n heading: match[2].trim(),\n };\n};\n\nconst getLineStartOffsets = (lines: string[]) => {\n const offsets: number[] = [];\n let offset = 0;\n\n for (const line of lines) {\n offsets.push(offset);\n offset += line.length + 1;\n }\n\n return offsets;\n};\n\nconst findHeadingMatches = (markdown: string, placement: MarkdownAppendPlacementAfterHeading) => {\n const lines = splitLines(markdown);\n const offsets = getLineStartOffsets(lines);\n const headingLines = lines\n .map((line, index) => {\n const heading = parseHeadingLine(line);\n\n if (!heading) {\n return null;\n }\n\n return {\n ...heading,\n lineIndex: index,\n start: offsets[index],\n };\n })\n .filter((heading): heading is Omit<HeadingMatch, 'nextSectionStart'> => heading !== null);\n const matches = headingLines.filter((heading) => {\n return (\n heading.heading === placement.heading &&\n (placement.level === undefined || heading.level === placement.level)\n );\n });\n\n return matches.map((match) => {\n const nextHeading = headingLines.find(\n (heading) => heading.lineIndex > match.lineIndex && heading.level <= match.level,\n );\n\n return {\n ...match,\n nextSectionStart: nextHeading?.start ?? markdown.length,\n };\n });\n};\n\nconst appendWithSeparator = (prefix: string, insertion: string, separator: '\\n\\n' | '\\n') => {\n if (prefix.length === 0) {\n return insertion;\n }\n\n return `${prefix}${prefix.endsWith(separator) ? '' : separator}${insertion}`;\n};\n\nconst applyAppendPlacement = (\n markdown: string,\n insertion: string,\n placement: MarkdownAppendPlacement,\n separator: '\\n\\n' | '\\n',\n): { markdown: string; placement: MarkdownAppendPlacement } | MarkdownChangeFailure => {\n if (insertion.length === 0) {\n return {\n status: 'failed',\n reason: 'EMPTY_INSERTION',\n message: 'Append insertion must not be empty.',\n };\n }\n\n if (placement.type === 'end') {\n return {\n markdown: appendWithSeparator(markdown, insertion, separator),\n placement,\n };\n }\n\n if (\n placement.level !== undefined &&\n (!Number.isInteger(placement.level) || placement.level < 1 || placement.level > 6)\n ) {\n return {\n status: 'failed',\n reason: 'INVALID_HEADING_LEVEL',\n message: 'Heading level must be an integer from 1 to 6.',\n };\n }\n\n const headingMatches = findHeadingMatches(markdown, placement);\n\n if (headingMatches.length === 0) {\n return {\n status: 'failed',\n reason: 'HEADING_NOT_FOUND',\n message: 'The requested heading was not found.',\n };\n }\n\n if (headingMatches.length > 1) {\n return {\n status: 'failed',\n reason: 'HEADING_AMBIGUOUS',\n message: 'The requested heading appears more than once.',\n };\n }\n\n const [headingMatch] = headingMatches;\n const prefix = markdown.slice(0, headingMatch.nextSectionStart);\n const suffix = markdown.slice(headingMatch.nextSectionStart);\n const prefixWithInsertion = appendWithSeparator(prefix, insertion, separator);\n const nextMarkdown =\n suffix.length === 0\n ? prefixWithInsertion\n : `${prefixWithInsertion}${prefixWithInsertion.endsWith(separator) ? '' : separator}${suffix}`;\n\n return {\n markdown: nextMarkdown,\n placement,\n };\n};\n\nexport const buildMarkdownAppendPlan = (input: PreviewMarkdownAppendInput): MarkdownAppendPlanResult => {\n const baselineFailure = validateBaseline(input.note, input.expectedUpdatedAt, input.baseMarkdownSha256);\n\n if (baselineFailure) {\n return baselineFailure;\n }\n\n const placement = input.placement ?? { type: 'end' as const };\n const separator = input.separator ?? '\\n\\n';\n const appended = applyAppendPlacement(input.note.markdown, input.insertion, placement, separator);\n\n if ('status' in appended) {\n return appended;\n }\n\n return createChangePlan({\n note: input.note,\n afterMarkdown: appended.markdown,\n changedCharCount: input.insertion.length,\n policy: input.policy,\n placement: appended.placement,\n });\n};\n\nexport const buildMarkdownReplacePlan = (input: PreviewMarkdownReplaceInput): MarkdownReplacePlanResult => {\n const baselineFailure = validateBaseline(input.note, input.expectedUpdatedAt, input.baseMarkdownSha256);\n\n if (baselineFailure) {\n return baselineFailure;\n }\n\n if (input.replacement.length === 0) {\n return {\n status: 'failed',\n reason: 'EMPTY_REPLACEMENT',\n message: 'Replacement markdown must not be empty.',\n };\n }\n\n return createChangePlan({\n note: input.note,\n afterMarkdown: input.replacement,\n changedCharCount: input.note.markdown.length + input.replacement.length,\n policy: input.policy,\n diffContextLines: null,\n });\n};\n\nconst toPreviewResult = (plan: MarkdownChangePlan): MarkdownChangeDryRun => {\n return {\n status: 'dry_run',\n note: plan.note,\n ...(plan.match ? { match: plan.match } : {}),\n ...(plan.placement ? { placement: plan.placement } : {}),\n proposed: plan.proposed,\n warnings: plan.warnings,\n };\n};\n\nexport const previewMarkdownPatch = (input: PreviewMarkdownPatchInput): MarkdownPatchPreviewResult => {\n const plan = buildMarkdownPatchPlan(input);\n\n if (plan.status !== 'dry_run') {\n return plan;\n }\n\n return toPreviewResult(plan);\n};\n\nexport const previewMarkdownAppend = (\n input: PreviewMarkdownAppendInput,\n): MarkdownChangeDryRun | MarkdownChangeFailure => {\n const plan = buildMarkdownAppendPlan(input);\n\n if (plan.status !== 'dry_run') {\n return plan;\n }\n\n return toPreviewResult(plan);\n};\n\nexport const previewMarkdownReplace = (\n input: PreviewMarkdownReplaceInput,\n): MarkdownChangeDryRun | MarkdownChangeFailure => {\n const plan = buildMarkdownReplacePlan(input);\n\n if (plan.status !== 'dry_run') {\n return plan;\n }\n\n return toPreviewResult(plan);\n};\n"],"mappings":"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","names":["match"]}
|
|
1
|
+
{"version":3,"sources":["../../../../src/features/note/services/markdown-patch.ts"],"sourcesContent":["import crypto from 'crypto';\n\nconst HASH_ALGORITHM = 'sha256';\nconst EXCERPT_CONTEXT_CHARS = 80;\nconst DIFF_CONTEXT_LINES = 3;\n\nexport interface MarkdownPatchNoteSnapshot {\n id: string;\n title: string;\n updatedAt: string;\n markdown: string;\n}\n\nexport interface ExactTextMarkdownPatchSelector {\n type: 'exact_text';\n text: string;\n before?: string;\n after?: string;\n}\n\nexport interface MatchCandidateMarkdownPatchSelector {\n type: 'match_candidate';\n text: string;\n matchIndex: number;\n lineStart: number;\n matchSha256: string;\n surroundingHash: string;\n positionHint?: 'first' | 'last';\n}\n\nexport type MarkdownPatchSelector = ExactTextMarkdownPatchSelector | MatchCandidateMarkdownPatchSelector;\n\nexport interface ReplaceMarkdownPatchOperation {\n type: 'replace';\n replacement: string;\n}\n\nexport interface InsertBeforeMarkdownPatchOperation {\n type: 'insert_before';\n insertion: string;\n}\n\nexport interface InsertAfterMarkdownPatchOperation {\n type: 'insert_after';\n insertion: string;\n}\n\nexport type MarkdownPatchOperation =\n | ReplaceMarkdownPatchOperation\n | InsertBeforeMarkdownPatchOperation\n | InsertAfterMarkdownPatchOperation;\n\nexport interface MarkdownChangePolicy {\n allowNoop?: boolean;\n maxChangedChars?: number;\n maxChangedLines?: number;\n diffPreviewMaxChars?: number;\n preserveReferences?: boolean | 'warn';\n preserveTags?: boolean | 'warn';\n}\n\nexport interface PreviewMarkdownPatchInput {\n note: MarkdownPatchNoteSnapshot;\n expectedUpdatedAt?: string;\n baseMarkdownSha256?: string;\n intent: string;\n selector: MarkdownPatchSelector;\n operation: MarkdownPatchOperation;\n policy?: MarkdownChangePolicy;\n}\n\nexport interface MarkdownAppendPlacementEnd {\n type: 'end';\n}\n\nexport interface MarkdownAppendPlacementAfterHeading {\n type: 'after_heading';\n heading: string;\n level?: number;\n}\n\nexport type MarkdownAppendPlacement = MarkdownAppendPlacementEnd | MarkdownAppendPlacementAfterHeading;\n\nexport interface PreviewMarkdownAppendInput {\n note: MarkdownPatchNoteSnapshot;\n expectedUpdatedAt?: string;\n baseMarkdownSha256?: string;\n intent: string;\n insertion: string;\n placement?: MarkdownAppendPlacement;\n separator?: '\\n\\n' | '\\n';\n policy?: MarkdownChangePolicy;\n}\n\nexport interface PreviewMarkdownReplaceInput {\n note: MarkdownPatchNoteSnapshot;\n expectedUpdatedAt?: string;\n baseMarkdownSha256?: string;\n intent: string;\n replacement: string;\n policy?: MarkdownChangePolicy;\n}\n\nexport interface MarkdownPatchMatch {\n matchIndex: number;\n lineStart: number;\n lineEnd: number;\n text: string;\n beforeExcerpt?: string;\n afterExcerpt?: string;\n matchSha256: string;\n surroundingHash: string;\n positionHint?: 'first' | 'last';\n}\n\nexport interface MarkdownChangeDryRun {\n status: 'dry_run';\n note: {\n id: string;\n title: string;\n updatedAt: string;\n };\n match?: {\n count: 1;\n lineStart: number;\n lineEnd: number;\n selectorType: string;\n matchedTextSha256: string;\n surroundingHash: string;\n };\n placement?: MarkdownAppendPlacement;\n proposed: {\n changedLineCount: number;\n changedCharCount: number;\n beforeMarkdownSha256: string;\n afterMarkdownSha256: string;\n diff: string;\n diffTruncated?: boolean;\n diffCharCount?: number;\n };\n warnings: string[];\n}\n\nexport interface MarkdownChangePlan extends MarkdownChangeDryRun {\n afterMarkdown: string;\n}\n\nexport interface MarkdownPatchNeedsDisambiguation {\n status: 'needs_disambiguation';\n reason: 'TARGET_AMBIGUOUS';\n matches: MarkdownPatchMatch[];\n}\n\nexport interface MarkdownChangeFailure {\n status: 'failed';\n reason:\n | 'ANCHOR_MISMATCH'\n | 'BASELINE_MISMATCH'\n | 'CANDIDATE_MISMATCH'\n | 'CHANGE_LIMIT_EXCEEDED'\n | 'EMPTY_INSERTION'\n | 'EMPTY_REPLACEMENT'\n | 'EMPTY_TARGET'\n | 'HEADING_AMBIGUOUS'\n | 'HEADING_NOT_FOUND'\n | 'INVALID_HEADING_LEVEL'\n | 'INVALID_PROPERTY_INPUT'\n | 'MARKDOWN_IMPORT_LOSSY'\n | 'MISSING_BASELINE'\n | 'NOOP'\n | 'REFERENCE_STRUCTURE_DECREASED'\n | 'REFERENCE_TOKEN_DECREASED'\n | 'TAG_TOKEN_DECREASED'\n | 'TARGET_NOT_FOUND'\n | 'UNSUPPORTED_MARKDOWN_STRUCTURE';\n message: string;\n details?: Record<string, unknown>;\n}\n\nexport type MarkdownPatchPreviewResult =\n | MarkdownChangeDryRun\n | MarkdownPatchNeedsDisambiguation\n | MarkdownChangeFailure;\n\nexport type MarkdownPatchPlanResult = MarkdownChangePlan | MarkdownPatchNeedsDisambiguation | MarkdownChangeFailure;\nexport type MarkdownAppendPlanResult = MarkdownChangePlan | MarkdownChangeFailure;\nexport type MarkdownReplacePlanResult = MarkdownChangePlan | MarkdownChangeFailure;\n\ninterface RawTextMatch {\n matchIndex: number;\n start: number;\n end: number;\n text: string;\n}\n\ninterface HeadingMatch {\n heading: string;\n level: number;\n lineIndex: number;\n start: number;\n nextSectionStart: number;\n}\n\nexport const calculateMarkdownSha256 = (value: string) => {\n return crypto.createHash(HASH_ALGORITHM).update(value, 'utf8').digest('hex');\n};\n\nconst countNewlines = (value: string) => {\n let count = 0;\n\n for (const character of value) {\n if (character === '\\n') {\n count += 1;\n }\n }\n\n return count;\n};\n\nconst getLineRange = (markdown: string, match: RawTextMatch) => {\n const lineStart = countNewlines(markdown.slice(0, match.start)) + 1;\n const lineEnd = lineStart + countNewlines(match.text);\n\n return {\n lineStart,\n lineEnd,\n };\n};\n\nconst buildExcerpt = (markdown: string, start: number, end: number) => {\n const excerpt = markdown.slice(start, end).trim();\n\n return excerpt.length > 0 ? excerpt : undefined;\n};\n\nconst toPatchMatch = (markdown: string, match: RawTextMatch, totalMatches: number): MarkdownPatchMatch => {\n const { lineStart, lineEnd } = getLineRange(markdown, match);\n const beforeStart = Math.max(0, match.start - EXCERPT_CONTEXT_CHARS);\n const afterEnd = Math.min(markdown.length, match.end + EXCERPT_CONTEXT_CHARS);\n const beforeText = markdown.slice(beforeStart, match.start);\n const afterText = markdown.slice(match.end, afterEnd);\n\n return {\n matchIndex: match.matchIndex,\n lineStart,\n lineEnd,\n text: match.text,\n beforeExcerpt: buildExcerpt(markdown, beforeStart, match.start),\n afterExcerpt: buildExcerpt(markdown, match.end, afterEnd),\n matchSha256: calculateMarkdownSha256(match.text),\n surroundingHash: calculateMarkdownSha256(JSON.stringify({ beforeText, targetText: match.text, afterText })),\n ...(match.matchIndex === 0 && totalMatches > 1 ? { positionHint: 'first' } : {}),\n ...(match.matchIndex === totalMatches - 1 && totalMatches > 1 ? { positionHint: 'last' } : {}),\n };\n};\n\nconst findExactTextMatches = (markdown: string, text: string) => {\n const matches: RawTextMatch[] = [];\n let searchStart = 0;\n\n while (searchStart <= markdown.length) {\n const start = markdown.indexOf(text, searchStart);\n\n if (start === -1) {\n break;\n }\n\n matches.push({\n matchIndex: matches.length,\n start,\n end: start + text.length,\n text,\n });\n searchStart = start + text.length;\n }\n\n return matches;\n};\n\nconst splitLines = (value: string) => {\n return value.split('\\n');\n};\n\nconst findSharedLineEdges = (beforeLines: string[], afterLines: string[]) => {\n let prefix = 0;\n const maxPrefix = Math.min(beforeLines.length, afterLines.length);\n\n while (prefix < maxPrefix && beforeLines[prefix] === afterLines[prefix]) {\n prefix += 1;\n }\n\n let suffix = 0;\n const maxSuffix = Math.min(beforeLines.length - prefix, afterLines.length - prefix);\n\n while (\n suffix < maxSuffix &&\n beforeLines[beforeLines.length - 1 - suffix] === afterLines[afterLines.length - 1 - suffix]\n ) {\n suffix += 1;\n }\n\n return {\n prefix,\n suffix,\n };\n};\n\nconst formatDiffRange = (lineIndex: number, lineCount: number) => {\n const lineNumber = lineIndex + 1;\n\n return lineCount === 1 ? String(lineNumber) : `${lineNumber},${lineCount}`;\n};\n\nexport const createUnifiedMarkdownDiff = (\n beforeMarkdown: string,\n afterMarkdown: string,\n contextLines: number | null = DIFF_CONTEXT_LINES,\n) => {\n const beforeLines = splitLines(beforeMarkdown);\n const afterLines = splitLines(afterMarkdown);\n const { prefix, suffix } = findSharedLineEdges(beforeLines, afterLines);\n const changedBeforeStart = prefix;\n const changedBeforeEnd = beforeLines.length - suffix;\n const changedAfterStart = prefix;\n const changedAfterEnd = afterLines.length - suffix;\n const displayBeforeStart = contextLines === null ? 0 : Math.max(0, changedBeforeStart - contextLines);\n const displayBeforeEnd =\n contextLines === null ? beforeLines.length : Math.min(beforeLines.length, changedBeforeEnd + contextLines);\n const displayAfterStart = contextLines === null ? 0 : Math.max(0, changedAfterStart - contextLines);\n const displayAfterEnd =\n contextLines === null ? afterLines.length : Math.min(afterLines.length, changedAfterEnd + contextLines);\n const diffLines = [\n '--- before.md',\n '+++ after.md',\n `@@ -${formatDiffRange(displayBeforeStart, displayBeforeEnd - displayBeforeStart)} +${formatDiffRange(displayAfterStart, displayAfterEnd - displayAfterStart)} @@`,\n ];\n\n for (let index = displayBeforeStart; index < changedBeforeStart; index += 1) {\n diffLines.push(` ${beforeLines[index]}`);\n }\n\n for (let index = changedBeforeStart; index < changedBeforeEnd; index += 1) {\n diffLines.push(`-${beforeLines[index]}`);\n }\n\n for (let index = changedAfterStart; index < changedAfterEnd; index += 1) {\n diffLines.push(`+${afterLines[index]}`);\n }\n\n for (let index = changedBeforeEnd; index < displayBeforeEnd; index += 1) {\n diffLines.push(` ${beforeLines[index]}`);\n }\n\n return diffLines.join('\\n');\n};\n\nconst countChangedLines = (beforeMarkdown: string, afterMarkdown: string) => {\n const beforeLines = splitLines(beforeMarkdown);\n const afterLines = splitLines(afterMarkdown);\n const { prefix, suffix } = findSharedLineEdges(beforeLines, afterLines);\n const removedLineCount = beforeLines.length - prefix - suffix;\n const addedLineCount = afterLines.length - prefix - suffix;\n\n return Math.max(removedLineCount, addedLineCount);\n};\n\nconst isMarkdownTokenWhitespace = (character: string) => /\\s/.test(character);\n\nexport const countExplicitTagTokens = (value: string) => {\n let count = 0;\n\n for (let index = 0; index < value.length; index += 1) {\n if (value[index] !== '[' || (value[index + 1] !== '@' && value[index + 1] !== '#')) {\n continue;\n }\n\n let cursor = index + 2;\n let isValid = cursor < value.length;\n\n while (cursor < value.length && value[cursor] !== ']') {\n if (value[cursor] === '[' || isMarkdownTokenWhitespace(value[cursor])) {\n isValid = false;\n break;\n }\n\n cursor += 1;\n }\n\n if (isValid && cursor < value.length && cursor > index + 2) {\n count += 1;\n index = cursor;\n continue;\n }\n\n if (!isValid) {\n index = Math.max(index, cursor - 1);\n }\n }\n\n return count;\n};\n\nexport const countMarkdownReferenceTokens = (value: string) => {\n let count = 0;\n let cursor = 0;\n\n while (cursor < value.length) {\n const start = value.indexOf('[[', cursor);\n\n if (start === -1) {\n break;\n }\n\n const end = value.indexOf(']]', start + 2);\n\n if (end === -1) {\n break;\n }\n\n if (end > start + 2) {\n count += 1;\n }\n\n cursor = end + 2;\n }\n\n return count;\n};\n\nconst parseVersionTimestamp = (value: string) => {\n const timestamp = /^\\d+$/.test(value) ? Number(value) : Date.parse(value);\n\n return Number.isFinite(timestamp) ? timestamp : null;\n};\n\nconst noteVersionsMatch = (expectedUpdatedAt: string, currentUpdatedAt: string) => {\n const expectedTimestamp = parseVersionTimestamp(expectedUpdatedAt);\n const currentTimestamp = parseVersionTimestamp(currentUpdatedAt);\n\n return expectedTimestamp !== null && currentTimestamp !== null && expectedTimestamp === currentTimestamp;\n};\n\nconst getPreservationFailure = (\n beforeMarkdown: string,\n afterMarkdown: string,\n policy: MarkdownChangePolicy | undefined,\n): MarkdownChangeFailure | null => {\n const beforeTagCount = countExplicitTagTokens(beforeMarkdown);\n const afterTagCount = countExplicitTagTokens(afterMarkdown);\n\n if (policy?.preserveTags === true && afterTagCount < beforeTagCount) {\n return {\n status: 'failed',\n reason: 'TAG_TOKEN_DECREASED',\n message: 'The patch would reduce explicit tag tokens.',\n };\n }\n\n const beforeReferenceCount = countMarkdownReferenceTokens(beforeMarkdown);\n const afterReferenceCount = countMarkdownReferenceTokens(afterMarkdown);\n\n if (policy?.preserveReferences === true && afterReferenceCount < beforeReferenceCount) {\n return {\n status: 'failed',\n reason: 'REFERENCE_TOKEN_DECREASED',\n message: 'The patch would reduce note reference tokens.',\n };\n }\n\n return null;\n};\n\nconst collectWarnings = (\n beforeMarkdown: string,\n afterMarkdown: string,\n policy: MarkdownChangePolicy | undefined,\n): string[] => {\n const warnings: string[] = [];\n const beforeTagCount = countExplicitTagTokens(beforeMarkdown);\n const afterTagCount = countExplicitTagTokens(afterMarkdown);\n const shouldWarnTags = policy?.preserveTags === undefined || policy.preserveTags === 'warn';\n\n if (shouldWarnTags && afterTagCount < beforeTagCount) {\n warnings.push('TAG_TOKEN_COUNT_DECREASED');\n }\n\n const beforeReferenceCount = countMarkdownReferenceTokens(beforeMarkdown);\n const afterReferenceCount = countMarkdownReferenceTokens(afterMarkdown);\n const shouldWarnReferences = policy?.preserveReferences === undefined || policy.preserveReferences === 'warn';\n\n if (shouldWarnReferences && afterReferenceCount < beforeReferenceCount) {\n warnings.push('REFERENCE_TOKEN_COUNT_DECREASED');\n }\n\n return warnings;\n};\n\nconst validateBaseline = (\n note: MarkdownPatchNoteSnapshot,\n expectedUpdatedAt: string | undefined,\n baseMarkdownSha256: string | undefined,\n): MarkdownChangeFailure | null => {\n const beforeMarkdownSha256 = calculateMarkdownSha256(note.markdown);\n\n if (!expectedUpdatedAt && !baseMarkdownSha256) {\n return {\n status: 'failed',\n reason: 'MISSING_BASELINE',\n message: 'expectedUpdatedAt or baseMarkdownSha256 is required for markdown writes.',\n };\n }\n\n if (\n (expectedUpdatedAt && !noteVersionsMatch(expectedUpdatedAt, note.updatedAt)) ||\n (baseMarkdownSha256 && baseMarkdownSha256 !== beforeMarkdownSha256)\n ) {\n return {\n status: 'failed',\n reason: 'BASELINE_MISMATCH',\n message:\n 'The markdown write baseline does not match the current note. expectedUpdatedAt accepts an ISO datetime string or epoch milliseconds string.',\n };\n }\n\n return null;\n};\n\nconst validateChangeLimits = (\n beforeMarkdown: string,\n afterMarkdown: string,\n changedCharCount: number,\n policy: MarkdownChangePolicy | undefined,\n): MarkdownChangeFailure | null => {\n const changedLineCount = countChangedLines(beforeMarkdown, afterMarkdown);\n const exceededLines = policy?.maxChangedLines !== undefined && changedLineCount > policy.maxChangedLines;\n const exceededChars = policy?.maxChangedChars !== undefined && changedCharCount > policy.maxChangedChars;\n\n if (exceededLines || exceededChars) {\n return {\n status: 'failed',\n reason: 'CHANGE_LIMIT_EXCEEDED',\n message:\n 'The markdown write exceeds the configured change limit. Increase maxChangedLines/maxChangedChars to at least the requiredMinimum values or omit them for this write.',\n details: {\n configured: {\n ...(policy?.maxChangedLines !== undefined ? { maxChangedLines: policy.maxChangedLines } : {}),\n ...(policy?.maxChangedChars !== undefined ? { maxChangedChars: policy.maxChangedChars } : {}),\n },\n requiredMinimum: {\n maxChangedLines: changedLineCount,\n maxChangedChars: changedCharCount,\n },\n },\n };\n }\n\n return null;\n};\n\nconst buildDiffPreview = (diff: string, policy: MarkdownChangePolicy | undefined) => {\n const maxChars = policy?.diffPreviewMaxChars;\n\n if (maxChars === undefined || diff.length <= maxChars) {\n return {\n diff,\n ...(maxChars !== undefined ? { diffCharCount: diff.length } : {}),\n };\n }\n\n return {\n diff: diff.slice(0, maxChars),\n diffTruncated: true,\n diffCharCount: diff.length,\n };\n};\n\nconst createChangePlan = ({\n note,\n afterMarkdown,\n changedCharCount,\n policy,\n diffContextLines = DIFF_CONTEXT_LINES,\n match,\n placement,\n}: {\n note: MarkdownPatchNoteSnapshot;\n afterMarkdown: string;\n changedCharCount: number;\n policy: MarkdownChangePolicy | undefined;\n diffContextLines?: number | null;\n match?: MarkdownChangeDryRun['match'];\n placement?: MarkdownAppendPlacement;\n}): MarkdownChangePlan | MarkdownChangeFailure => {\n if (!policy?.allowNoop && afterMarkdown === note.markdown) {\n return {\n status: 'failed',\n reason: 'NOOP',\n message: 'The markdown write would not change the note.',\n };\n }\n\n const preservationFailure = getPreservationFailure(note.markdown, afterMarkdown, policy);\n\n if (preservationFailure) {\n return preservationFailure;\n }\n\n const limitFailure = validateChangeLimits(note.markdown, afterMarkdown, changedCharCount, policy);\n\n if (limitFailure) {\n return limitFailure;\n }\n\n const diffPreview = buildDiffPreview(\n createUnifiedMarkdownDiff(note.markdown, afterMarkdown, diffContextLines),\n policy,\n );\n\n return {\n status: 'dry_run',\n note: {\n id: note.id,\n title: note.title,\n updatedAt: note.updatedAt,\n },\n ...(match ? { match } : {}),\n ...(placement ? { placement } : {}),\n proposed: {\n changedLineCount: countChangedLines(note.markdown, afterMarkdown),\n changedCharCount,\n beforeMarkdownSha256: calculateMarkdownSha256(note.markdown),\n afterMarkdownSha256: calculateMarkdownSha256(afterMarkdown),\n ...diffPreview,\n },\n warnings: collectWarnings(note.markdown, afterMarkdown, policy),\n afterMarkdown,\n };\n};\n\nconst matchHasAnchors = (markdown: string, match: RawTextMatch, selector: ExactTextMarkdownPatchSelector) => {\n if (selector.before !== undefined && !markdown.slice(0, match.start).endsWith(selector.before)) {\n return false;\n }\n\n if (selector.after !== undefined && !markdown.slice(match.end).startsWith(selector.after)) {\n return false;\n }\n\n return true;\n};\n\nconst selectExactTextMatch = (\n markdown: string,\n selector: ExactTextMarkdownPatchSelector,\n):\n | { match: RawTextMatch; patchMatch: MarkdownPatchMatch }\n | MarkdownPatchNeedsDisambiguation\n | MarkdownChangeFailure => {\n if (selector.text.length === 0) {\n return {\n status: 'failed',\n reason: 'EMPTY_TARGET',\n message: 'Exact text selector must not be empty.',\n };\n }\n\n const rawMatches = findExactTextMatches(markdown, selector.text);\n\n if (rawMatches.length === 0) {\n return {\n status: 'failed',\n reason: 'TARGET_NOT_FOUND',\n message: 'No exact text match was found in the current note.',\n };\n }\n\n const anchoredMatches = rawMatches.filter((match) => matchHasAnchors(markdown, match, selector));\n\n if (anchoredMatches.length === 0) {\n return {\n status: 'failed',\n reason: 'ANCHOR_MISMATCH',\n message: 'Exact text matches were found, but before/after anchors did not match.',\n };\n }\n\n if (anchoredMatches.length > 1) {\n return {\n status: 'needs_disambiguation',\n reason: 'TARGET_AMBIGUOUS',\n matches: anchoredMatches.map((match) => toPatchMatch(markdown, match, rawMatches.length)),\n };\n }\n\n const match = anchoredMatches[0];\n\n return {\n match,\n patchMatch: toPatchMatch(markdown, match, rawMatches.length),\n };\n};\n\nconst selectMatchCandidate = (\n markdown: string,\n selector: MatchCandidateMarkdownPatchSelector,\n): { match: RawTextMatch; patchMatch: MarkdownPatchMatch } | MarkdownChangeFailure => {\n if (selector.text.length === 0) {\n return {\n status: 'failed',\n reason: 'EMPTY_TARGET',\n message: 'Match candidate selector text must not be empty.',\n };\n }\n\n const rawMatches = findExactTextMatches(markdown, selector.text);\n\n if (rawMatches.length === 0) {\n return {\n status: 'failed',\n reason: 'TARGET_NOT_FOUND',\n message: 'No match candidate text was found in the current note.',\n };\n }\n\n const match = rawMatches.find((candidate) => candidate.matchIndex === selector.matchIndex);\n\n if (!match) {\n return {\n status: 'failed',\n reason: 'CANDIDATE_MISMATCH',\n message: 'The selected match index does not exist in the current note.',\n };\n }\n\n const patchMatch = toPatchMatch(markdown, match, rawMatches.length);\n const positionMatches = selector.positionHint === undefined || patchMatch.positionHint === selector.positionHint;\n\n if (\n patchMatch.lineStart !== selector.lineStart ||\n patchMatch.matchSha256 !== selector.matchSha256 ||\n patchMatch.surroundingHash !== selector.surroundingHash ||\n !positionMatches\n ) {\n return {\n status: 'failed',\n reason: 'CANDIDATE_MISMATCH',\n message: 'The selected match candidate no longer matches the current note context.',\n };\n }\n\n return {\n match,\n patchMatch,\n };\n};\n\nconst applyPatchOperation = (markdown: string, match: RawTextMatch, operation: MarkdownPatchOperation) => {\n if (operation.type === 'replace') {\n return {\n markdown: `${markdown.slice(0, match.start)}${operation.replacement}${markdown.slice(match.end)}`,\n changedCharCount: match.text.length + operation.replacement.length,\n };\n }\n\n if (operation.type === 'insert_before') {\n return {\n markdown: `${markdown.slice(0, match.start)}${operation.insertion}${markdown.slice(match.start)}`,\n changedCharCount: operation.insertion.length,\n };\n }\n\n return {\n markdown: `${markdown.slice(0, match.end)}${operation.insertion}${markdown.slice(match.end)}`,\n changedCharCount: operation.insertion.length,\n };\n};\n\nexport const buildMarkdownPatchPlan = (input: PreviewMarkdownPatchInput): MarkdownPatchPlanResult => {\n const baselineFailure = validateBaseline(input.note, input.expectedUpdatedAt, input.baseMarkdownSha256);\n\n if (baselineFailure) {\n return baselineFailure;\n }\n\n const selected =\n input.selector.type === 'exact_text'\n ? selectExactTextMatch(input.note.markdown, input.selector)\n : selectMatchCandidate(input.note.markdown, input.selector);\n\n if ('status' in selected) {\n return selected;\n }\n\n const patched = applyPatchOperation(input.note.markdown, selected.match, input.operation);\n const plan = createChangePlan({\n note: input.note,\n afterMarkdown: patched.markdown,\n changedCharCount: patched.changedCharCount,\n policy: input.policy,\n match: {\n count: 1,\n lineStart: selected.patchMatch.lineStart,\n lineEnd: selected.patchMatch.lineEnd,\n selectorType: input.selector.type,\n matchedTextSha256: selected.patchMatch.matchSha256,\n surroundingHash: selected.patchMatch.surroundingHash,\n },\n });\n\n return plan;\n};\n\nconst parseHeadingLine = (line: string) => {\n const match = /^(#{1,6})\\s+(.+?)\\s*#*\\s*$/.exec(line);\n\n if (!match) {\n return null;\n }\n\n return {\n level: match[1].length,\n heading: match[2].trim(),\n };\n};\n\nconst getLineStartOffsets = (lines: string[]) => {\n const offsets: number[] = [];\n let offset = 0;\n\n for (const line of lines) {\n offsets.push(offset);\n offset += line.length + 1;\n }\n\n return offsets;\n};\n\nconst findHeadingMatches = (markdown: string, placement: MarkdownAppendPlacementAfterHeading) => {\n const lines = splitLines(markdown);\n const offsets = getLineStartOffsets(lines);\n const headingLines = lines\n .map((line, index) => {\n const heading = parseHeadingLine(line);\n\n if (!heading) {\n return null;\n }\n\n return {\n ...heading,\n lineIndex: index,\n start: offsets[index],\n };\n })\n .filter((heading): heading is Omit<HeadingMatch, 'nextSectionStart'> => heading !== null);\n const matches = headingLines.filter((heading) => {\n return (\n heading.heading === placement.heading &&\n (placement.level === undefined || heading.level === placement.level)\n );\n });\n\n return matches.map((match) => {\n const nextHeading = headingLines.find(\n (heading) => heading.lineIndex > match.lineIndex && heading.level <= match.level,\n );\n\n return {\n ...match,\n nextSectionStart: nextHeading?.start ?? markdown.length,\n };\n });\n};\n\nconst appendWithSeparator = (prefix: string, insertion: string, separator: '\\n\\n' | '\\n') => {\n if (prefix.length === 0) {\n return insertion;\n }\n\n return `${prefix}${prefix.endsWith(separator) ? '' : separator}${insertion}`;\n};\n\nconst applyAppendPlacement = (\n markdown: string,\n insertion: string,\n placement: MarkdownAppendPlacement,\n separator: '\\n\\n' | '\\n',\n): { markdown: string; placement: MarkdownAppendPlacement } | MarkdownChangeFailure => {\n if (insertion.length === 0) {\n return {\n status: 'failed',\n reason: 'EMPTY_INSERTION',\n message: 'Append insertion must not be empty.',\n };\n }\n\n if (placement.type === 'end') {\n return {\n markdown: appendWithSeparator(markdown, insertion, separator),\n placement,\n };\n }\n\n if (\n placement.level !== undefined &&\n (!Number.isInteger(placement.level) || placement.level < 1 || placement.level > 6)\n ) {\n return {\n status: 'failed',\n reason: 'INVALID_HEADING_LEVEL',\n message: 'Heading level must be an integer from 1 to 6.',\n };\n }\n\n const headingMatches = findHeadingMatches(markdown, placement);\n\n if (headingMatches.length === 0) {\n return {\n status: 'failed',\n reason: 'HEADING_NOT_FOUND',\n message: 'The requested heading was not found.',\n };\n }\n\n if (headingMatches.length > 1) {\n return {\n status: 'failed',\n reason: 'HEADING_AMBIGUOUS',\n message: 'The requested heading appears more than once.',\n };\n }\n\n const [headingMatch] = headingMatches;\n const prefix = markdown.slice(0, headingMatch.nextSectionStart);\n const suffix = markdown.slice(headingMatch.nextSectionStart);\n const prefixWithInsertion = appendWithSeparator(prefix, insertion, separator);\n const nextMarkdown =\n suffix.length === 0\n ? prefixWithInsertion\n : `${prefixWithInsertion}${prefixWithInsertion.endsWith(separator) ? '' : separator}${suffix}`;\n\n return {\n markdown: nextMarkdown,\n placement,\n };\n};\n\nexport const buildMarkdownAppendPlan = (input: PreviewMarkdownAppendInput): MarkdownAppendPlanResult => {\n const baselineFailure = validateBaseline(input.note, input.expectedUpdatedAt, input.baseMarkdownSha256);\n\n if (baselineFailure) {\n return baselineFailure;\n }\n\n const placement = input.placement ?? { type: 'end' as const };\n const separator = input.separator ?? '\\n\\n';\n const appended = applyAppendPlacement(input.note.markdown, input.insertion, placement, separator);\n\n if ('status' in appended) {\n return appended;\n }\n\n return createChangePlan({\n note: input.note,\n afterMarkdown: appended.markdown,\n changedCharCount: input.insertion.length,\n policy: input.policy,\n placement: appended.placement,\n });\n};\n\nexport const buildMarkdownReplacePlan = (input: PreviewMarkdownReplaceInput): MarkdownReplacePlanResult => {\n const baselineFailure = validateBaseline(input.note, input.expectedUpdatedAt, input.baseMarkdownSha256);\n\n if (baselineFailure) {\n return baselineFailure;\n }\n\n if (input.replacement.length === 0) {\n return {\n status: 'failed',\n reason: 'EMPTY_REPLACEMENT',\n message: 'Replacement markdown must not be empty.',\n };\n }\n\n return createChangePlan({\n note: input.note,\n afterMarkdown: input.replacement,\n changedCharCount: input.note.markdown.length + input.replacement.length,\n policy: input.policy,\n diffContextLines: null,\n });\n};\n\nconst toPreviewResult = (plan: MarkdownChangePlan): MarkdownChangeDryRun => {\n return {\n status: 'dry_run',\n note: plan.note,\n ...(plan.match ? { match: plan.match } : {}),\n ...(plan.placement ? { placement: plan.placement } : {}),\n proposed: plan.proposed,\n warnings: plan.warnings,\n };\n};\n\nexport const previewMarkdownPatch = (input: PreviewMarkdownPatchInput): MarkdownPatchPreviewResult => {\n const plan = buildMarkdownPatchPlan(input);\n\n if (plan.status !== 'dry_run') {\n return plan;\n }\n\n return toPreviewResult(plan);\n};\n\nexport const previewMarkdownAppend = (\n input: PreviewMarkdownAppendInput,\n): MarkdownChangeDryRun | MarkdownChangeFailure => {\n const plan = buildMarkdownAppendPlan(input);\n\n if (plan.status !== 'dry_run') {\n return plan;\n }\n\n return toPreviewResult(plan);\n};\n\nexport const previewMarkdownReplace = (\n input: PreviewMarkdownReplaceInput,\n): MarkdownChangeDryRun | MarkdownChangeFailure => {\n const plan = buildMarkdownReplacePlan(input);\n\n if (plan.status !== 'dry_run') {\n return plan;\n }\n\n return toPreviewResult(plan);\n};\n"],"mappings":"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","names":["match"]}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { FILE_HEADERS_ONLY, formatPatch, structuredPatch } from "diff";
|
|
1
2
|
import { ensureTagByName } from "../../../features/tag/services/organization.js";
|
|
2
3
|
import models from "../../../models.js";
|
|
3
4
|
import { blocksToMarkdown, extractTagIdsFromContentJson } from "../../../modules/blocknote.js";
|
|
@@ -8,6 +9,7 @@ import {
|
|
|
8
9
|
SNAPSHOT_MAX_PER_NOTE,
|
|
9
10
|
SNAPSHOT_RETENTION_DAYS
|
|
10
11
|
} from "../../../modules/recovery-retention.js";
|
|
12
|
+
import { calculateMarkdownSha256 } from "./markdown-patch.js";
|
|
11
13
|
import { buildNoteSearchProjection, extractVisibleSearchTextFromContent } from "./search.js";
|
|
12
14
|
const SNAPSHOT_CONTENT_PREVIEW_MAX_LENGTH = 240;
|
|
13
15
|
const parseMeta = (value) => {
|
|
@@ -31,6 +33,13 @@ const readSnapshotContent = (payload) => {
|
|
|
31
33
|
return "";
|
|
32
34
|
}
|
|
33
35
|
};
|
|
36
|
+
const readSnapshotTitle = (payload, fallback) => {
|
|
37
|
+
try {
|
|
38
|
+
return parsePayload(payload).title;
|
|
39
|
+
} catch {
|
|
40
|
+
return fallback;
|
|
41
|
+
}
|
|
42
|
+
};
|
|
34
43
|
const buildSnapshotContentPreview = (content) => {
|
|
35
44
|
const preview = extractVisibleSearchTextFromContent(content);
|
|
36
45
|
if (preview.length <= SNAPSHOT_CONTENT_PREVIEW_MAX_LENGTH) {
|
|
@@ -159,6 +168,65 @@ const renderNoteSnapshotContentAsMarkdown = async (payload) => {
|
|
|
159
168
|
return "";
|
|
160
169
|
}
|
|
161
170
|
};
|
|
171
|
+
const renderNoteRecordContentAsMarkdown = async (note) => {
|
|
172
|
+
try {
|
|
173
|
+
return blocksToMarkdown(note.content);
|
|
174
|
+
} catch {
|
|
175
|
+
return "";
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
const createSnapshotMarkdownDiff = (beforeMarkdown, afterMarkdown, contextLines = 3) => {
|
|
179
|
+
const patch = structuredPatch("before.md", "after.md", beforeMarkdown, afterMarkdown, void 0, void 0, {
|
|
180
|
+
context: contextLines === null ? Number.MAX_SAFE_INTEGER : contextLines
|
|
181
|
+
});
|
|
182
|
+
const changedLines = patch.hunks.flatMap(
|
|
183
|
+
(hunk) => hunk.lines.filter((line) => line.startsWith("+") && !line.startsWith("+++") || line.startsWith("-"))
|
|
184
|
+
);
|
|
185
|
+
return {
|
|
186
|
+
markdown: formatPatch(patch, FILE_HEADERS_ONLY).trimEnd(),
|
|
187
|
+
changedLineCount: changedLines.length,
|
|
188
|
+
changedCharCount: changedLines.reduce((total, line) => total + line.slice(1).length, 0)
|
|
189
|
+
};
|
|
190
|
+
};
|
|
191
|
+
const snapshotToDiffEndpoint = (snapshot) => ({
|
|
192
|
+
kind: "snapshot",
|
|
193
|
+
id: String(snapshot.id),
|
|
194
|
+
title: readSnapshotTitle(snapshot.payload, snapshot.title),
|
|
195
|
+
createdAt: snapshot.createdAt.toISOString(),
|
|
196
|
+
meta: parseMeta(snapshot.meta)
|
|
197
|
+
});
|
|
198
|
+
const noteToDiffEndpoint = (note) => ({
|
|
199
|
+
kind: "current_note",
|
|
200
|
+
id: String(note.id),
|
|
201
|
+
title: note.title,
|
|
202
|
+
...note.updatedAt ? { updatedAt: note.updatedAt.toISOString() } : {}
|
|
203
|
+
});
|
|
204
|
+
const buildSnapshotDiff = async ({
|
|
205
|
+
beforeSnapshot,
|
|
206
|
+
afterSnapshot,
|
|
207
|
+
afterNote,
|
|
208
|
+
contextLines
|
|
209
|
+
}) => {
|
|
210
|
+
if (!afterSnapshot && !afterNote) {
|
|
211
|
+
return null;
|
|
212
|
+
}
|
|
213
|
+
const beforeMarkdown = await renderNoteSnapshotContentAsMarkdown(beforeSnapshot.payload);
|
|
214
|
+
const afterMarkdown = afterSnapshot ? await renderNoteSnapshotContentAsMarkdown(afterSnapshot.payload) : afterNote ? await renderNoteRecordContentAsMarkdown(afterNote) : "";
|
|
215
|
+
const diff = createSnapshotMarkdownDiff(beforeMarkdown, afterMarkdown, contextLines ?? 3);
|
|
216
|
+
return {
|
|
217
|
+
noteId: String(beforeSnapshot.noteId),
|
|
218
|
+
mode: afterSnapshot ? "snapshot_to_snapshot" : "snapshot_to_current",
|
|
219
|
+
before: snapshotToDiffEndpoint(beforeSnapshot),
|
|
220
|
+
after: afterSnapshot ? snapshotToDiffEndpoint(afterSnapshot) : noteToDiffEndpoint(afterNote),
|
|
221
|
+
diff: {
|
|
222
|
+
markdown: diff.markdown,
|
|
223
|
+
changedLineCount: diff.changedLineCount,
|
|
224
|
+
changedCharCount: diff.changedCharCount,
|
|
225
|
+
beforeMarkdownSha256: calculateMarkdownSha256(beforeMarkdown),
|
|
226
|
+
afterMarkdownSha256: calculateMarkdownSha256(afterMarkdown)
|
|
227
|
+
}
|
|
228
|
+
};
|
|
229
|
+
};
|
|
162
230
|
const serializeSnapshotWithContent = async (snapshot) => {
|
|
163
231
|
const contentAsMarkdown = await renderNoteSnapshotContentAsMarkdown(snapshot.payload);
|
|
164
232
|
return {
|
|
@@ -249,6 +317,63 @@ const createNoteSnapshotService = (deps) => ({
|
|
|
249
317
|
}
|
|
250
318
|
return serializeSnapshotWithContent(snapshot);
|
|
251
319
|
},
|
|
320
|
+
diffSnapshot: async (snapshotId, options = {}) => {
|
|
321
|
+
await deps.purgeExpiredSnapshots(createRetentionCutoff(SNAPSHOT_RETENTION_DAYS), RECOVERY_CLEANUP_BATCH_LIMIT);
|
|
322
|
+
const snapshot = await deps.findSnapshotById(snapshotId);
|
|
323
|
+
if (!snapshot) {
|
|
324
|
+
return null;
|
|
325
|
+
}
|
|
326
|
+
if (options.compareToSnapshotId !== void 0) {
|
|
327
|
+
const comparedSnapshot = await deps.findSnapshotById(options.compareToSnapshotId);
|
|
328
|
+
if (!comparedSnapshot || comparedSnapshot.noteId !== snapshot.noteId) {
|
|
329
|
+
return null;
|
|
330
|
+
}
|
|
331
|
+
return buildSnapshotDiff({
|
|
332
|
+
beforeSnapshot: snapshot,
|
|
333
|
+
afterSnapshot: comparedSnapshot,
|
|
334
|
+
contextLines: options.contextLines
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
if (options.target === "previous") {
|
|
338
|
+
const previousSnapshot = deps.findPreviousSnapshot ? await deps.findPreviousSnapshot(snapshot) : null;
|
|
339
|
+
if (!previousSnapshot) {
|
|
340
|
+
return null;
|
|
341
|
+
}
|
|
342
|
+
return buildSnapshotDiff({
|
|
343
|
+
beforeSnapshot: previousSnapshot,
|
|
344
|
+
afterSnapshot: snapshot,
|
|
345
|
+
contextLines: options.contextLines
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
if (options.target === "current") {
|
|
349
|
+
const note2 = await deps.findNoteById(snapshot.noteId);
|
|
350
|
+
if (!note2) {
|
|
351
|
+
return null;
|
|
352
|
+
}
|
|
353
|
+
return buildSnapshotDiff({
|
|
354
|
+
beforeSnapshot: snapshot,
|
|
355
|
+
afterNote: note2,
|
|
356
|
+
contextLines: options.contextLines
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
const nextSnapshot = deps.findNextSnapshot ? await deps.findNextSnapshot(snapshot) : null;
|
|
360
|
+
if (nextSnapshot) {
|
|
361
|
+
return buildSnapshotDiff({
|
|
362
|
+
beforeSnapshot: snapshot,
|
|
363
|
+
afterSnapshot: nextSnapshot,
|
|
364
|
+
contextLines: options.contextLines
|
|
365
|
+
});
|
|
366
|
+
}
|
|
367
|
+
const note = await deps.findNoteById(snapshot.noteId);
|
|
368
|
+
if (!note) {
|
|
369
|
+
return null;
|
|
370
|
+
}
|
|
371
|
+
return buildSnapshotDiff({
|
|
372
|
+
beforeSnapshot: snapshot,
|
|
373
|
+
afterNote: note,
|
|
374
|
+
contextLines: options.contextLines
|
|
375
|
+
});
|
|
376
|
+
},
|
|
252
377
|
restoreSnapshot: async (snapshotId, options) => {
|
|
253
378
|
await deps.purgeExpiredSnapshots(createRetentionCutoff(SNAPSHOT_RETENTION_DAYS), RECOVERY_CLEANUP_BATCH_LIMIT);
|
|
254
379
|
const snapshot = await deps.findSnapshotById(snapshotId);
|
|
@@ -371,6 +496,36 @@ const defaultNoteSnapshotService = createNoteSnapshotService({
|
|
|
371
496
|
findSnapshotById: async (id) => {
|
|
372
497
|
return models.noteSnapshot.findUnique({ where: { id } });
|
|
373
498
|
},
|
|
499
|
+
findNextSnapshot: async (snapshot) => {
|
|
500
|
+
return models.noteSnapshot.findFirst({
|
|
501
|
+
where: {
|
|
502
|
+
noteId: snapshot.noteId,
|
|
503
|
+
OR: [
|
|
504
|
+
{ createdAt: { gt: snapshot.createdAt } },
|
|
505
|
+
{
|
|
506
|
+
createdAt: snapshot.createdAt,
|
|
507
|
+
id: { gt: snapshot.id }
|
|
508
|
+
}
|
|
509
|
+
]
|
|
510
|
+
},
|
|
511
|
+
orderBy: [{ createdAt: "asc" }, { id: "asc" }]
|
|
512
|
+
});
|
|
513
|
+
},
|
|
514
|
+
findPreviousSnapshot: async (snapshot) => {
|
|
515
|
+
return models.noteSnapshot.findFirst({
|
|
516
|
+
where: {
|
|
517
|
+
noteId: snapshot.noteId,
|
|
518
|
+
OR: [
|
|
519
|
+
{ createdAt: { lt: snapshot.createdAt } },
|
|
520
|
+
{
|
|
521
|
+
createdAt: snapshot.createdAt,
|
|
522
|
+
id: { lt: snapshot.id }
|
|
523
|
+
}
|
|
524
|
+
]
|
|
525
|
+
},
|
|
526
|
+
orderBy: [{ createdAt: "desc" }, { id: "desc" }]
|
|
527
|
+
});
|
|
528
|
+
},
|
|
374
529
|
updateNote: async (id, input) => {
|
|
375
530
|
const { tagIds, properties, ...noteInput } = input;
|
|
376
531
|
return models.$transaction(async (tx) => {
|
|
@@ -445,6 +600,9 @@ const listNoteSnapshots = async (noteId, limit) => {
|
|
|
445
600
|
const getNoteSnapshot = async (snapshotId) => {
|
|
446
601
|
return defaultNoteSnapshotService.getSnapshot(snapshotId);
|
|
447
602
|
};
|
|
603
|
+
const diffNoteSnapshot = async (snapshotId, options) => {
|
|
604
|
+
return defaultNoteSnapshotService.diffSnapshot(snapshotId, options);
|
|
605
|
+
};
|
|
448
606
|
const restoreNoteSnapshot = async (snapshotId, options) => {
|
|
449
607
|
return defaultNoteSnapshotService.restoreSnapshot(snapshotId, options);
|
|
450
608
|
};
|
|
@@ -457,6 +615,7 @@ export {
|
|
|
457
615
|
createNoteSnapshotService,
|
|
458
616
|
createSnapshotMetaFromUserAgent,
|
|
459
617
|
defaultNoteSnapshotService,
|
|
618
|
+
diffNoteSnapshot,
|
|
460
619
|
getNoteSnapshot,
|
|
461
620
|
listNoteSnapshots,
|
|
462
621
|
purgeExpiredNoteSnapshots,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/features/note/services/snapshot.ts"],"sourcesContent":["import { ensureTagByName } from '~/features/tag/services/organization.js';\nimport models, { type NoteLayout, type PropertyValueType } from '~/models.js';\nimport { blocksToMarkdown, extractTagIdsFromContentJson } from '~/modules/blocknote.js';\nimport { type BlockNoteTreeNode, parseBlockNoteContent, walkBlockNoteTree } from '~/modules/blocknote-tree.js';\nimport {\n createRetentionCutoff,\n RECOVERY_CLEANUP_BATCH_LIMIT,\n SNAPSHOT_MAX_PER_NOTE,\n SNAPSHOT_RETENTION_DAYS,\n} from '~/modules/recovery-retention.js';\nimport { buildNoteSearchProjection, extractVisibleSearchTextFromContent } from './search.js';\n\nconst SNAPSHOT_CONTENT_PREVIEW_MAX_LENGTH = 240;\n\ninterface NoteRecord {\n id: number;\n title: string;\n content: string;\n pinned: boolean;\n order: number;\n layout: NoteLayout;\n updatedAt?: Date;\n properties?: SnapshotNoteProperty[];\n}\n\ninterface SnapshotNoteProperty {\n key: string;\n name: string;\n valueType: PropertyValueType;\n textValue?: string | null;\n textValueNormalized?: string | null;\n numberValue?: number | null;\n dateValue?: Date | string | null;\n boolValue?: boolean | null;\n optionValue?: string | null;\n optionLabel?: string | null;\n optionColor?: string | null;\n}\n\ninterface NoteSnapshotRecord {\n id: number;\n noteId: number;\n title: string;\n payload: string;\n editSessionId: string | null;\n meta: string | null;\n createdAt: Date;\n}\n\ninterface NoteSnapshotDeps {\n findNoteById: (id: number) => Promise<NoteRecord | null>;\n findSnapshotByEditSessionId: (noteId: number, editSessionId: string) => Promise<NoteSnapshotRecord | null>;\n findLatestSnapshot: (noteId: number) => Promise<NoteSnapshotRecord | null>;\n createSnapshot: (input: {\n noteId: number;\n title: string;\n payload: string;\n editSessionId?: string;\n meta?: string;\n }) => Promise<NoteSnapshotRecord>;\n listSnapshots: (noteId: number, limit: number) => Promise<NoteSnapshotRecord[]>;\n findSnapshotById: (id: number) => Promise<NoteSnapshotRecord | null>;\n purgeExpiredSnapshots: (before: Date, limit: number) => Promise<number>;\n trimOverflowSnapshots: (noteId: number, keep: number, limit: number) => Promise<number>;\n resolveRestoredTags?: (content: string) => Promise<ResolvedRestoredSnapshotTags | null>;\n updateNote: (\n id: number,\n input: {\n title: string;\n content: string;\n pinned: boolean;\n order: number;\n layout: NoteLayout;\n tagIds?: number[];\n properties?: SnapshotNoteProperty[];\n },\n ) => Promise<NoteRecord>;\n}\n\nexport interface NoteSnapshotMeta {\n entrypoint?: 'web' | 'mobile' | 'mcp';\n label?: string;\n}\n\ninterface NoteSnapshotPayload {\n title: string;\n content: string;\n pinned: boolean;\n order: number;\n layout: NoteLayout;\n properties?: SnapshotNoteProperty[];\n}\n\ninterface TagRecord {\n id: number;\n name: string;\n}\n\ninterface ResolvedRestoredSnapshotTags {\n content: string;\n tagIds: number[];\n}\n\ninterface ResolveRestoredSnapshotTagsDeps {\n ensureTagByName: (name: string) => Promise<TagRecord>;\n findTagsByIds: (ids: number[]) => Promise<TagRecord[]>;\n}\n\nexport interface NoteSnapshotSummary {\n id: string;\n title: string;\n createdAt: string;\n meta: NoteSnapshotMeta;\n contentPreview: string;\n contentAsMarkdown?: string;\n payload?: string;\n}\n\nconst parseMeta = (value?: string | null): NoteSnapshotMeta => {\n if (!value) {\n return {};\n }\n\n try {\n const parsed = JSON.parse(value) as NoteSnapshotMeta;\n return {\n ...(parsed.entrypoint ? { entrypoint: parsed.entrypoint } : {}),\n ...(parsed.label ? { label: parsed.label } : {}),\n };\n } catch {\n return {};\n }\n};\n\nconst readSnapshotContent = (payload: string) => {\n try {\n return parsePayload(payload).content;\n } catch {\n return '';\n }\n};\n\nconst buildSnapshotContentPreview = (content: string) => {\n const preview = extractVisibleSearchTextFromContent(content);\n\n if (preview.length <= SNAPSHOT_CONTENT_PREVIEW_MAX_LENGTH) {\n return preview;\n }\n\n return `${preview.slice(0, SNAPSHOT_CONTENT_PREVIEW_MAX_LENGTH).trimEnd()}...`;\n};\n\nconst serializeSnapshot = (snapshot: NoteSnapshotRecord): NoteSnapshotSummary => ({\n id: String(snapshot.id),\n title: snapshot.title,\n contentPreview: buildSnapshotContentPreview(readSnapshotContent(snapshot.payload)),\n createdAt: snapshot.createdAt.toISOString(),\n meta: parseMeta(snapshot.meta),\n payload: snapshot.payload,\n});\n\nconst serializePayload = (note: NoteRecord): string => {\n const payload: NoteSnapshotPayload = {\n title: note.title,\n content: note.content,\n pinned: note.pinned,\n order: note.order,\n layout: note.layout,\n ...(note.properties\n ? {\n properties: note.properties.map((property) => ({\n ...property,\n ...(property.dateValue instanceof Date ? { dateValue: property.dateValue.toISOString() } : {}),\n })),\n }\n : {}),\n };\n\n return JSON.stringify(payload);\n};\n\nconst hasSameSnapshotPayload = (snapshot: NoteSnapshotRecord | null, payload: string) => {\n return snapshot?.payload === payload;\n};\n\nconst parsePayload = (payload: string): NoteSnapshotPayload => {\n const parsed = JSON.parse(payload) as Partial<NoteSnapshotPayload>;\n\n if (\n typeof parsed.title !== 'string' ||\n typeof parsed.content !== 'string' ||\n typeof parsed.pinned !== 'boolean' ||\n typeof parsed.order !== 'number' ||\n (parsed.layout !== 'narrow' && parsed.layout !== 'wide' && parsed.layout !== 'full')\n ) {\n throw new Error('INVALID_NOTE_SNAPSHOT_PAYLOAD');\n }\n\n return parsed as NoteSnapshotPayload;\n};\n\nconst extractRestoredTagIds = (content: string): number[] | null => {\n try {\n return extractTagIdsFromContentJson(content)\n .map(normalizeRestoredTagId)\n .filter((id): id is number => id !== null);\n } catch {\n return null;\n }\n};\n\nconst isRecord = (value: unknown): value is Record<string, unknown> => {\n return typeof value === 'object' && value !== null;\n};\n\nconst normalizeRestoredTagId = (value: unknown) => {\n if (value === undefined || value === null) {\n return null;\n }\n\n const normalized = typeof value === 'string' ? value.trim() : value;\n\n if (normalized === '') {\n return null;\n }\n\n const numericId = Number(normalized);\n return Number.isSafeInteger(numericId) && numericId > 0 ? numericId : null;\n};\n\nconst normalizeRestoredTagName = (value: unknown) => {\n if (typeof value !== 'string') {\n return null;\n }\n\n const normalized = value.trim();\n return normalized ? normalized : null;\n};\n\nexport const resolveRestoredSnapshotTags = async (\n content: string,\n deps: ResolveRestoredSnapshotTagsDeps,\n): Promise<ResolvedRestoredSnapshotTags | null> => {\n const parsed = parseBlockNoteContent(content);\n\n if (!parsed) {\n return null;\n }\n\n const tagRefs: Array<{\n id: number | null;\n name: string | null;\n node: BlockNoteTreeNode;\n props: Record<string, unknown>;\n }> = [];\n\n walkBlockNoteTree(parsed, (node) => {\n if (node.type !== 'tag' || !isRecord(node.props)) {\n return;\n }\n\n tagRefs.push({\n id: normalizeRestoredTagId(node.props.id),\n name: normalizeRestoredTagName(node.props.tag),\n node,\n props: node.props,\n });\n });\n\n if (tagRefs.length === 0) {\n return { content, tagIds: [] };\n }\n\n const candidateIds = Array.from(new Set(tagRefs.map((tag) => tag.id).filter((id): id is number => id !== null)));\n const existingTags = candidateIds.length > 0 ? await deps.findTagsByIds(candidateIds) : [];\n const existingTagIds = new Set(existingTags.map((tag) => tag.id));\n const tagIds = new Set<number>();\n let contentChanged = false;\n\n for (const tagRef of tagRefs) {\n if (tagRef.id !== null && existingTagIds.has(tagRef.id)) {\n tagIds.add(tagRef.id);\n continue;\n }\n\n if (!tagRef.name) {\n continue;\n }\n\n try {\n const resolvedTag = await deps.ensureTagByName(tagRef.name);\n tagRef.node.props = {\n ...tagRef.props,\n id: String(resolvedTag.id),\n tag: resolvedTag.name,\n };\n tagIds.add(resolvedTag.id);\n contentChanged = true;\n } catch {\n continue;\n }\n }\n\n return {\n content: contentChanged ? JSON.stringify(parsed) : content,\n tagIds: [...tagIds],\n };\n};\n\nexport const renderNoteSnapshotContentAsMarkdown = async (payload: string) => {\n try {\n const snapshotPayload = parsePayload(payload);\n return blocksToMarkdown(snapshotPayload.content);\n } catch {\n return '';\n }\n};\n\nconst serializeSnapshotWithContent = async (snapshot: NoteSnapshotRecord): Promise<NoteSnapshotSummary> => {\n const contentAsMarkdown = await renderNoteSnapshotContentAsMarkdown(snapshot.payload);\n\n return {\n ...serializeSnapshot(snapshot),\n contentAsMarkdown,\n };\n};\n\nexport const createSnapshotMetaFromUserAgent = (userAgent?: string | null): string | undefined => {\n const normalized = userAgent?.toLowerCase() ?? '';\n const isMobile = /iphone|ipad|android|mobile/.test(normalized);\n\n return JSON.stringify({\n entrypoint: isMobile ? 'mobile' : 'web',\n label: isMobile ? 'Mobile browser' : 'Web browser',\n } satisfies NoteSnapshotMeta);\n};\n\nexport const MCP_SNAPSHOT_META = JSON.stringify({\n entrypoint: 'mcp',\n label: 'MCP',\n} satisfies NoteSnapshotMeta);\n\nconst defaultPurgeExpiredSnapshots = async (before: Date, limit: number) => {\n const expiredSnapshots = await models.noteSnapshot.findMany({\n where: { createdAt: { lt: before } },\n orderBy: { createdAt: 'asc' },\n take: limit,\n select: { id: true },\n });\n\n if (expiredSnapshots.length === 0) {\n return 0;\n }\n\n const deleted = await models.noteSnapshot.deleteMany({\n where: { id: { in: expiredSnapshots.map((snapshot) => snapshot.id) } },\n });\n\n return deleted.count;\n};\n\nconst defaultTrimOverflowSnapshots = async (noteId: number, keep: number, limit: number) => {\n const overflowSnapshots = await models.noteSnapshot.findMany({\n where: { noteId },\n orderBy: [{ createdAt: 'desc' }, { id: 'desc' }],\n skip: keep,\n take: limit,\n select: { id: true },\n });\n\n if (overflowSnapshots.length === 0) {\n return 0;\n }\n\n const deleted = await models.noteSnapshot.deleteMany({\n where: { id: { in: overflowSnapshots.map((snapshot) => snapshot.id) } },\n });\n\n return deleted.count;\n};\n\nexport const createNoteSnapshotService = (deps: NoteSnapshotDeps) => ({\n captureBaseline: async (input: {\n noteId: number;\n editSessionId?: string;\n meta?: string;\n baseline?: NoteRecord;\n force?: boolean;\n }) => {\n await deps.purgeExpiredSnapshots(createRetentionCutoff(SNAPSHOT_RETENTION_DAYS), RECOVERY_CLEANUP_BATCH_LIMIT);\n\n const existing =\n input.editSessionId && !input.force\n ? await deps.findSnapshotByEditSessionId(input.noteId, input.editSessionId)\n : null;\n\n if (existing) {\n return serializeSnapshot(existing);\n }\n\n const note = input.baseline ?? (await deps.findNoteById(input.noteId));\n\n if (!note) {\n return null;\n }\n\n const payload = serializePayload(note);\n const latestSnapshot = await deps.findLatestSnapshot(note.id);\n\n if (latestSnapshot && hasSameSnapshotPayload(latestSnapshot, payload)) {\n return serializeSnapshot(latestSnapshot);\n }\n\n const snapshot = await deps.createSnapshot({\n noteId: note.id,\n title: note.title,\n payload,\n ...(input.editSessionId && !input.force ? { editSessionId: input.editSessionId } : {}),\n ...(input.meta ? { meta: input.meta } : {}),\n });\n\n await deps.trimOverflowSnapshots(note.id, SNAPSHOT_MAX_PER_NOTE, RECOVERY_CLEANUP_BATCH_LIMIT);\n\n return serializeSnapshot(snapshot);\n },\n\n listSnapshots: async (noteId: number, limit = SNAPSHOT_MAX_PER_NOTE) => {\n await deps.purgeExpiredSnapshots(createRetentionCutoff(SNAPSHOT_RETENTION_DAYS), RECOVERY_CLEANUP_BATCH_LIMIT);\n await deps.trimOverflowSnapshots(noteId, SNAPSHOT_MAX_PER_NOTE, RECOVERY_CLEANUP_BATCH_LIMIT);\n const snapshots = await deps.listSnapshots(noteId, limit);\n return snapshots.map(serializeSnapshot);\n },\n\n getSnapshot: async (snapshotId: number) => {\n await deps.purgeExpiredSnapshots(createRetentionCutoff(SNAPSHOT_RETENTION_DAYS), RECOVERY_CLEANUP_BATCH_LIMIT);\n const snapshot = await deps.findSnapshotById(snapshotId);\n\n if (!snapshot) {\n return null;\n }\n\n return serializeSnapshotWithContent(snapshot);\n },\n\n restoreSnapshot: async (snapshotId: number, options?: { meta?: string }) => {\n await deps.purgeExpiredSnapshots(createRetentionCutoff(SNAPSHOT_RETENTION_DAYS), RECOVERY_CLEANUP_BATCH_LIMIT);\n\n const snapshot = await deps.findSnapshotById(snapshotId);\n\n if (!snapshot) {\n return null;\n }\n\n const note = await deps.findNoteById(snapshot.noteId);\n\n if (!note) {\n return null;\n }\n\n const payload = parsePayload(snapshot.payload);\n const resolvedTags = deps.resolveRestoredTags\n ? await deps.resolveRestoredTags(payload.content)\n : extractRestoredTagIds(payload.content);\n const restoredPayload =\n resolvedTags && !Array.isArray(resolvedTags)\n ? {\n ...payload,\n content: resolvedTags.content,\n }\n : payload;\n const restoredTagIds = Array.isArray(resolvedTags) ? resolvedTags : resolvedTags?.tagIds;\n\n const currentPayload = serializePayload(note);\n const latestSnapshot = await deps.findLatestSnapshot(note.id);\n\n if (!hasSameSnapshotPayload(latestSnapshot, currentPayload)) {\n await deps.createSnapshot({\n noteId: note.id,\n title: note.title,\n payload: currentPayload,\n ...(options?.meta ? { meta: options.meta } : {}),\n });\n }\n\n const restoredNote = await deps.updateNote(snapshot.noteId, {\n ...restoredPayload,\n ...(restoredTagIds !== undefined ? { tagIds: restoredTagIds } : {}),\n });\n\n await deps.trimOverflowSnapshots(note.id, SNAPSHOT_MAX_PER_NOTE, RECOVERY_CLEANUP_BATCH_LIMIT);\n\n return restoredNote;\n },\n});\n\nexport const defaultNoteSnapshotService = createNoteSnapshotService({\n findNoteById: async (id) => {\n const note = await models.note.findUnique({\n where: { id },\n include: {\n properties: {\n include: { definition: true, option: true },\n },\n },\n });\n\n if (!note) {\n return null;\n }\n\n return {\n ...note,\n properties: note.properties.map((property) => ({\n key: property.definition.key,\n name: property.definition.name,\n valueType: property.definition.valueType,\n textValue: property.textValue,\n textValueNormalized: property.textValueNormalized,\n numberValue: property.numberValue,\n dateValue: property.dateValue,\n boolValue: property.boolValue,\n optionValue: property.option?.value ?? null,\n optionLabel: property.option?.label ?? null,\n optionColor: property.option?.color ?? null,\n })),\n };\n },\n findSnapshotByEditSessionId: async (noteId, editSessionId) => {\n return models.noteSnapshot.findFirst({\n where: {\n noteId,\n editSessionId,\n },\n });\n },\n findLatestSnapshot: async (noteId) => {\n return models.noteSnapshot.findFirst({\n where: { noteId },\n orderBy: [{ createdAt: 'desc' }, { id: 'desc' }],\n });\n },\n createSnapshot: async (input) => {\n return models.noteSnapshot.create({\n data: {\n noteId: input.noteId,\n title: input.title,\n payload: input.payload,\n ...(input.editSessionId ? { editSessionId: input.editSessionId } : {}),\n ...(input.meta ? { meta: input.meta } : {}),\n },\n });\n },\n purgeExpiredSnapshots: defaultPurgeExpiredSnapshots,\n trimOverflowSnapshots: defaultTrimOverflowSnapshots,\n resolveRestoredTags: async (content) => {\n return resolveRestoredSnapshotTags(content, {\n ensureTagByName: async (name) => {\n const result = await ensureTagByName(name);\n return {\n id: Number(result.tag.id),\n name: result.tag.name,\n };\n },\n findTagsByIds: async (ids) => {\n return models.tag.findMany({\n where: { id: { in: ids } },\n select: {\n id: true,\n name: true,\n },\n });\n },\n });\n },\n listSnapshots: async (noteId, limit) => {\n return models.noteSnapshot.findMany({\n where: { noteId },\n orderBy: [{ createdAt: 'desc' }, { id: 'desc' }],\n take: limit,\n });\n },\n findSnapshotById: async (id) => {\n return models.noteSnapshot.findUnique({ where: { id } });\n },\n updateNote: async (id, input) => {\n const { tagIds, properties, ...noteInput } = input;\n\n return models.$transaction(async (tx) => {\n const note = await tx.note.update({\n where: { id },\n data: {\n ...noteInput,\n ...buildNoteSearchProjection({\n title: noteInput.title,\n content: noteInput.content,\n }),\n ...(tagIds !== undefined ? { tags: { set: tagIds.map((tagId) => ({ id: tagId })) } } : {}),\n },\n });\n\n if (properties !== undefined) {\n await tx.noteProperty.deleteMany({ where: { noteId: id } });\n\n for (const property of properties) {\n const definition = await tx.propertyDefinition.upsert({\n where: { key: property.key },\n create: {\n key: property.key,\n name: property.name,\n valueType: property.valueType,\n },\n update: {\n name: property.name,\n valueType: property.valueType,\n },\n });\n\n const option =\n property.valueType === 'select' && property.optionValue\n ? await tx.propertyOption.upsert({\n where: {\n propertyDefinitionId_value: {\n propertyDefinitionId: definition.id,\n value: property.optionValue,\n },\n },\n create: {\n propertyDefinitionId: definition.id,\n value: property.optionValue,\n label: property.optionLabel ?? property.optionValue,\n color: property.optionColor ?? null,\n },\n update: {\n label: property.optionLabel ?? property.optionValue,\n color: property.optionColor ?? null,\n },\n })\n : null;\n\n await tx.noteProperty.create({\n data: {\n noteId: id,\n propertyDefinitionId: definition.id,\n optionId: option?.id ?? null,\n textValue: property.textValue ?? null,\n textValueNormalized: property.textValueNormalized ?? null,\n numberValue: property.numberValue ?? null,\n dateValue: property.dateValue ? new Date(property.dateValue) : null,\n boolValue: property.boolValue ?? null,\n },\n });\n }\n }\n\n return note;\n });\n },\n});\n\nexport const captureNoteBaseline = async (input: {\n noteId: number;\n editSessionId?: string;\n meta?: string;\n baseline?: NoteRecord;\n force?: boolean;\n}) => {\n return defaultNoteSnapshotService.captureBaseline(input);\n};\n\nexport const listNoteSnapshots = async (noteId: number, limit?: number) => {\n return defaultNoteSnapshotService.listSnapshots(noteId, limit);\n};\n\nexport const getNoteSnapshot = async (snapshotId: number) => {\n return defaultNoteSnapshotService.getSnapshot(snapshotId);\n};\n\nexport const restoreNoteSnapshot = async (snapshotId: number, options?: { meta?: string }) => {\n return defaultNoteSnapshotService.restoreSnapshot(snapshotId, options);\n};\n\nexport const purgeExpiredNoteSnapshots = async () => {\n return defaultPurgeExpiredSnapshots(createRetentionCutoff(SNAPSHOT_RETENTION_DAYS), RECOVERY_CLEANUP_BATCH_LIMIT);\n};\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../../src/features/note/services/snapshot.ts"],"sourcesContent":["import { FILE_HEADERS_ONLY, formatPatch, structuredPatch } from 'diff';\nimport { ensureTagByName } from '~/features/tag/services/organization.js';\nimport models, { type NoteLayout, type PropertyValueType } from '~/models.js';\nimport { blocksToMarkdown, extractTagIdsFromContentJson } from '~/modules/blocknote.js';\nimport { type BlockNoteTreeNode, parseBlockNoteContent, walkBlockNoteTree } from '~/modules/blocknote-tree.js';\nimport {\n createRetentionCutoff,\n RECOVERY_CLEANUP_BATCH_LIMIT,\n SNAPSHOT_MAX_PER_NOTE,\n SNAPSHOT_RETENTION_DAYS,\n} from '~/modules/recovery-retention.js';\nimport { calculateMarkdownSha256 } from './markdown-patch.js';\nimport { buildNoteSearchProjection, extractVisibleSearchTextFromContent } from './search.js';\n\nconst SNAPSHOT_CONTENT_PREVIEW_MAX_LENGTH = 240;\n\ninterface NoteRecord {\n id: number;\n title: string;\n content: string;\n pinned: boolean;\n order: number;\n layout: NoteLayout;\n updatedAt?: Date;\n properties?: SnapshotNoteProperty[];\n}\n\ninterface SnapshotNoteProperty {\n key: string;\n name: string;\n valueType: PropertyValueType;\n textValue?: string | null;\n textValueNormalized?: string | null;\n numberValue?: number | null;\n dateValue?: Date | string | null;\n boolValue?: boolean | null;\n optionValue?: string | null;\n optionLabel?: string | null;\n optionColor?: string | null;\n}\n\ninterface NoteSnapshotRecord {\n id: number;\n noteId: number;\n title: string;\n payload: string;\n editSessionId: string | null;\n meta: string | null;\n createdAt: Date;\n}\n\ninterface NoteSnapshotDeps {\n findNoteById: (id: number) => Promise<NoteRecord | null>;\n findSnapshotByEditSessionId: (noteId: number, editSessionId: string) => Promise<NoteSnapshotRecord | null>;\n findLatestSnapshot: (noteId: number) => Promise<NoteSnapshotRecord | null>;\n createSnapshot: (input: {\n noteId: number;\n title: string;\n payload: string;\n editSessionId?: string;\n meta?: string;\n }) => Promise<NoteSnapshotRecord>;\n listSnapshots: (noteId: number, limit: number) => Promise<NoteSnapshotRecord[]>;\n findSnapshotById: (id: number) => Promise<NoteSnapshotRecord | null>;\n findNextSnapshot?: (snapshot: NoteSnapshotRecord) => Promise<NoteSnapshotRecord | null>;\n findPreviousSnapshot?: (snapshot: NoteSnapshotRecord) => Promise<NoteSnapshotRecord | null>;\n purgeExpiredSnapshots: (before: Date, limit: number) => Promise<number>;\n trimOverflowSnapshots: (noteId: number, keep: number, limit: number) => Promise<number>;\n resolveRestoredTags?: (content: string) => Promise<ResolvedRestoredSnapshotTags | null>;\n updateNote: (\n id: number,\n input: {\n title: string;\n content: string;\n pinned: boolean;\n order: number;\n layout: NoteLayout;\n tagIds?: number[];\n properties?: SnapshotNoteProperty[];\n },\n ) => Promise<NoteRecord>;\n}\n\nexport interface NoteSnapshotMeta {\n entrypoint?: 'web' | 'mobile' | 'mcp';\n label?: string;\n}\n\ninterface NoteSnapshotPayload {\n title: string;\n content: string;\n pinned: boolean;\n order: number;\n layout: NoteLayout;\n properties?: SnapshotNoteProperty[];\n}\n\ninterface TagRecord {\n id: number;\n name: string;\n}\n\ninterface ResolvedRestoredSnapshotTags {\n content: string;\n tagIds: number[];\n}\n\ninterface ResolveRestoredSnapshotTagsDeps {\n ensureTagByName: (name: string) => Promise<TagRecord>;\n findTagsByIds: (ids: number[]) => Promise<TagRecord[]>;\n}\n\nexport interface NoteSnapshotSummary {\n id: string;\n title: string;\n createdAt: string;\n meta: NoteSnapshotMeta;\n contentPreview: string;\n contentAsMarkdown?: string;\n payload?: string;\n}\n\nexport type NoteSnapshotDiffTarget = 'next' | 'previous' | 'current';\n\nexport interface NoteSnapshotDiffEndpoint {\n kind: 'snapshot' | 'current_note';\n id: string;\n title: string;\n createdAt?: string;\n updatedAt?: string;\n meta?: NoteSnapshotMeta;\n}\n\nexport interface NoteSnapshotDiffResult {\n noteId: string;\n mode: 'snapshot_to_snapshot' | 'snapshot_to_current';\n before: NoteSnapshotDiffEndpoint;\n after: NoteSnapshotDiffEndpoint;\n diff: {\n markdown: string;\n changedLineCount: number;\n changedCharCount: number;\n beforeMarkdownSha256: string;\n afterMarkdownSha256: string;\n };\n}\n\nconst parseMeta = (value?: string | null): NoteSnapshotMeta => {\n if (!value) {\n return {};\n }\n\n try {\n const parsed = JSON.parse(value) as NoteSnapshotMeta;\n return {\n ...(parsed.entrypoint ? { entrypoint: parsed.entrypoint } : {}),\n ...(parsed.label ? { label: parsed.label } : {}),\n };\n } catch {\n return {};\n }\n};\n\nconst readSnapshotContent = (payload: string) => {\n try {\n return parsePayload(payload).content;\n } catch {\n return '';\n }\n};\n\nconst readSnapshotTitle = (payload: string, fallback: string) => {\n try {\n return parsePayload(payload).title;\n } catch {\n return fallback;\n }\n};\n\nconst buildSnapshotContentPreview = (content: string) => {\n const preview = extractVisibleSearchTextFromContent(content);\n\n if (preview.length <= SNAPSHOT_CONTENT_PREVIEW_MAX_LENGTH) {\n return preview;\n }\n\n return `${preview.slice(0, SNAPSHOT_CONTENT_PREVIEW_MAX_LENGTH).trimEnd()}...`;\n};\n\nconst serializeSnapshot = (snapshot: NoteSnapshotRecord): NoteSnapshotSummary => ({\n id: String(snapshot.id),\n title: snapshot.title,\n contentPreview: buildSnapshotContentPreview(readSnapshotContent(snapshot.payload)),\n createdAt: snapshot.createdAt.toISOString(),\n meta: parseMeta(snapshot.meta),\n payload: snapshot.payload,\n});\n\nconst serializePayload = (note: NoteRecord): string => {\n const payload: NoteSnapshotPayload = {\n title: note.title,\n content: note.content,\n pinned: note.pinned,\n order: note.order,\n layout: note.layout,\n ...(note.properties\n ? {\n properties: note.properties.map((property) => ({\n ...property,\n ...(property.dateValue instanceof Date ? { dateValue: property.dateValue.toISOString() } : {}),\n })),\n }\n : {}),\n };\n\n return JSON.stringify(payload);\n};\n\nconst hasSameSnapshotPayload = (snapshot: NoteSnapshotRecord | null, payload: string) => {\n return snapshot?.payload === payload;\n};\n\nconst parsePayload = (payload: string): NoteSnapshotPayload => {\n const parsed = JSON.parse(payload) as Partial<NoteSnapshotPayload>;\n\n if (\n typeof parsed.title !== 'string' ||\n typeof parsed.content !== 'string' ||\n typeof parsed.pinned !== 'boolean' ||\n typeof parsed.order !== 'number' ||\n (parsed.layout !== 'narrow' && parsed.layout !== 'wide' && parsed.layout !== 'full')\n ) {\n throw new Error('INVALID_NOTE_SNAPSHOT_PAYLOAD');\n }\n\n return parsed as NoteSnapshotPayload;\n};\n\nconst extractRestoredTagIds = (content: string): number[] | null => {\n try {\n return extractTagIdsFromContentJson(content)\n .map(normalizeRestoredTagId)\n .filter((id): id is number => id !== null);\n } catch {\n return null;\n }\n};\n\nconst isRecord = (value: unknown): value is Record<string, unknown> => {\n return typeof value === 'object' && value !== null;\n};\n\nconst normalizeRestoredTagId = (value: unknown) => {\n if (value === undefined || value === null) {\n return null;\n }\n\n const normalized = typeof value === 'string' ? value.trim() : value;\n\n if (normalized === '') {\n return null;\n }\n\n const numericId = Number(normalized);\n return Number.isSafeInteger(numericId) && numericId > 0 ? numericId : null;\n};\n\nconst normalizeRestoredTagName = (value: unknown) => {\n if (typeof value !== 'string') {\n return null;\n }\n\n const normalized = value.trim();\n return normalized ? normalized : null;\n};\n\nexport const resolveRestoredSnapshotTags = async (\n content: string,\n deps: ResolveRestoredSnapshotTagsDeps,\n): Promise<ResolvedRestoredSnapshotTags | null> => {\n const parsed = parseBlockNoteContent(content);\n\n if (!parsed) {\n return null;\n }\n\n const tagRefs: Array<{\n id: number | null;\n name: string | null;\n node: BlockNoteTreeNode;\n props: Record<string, unknown>;\n }> = [];\n\n walkBlockNoteTree(parsed, (node) => {\n if (node.type !== 'tag' || !isRecord(node.props)) {\n return;\n }\n\n tagRefs.push({\n id: normalizeRestoredTagId(node.props.id),\n name: normalizeRestoredTagName(node.props.tag),\n node,\n props: node.props,\n });\n });\n\n if (tagRefs.length === 0) {\n return { content, tagIds: [] };\n }\n\n const candidateIds = Array.from(new Set(tagRefs.map((tag) => tag.id).filter((id): id is number => id !== null)));\n const existingTags = candidateIds.length > 0 ? await deps.findTagsByIds(candidateIds) : [];\n const existingTagIds = new Set(existingTags.map((tag) => tag.id));\n const tagIds = new Set<number>();\n let contentChanged = false;\n\n for (const tagRef of tagRefs) {\n if (tagRef.id !== null && existingTagIds.has(tagRef.id)) {\n tagIds.add(tagRef.id);\n continue;\n }\n\n if (!tagRef.name) {\n continue;\n }\n\n try {\n const resolvedTag = await deps.ensureTagByName(tagRef.name);\n tagRef.node.props = {\n ...tagRef.props,\n id: String(resolvedTag.id),\n tag: resolvedTag.name,\n };\n tagIds.add(resolvedTag.id);\n contentChanged = true;\n } catch {\n continue;\n }\n }\n\n return {\n content: contentChanged ? JSON.stringify(parsed) : content,\n tagIds: [...tagIds],\n };\n};\n\nexport const renderNoteSnapshotContentAsMarkdown = async (payload: string) => {\n try {\n const snapshotPayload = parsePayload(payload);\n return blocksToMarkdown(snapshotPayload.content);\n } catch {\n return '';\n }\n};\n\nconst renderNoteRecordContentAsMarkdown = async (note: NoteRecord) => {\n try {\n return blocksToMarkdown(note.content);\n } catch {\n return '';\n }\n};\n\nconst createSnapshotMarkdownDiff = (beforeMarkdown: string, afterMarkdown: string, contextLines: number | null = 3) => {\n const patch = structuredPatch('before.md', 'after.md', beforeMarkdown, afterMarkdown, undefined, undefined, {\n context: contextLines === null ? Number.MAX_SAFE_INTEGER : contextLines,\n });\n const changedLines = patch.hunks.flatMap((hunk) =>\n hunk.lines.filter((line) => (line.startsWith('+') && !line.startsWith('+++')) || line.startsWith('-')),\n );\n\n return {\n markdown: formatPatch(patch, FILE_HEADERS_ONLY).trimEnd(),\n changedLineCount: changedLines.length,\n changedCharCount: changedLines.reduce((total, line) => total + line.slice(1).length, 0),\n };\n};\n\nconst snapshotToDiffEndpoint = (snapshot: NoteSnapshotRecord): NoteSnapshotDiffEndpoint => ({\n kind: 'snapshot',\n id: String(snapshot.id),\n title: readSnapshotTitle(snapshot.payload, snapshot.title),\n createdAt: snapshot.createdAt.toISOString(),\n meta: parseMeta(snapshot.meta),\n});\n\nconst noteToDiffEndpoint = (note: NoteRecord): NoteSnapshotDiffEndpoint => ({\n kind: 'current_note',\n id: String(note.id),\n title: note.title,\n ...(note.updatedAt ? { updatedAt: note.updatedAt.toISOString() } : {}),\n});\n\nconst buildSnapshotDiff = async ({\n beforeSnapshot,\n afterSnapshot,\n afterNote,\n contextLines,\n}: {\n beforeSnapshot: NoteSnapshotRecord;\n afterSnapshot?: NoteSnapshotRecord;\n afterNote?: NoteRecord;\n contextLines?: number | null;\n}): Promise<NoteSnapshotDiffResult | null> => {\n if (!afterSnapshot && !afterNote) {\n return null;\n }\n\n const beforeMarkdown = await renderNoteSnapshotContentAsMarkdown(beforeSnapshot.payload);\n const afterMarkdown = afterSnapshot\n ? await renderNoteSnapshotContentAsMarkdown(afterSnapshot.payload)\n : afterNote\n ? await renderNoteRecordContentAsMarkdown(afterNote)\n : '';\n const diff = createSnapshotMarkdownDiff(beforeMarkdown, afterMarkdown, contextLines ?? 3);\n\n return {\n noteId: String(beforeSnapshot.noteId),\n mode: afterSnapshot ? 'snapshot_to_snapshot' : 'snapshot_to_current',\n before: snapshotToDiffEndpoint(beforeSnapshot),\n after: afterSnapshot ? snapshotToDiffEndpoint(afterSnapshot) : noteToDiffEndpoint(afterNote as NoteRecord),\n diff: {\n markdown: diff.markdown,\n changedLineCount: diff.changedLineCount,\n changedCharCount: diff.changedCharCount,\n beforeMarkdownSha256: calculateMarkdownSha256(beforeMarkdown),\n afterMarkdownSha256: calculateMarkdownSha256(afterMarkdown),\n },\n };\n};\n\nconst serializeSnapshotWithContent = async (snapshot: NoteSnapshotRecord): Promise<NoteSnapshotSummary> => {\n const contentAsMarkdown = await renderNoteSnapshotContentAsMarkdown(snapshot.payload);\n\n return {\n ...serializeSnapshot(snapshot),\n contentAsMarkdown,\n };\n};\n\nexport const createSnapshotMetaFromUserAgent = (userAgent?: string | null): string | undefined => {\n const normalized = userAgent?.toLowerCase() ?? '';\n const isMobile = /iphone|ipad|android|mobile/.test(normalized);\n\n return JSON.stringify({\n entrypoint: isMobile ? 'mobile' : 'web',\n label: isMobile ? 'Mobile browser' : 'Web browser',\n } satisfies NoteSnapshotMeta);\n};\n\nexport const MCP_SNAPSHOT_META = JSON.stringify({\n entrypoint: 'mcp',\n label: 'MCP',\n} satisfies NoteSnapshotMeta);\n\nconst defaultPurgeExpiredSnapshots = async (before: Date, limit: number) => {\n const expiredSnapshots = await models.noteSnapshot.findMany({\n where: { createdAt: { lt: before } },\n orderBy: { createdAt: 'asc' },\n take: limit,\n select: { id: true },\n });\n\n if (expiredSnapshots.length === 0) {\n return 0;\n }\n\n const deleted = await models.noteSnapshot.deleteMany({\n where: { id: { in: expiredSnapshots.map((snapshot) => snapshot.id) } },\n });\n\n return deleted.count;\n};\n\nconst defaultTrimOverflowSnapshots = async (noteId: number, keep: number, limit: number) => {\n const overflowSnapshots = await models.noteSnapshot.findMany({\n where: { noteId },\n orderBy: [{ createdAt: 'desc' }, { id: 'desc' }],\n skip: keep,\n take: limit,\n select: { id: true },\n });\n\n if (overflowSnapshots.length === 0) {\n return 0;\n }\n\n const deleted = await models.noteSnapshot.deleteMany({\n where: { id: { in: overflowSnapshots.map((snapshot) => snapshot.id) } },\n });\n\n return deleted.count;\n};\n\nexport const createNoteSnapshotService = (deps: NoteSnapshotDeps) => ({\n captureBaseline: async (input: {\n noteId: number;\n editSessionId?: string;\n meta?: string;\n baseline?: NoteRecord;\n force?: boolean;\n }) => {\n await deps.purgeExpiredSnapshots(createRetentionCutoff(SNAPSHOT_RETENTION_DAYS), RECOVERY_CLEANUP_BATCH_LIMIT);\n\n const existing =\n input.editSessionId && !input.force\n ? await deps.findSnapshotByEditSessionId(input.noteId, input.editSessionId)\n : null;\n\n if (existing) {\n return serializeSnapshot(existing);\n }\n\n const note = input.baseline ?? (await deps.findNoteById(input.noteId));\n\n if (!note) {\n return null;\n }\n\n const payload = serializePayload(note);\n const latestSnapshot = await deps.findLatestSnapshot(note.id);\n\n if (latestSnapshot && hasSameSnapshotPayload(latestSnapshot, payload)) {\n return serializeSnapshot(latestSnapshot);\n }\n\n const snapshot = await deps.createSnapshot({\n noteId: note.id,\n title: note.title,\n payload,\n ...(input.editSessionId && !input.force ? { editSessionId: input.editSessionId } : {}),\n ...(input.meta ? { meta: input.meta } : {}),\n });\n\n await deps.trimOverflowSnapshots(note.id, SNAPSHOT_MAX_PER_NOTE, RECOVERY_CLEANUP_BATCH_LIMIT);\n\n return serializeSnapshot(snapshot);\n },\n\n listSnapshots: async (noteId: number, limit = SNAPSHOT_MAX_PER_NOTE) => {\n await deps.purgeExpiredSnapshots(createRetentionCutoff(SNAPSHOT_RETENTION_DAYS), RECOVERY_CLEANUP_BATCH_LIMIT);\n await deps.trimOverflowSnapshots(noteId, SNAPSHOT_MAX_PER_NOTE, RECOVERY_CLEANUP_BATCH_LIMIT);\n const snapshots = await deps.listSnapshots(noteId, limit);\n return snapshots.map(serializeSnapshot);\n },\n\n getSnapshot: async (snapshotId: number) => {\n await deps.purgeExpiredSnapshots(createRetentionCutoff(SNAPSHOT_RETENTION_DAYS), RECOVERY_CLEANUP_BATCH_LIMIT);\n const snapshot = await deps.findSnapshotById(snapshotId);\n\n if (!snapshot) {\n return null;\n }\n\n return serializeSnapshotWithContent(snapshot);\n },\n\n diffSnapshot: async (\n snapshotId: number,\n options: {\n compareToSnapshotId?: number;\n target?: NoteSnapshotDiffTarget;\n contextLines?: number | null;\n } = {},\n ) => {\n await deps.purgeExpiredSnapshots(createRetentionCutoff(SNAPSHOT_RETENTION_DAYS), RECOVERY_CLEANUP_BATCH_LIMIT);\n const snapshot = await deps.findSnapshotById(snapshotId);\n\n if (!snapshot) {\n return null;\n }\n\n if (options.compareToSnapshotId !== undefined) {\n const comparedSnapshot = await deps.findSnapshotById(options.compareToSnapshotId);\n\n if (!comparedSnapshot || comparedSnapshot.noteId !== snapshot.noteId) {\n return null;\n }\n\n return buildSnapshotDiff({\n beforeSnapshot: snapshot,\n afterSnapshot: comparedSnapshot,\n contextLines: options.contextLines,\n });\n }\n\n if (options.target === 'previous') {\n const previousSnapshot = deps.findPreviousSnapshot ? await deps.findPreviousSnapshot(snapshot) : null;\n\n if (!previousSnapshot) {\n return null;\n }\n\n return buildSnapshotDiff({\n beforeSnapshot: previousSnapshot,\n afterSnapshot: snapshot,\n contextLines: options.contextLines,\n });\n }\n\n if (options.target === 'current') {\n const note = await deps.findNoteById(snapshot.noteId);\n\n if (!note) {\n return null;\n }\n\n return buildSnapshotDiff({\n beforeSnapshot: snapshot,\n afterNote: note,\n contextLines: options.contextLines,\n });\n }\n\n const nextSnapshot = deps.findNextSnapshot ? await deps.findNextSnapshot(snapshot) : null;\n\n if (nextSnapshot) {\n return buildSnapshotDiff({\n beforeSnapshot: snapshot,\n afterSnapshot: nextSnapshot,\n contextLines: options.contextLines,\n });\n }\n\n const note = await deps.findNoteById(snapshot.noteId);\n\n if (!note) {\n return null;\n }\n\n return buildSnapshotDiff({\n beforeSnapshot: snapshot,\n afterNote: note,\n contextLines: options.contextLines,\n });\n },\n\n restoreSnapshot: async (snapshotId: number, options?: { meta?: string }) => {\n await deps.purgeExpiredSnapshots(createRetentionCutoff(SNAPSHOT_RETENTION_DAYS), RECOVERY_CLEANUP_BATCH_LIMIT);\n\n const snapshot = await deps.findSnapshotById(snapshotId);\n\n if (!snapshot) {\n return null;\n }\n\n const note = await deps.findNoteById(snapshot.noteId);\n\n if (!note) {\n return null;\n }\n\n const payload = parsePayload(snapshot.payload);\n const resolvedTags = deps.resolveRestoredTags\n ? await deps.resolveRestoredTags(payload.content)\n : extractRestoredTagIds(payload.content);\n const restoredPayload =\n resolvedTags && !Array.isArray(resolvedTags)\n ? {\n ...payload,\n content: resolvedTags.content,\n }\n : payload;\n const restoredTagIds = Array.isArray(resolvedTags) ? resolvedTags : resolvedTags?.tagIds;\n\n const currentPayload = serializePayload(note);\n const latestSnapshot = await deps.findLatestSnapshot(note.id);\n\n if (!hasSameSnapshotPayload(latestSnapshot, currentPayload)) {\n await deps.createSnapshot({\n noteId: note.id,\n title: note.title,\n payload: currentPayload,\n ...(options?.meta ? { meta: options.meta } : {}),\n });\n }\n\n const restoredNote = await deps.updateNote(snapshot.noteId, {\n ...restoredPayload,\n ...(restoredTagIds !== undefined ? { tagIds: restoredTagIds } : {}),\n });\n\n await deps.trimOverflowSnapshots(note.id, SNAPSHOT_MAX_PER_NOTE, RECOVERY_CLEANUP_BATCH_LIMIT);\n\n return restoredNote;\n },\n});\n\nexport const defaultNoteSnapshotService = createNoteSnapshotService({\n findNoteById: async (id) => {\n const note = await models.note.findUnique({\n where: { id },\n include: {\n properties: {\n include: { definition: true, option: true },\n },\n },\n });\n\n if (!note) {\n return null;\n }\n\n return {\n ...note,\n properties: note.properties.map((property) => ({\n key: property.definition.key,\n name: property.definition.name,\n valueType: property.definition.valueType,\n textValue: property.textValue,\n textValueNormalized: property.textValueNormalized,\n numberValue: property.numberValue,\n dateValue: property.dateValue,\n boolValue: property.boolValue,\n optionValue: property.option?.value ?? null,\n optionLabel: property.option?.label ?? null,\n optionColor: property.option?.color ?? null,\n })),\n };\n },\n findSnapshotByEditSessionId: async (noteId, editSessionId) => {\n return models.noteSnapshot.findFirst({\n where: {\n noteId,\n editSessionId,\n },\n });\n },\n findLatestSnapshot: async (noteId) => {\n return models.noteSnapshot.findFirst({\n where: { noteId },\n orderBy: [{ createdAt: 'desc' }, { id: 'desc' }],\n });\n },\n createSnapshot: async (input) => {\n return models.noteSnapshot.create({\n data: {\n noteId: input.noteId,\n title: input.title,\n payload: input.payload,\n ...(input.editSessionId ? { editSessionId: input.editSessionId } : {}),\n ...(input.meta ? { meta: input.meta } : {}),\n },\n });\n },\n purgeExpiredSnapshots: defaultPurgeExpiredSnapshots,\n trimOverflowSnapshots: defaultTrimOverflowSnapshots,\n resolveRestoredTags: async (content) => {\n return resolveRestoredSnapshotTags(content, {\n ensureTagByName: async (name) => {\n const result = await ensureTagByName(name);\n return {\n id: Number(result.tag.id),\n name: result.tag.name,\n };\n },\n findTagsByIds: async (ids) => {\n return models.tag.findMany({\n where: { id: { in: ids } },\n select: {\n id: true,\n name: true,\n },\n });\n },\n });\n },\n listSnapshots: async (noteId, limit) => {\n return models.noteSnapshot.findMany({\n where: { noteId },\n orderBy: [{ createdAt: 'desc' }, { id: 'desc' }],\n take: limit,\n });\n },\n findSnapshotById: async (id) => {\n return models.noteSnapshot.findUnique({ where: { id } });\n },\n findNextSnapshot: async (snapshot) => {\n return models.noteSnapshot.findFirst({\n where: {\n noteId: snapshot.noteId,\n OR: [\n { createdAt: { gt: snapshot.createdAt } },\n {\n createdAt: snapshot.createdAt,\n id: { gt: snapshot.id },\n },\n ],\n },\n orderBy: [{ createdAt: 'asc' }, { id: 'asc' }],\n });\n },\n findPreviousSnapshot: async (snapshot) => {\n return models.noteSnapshot.findFirst({\n where: {\n noteId: snapshot.noteId,\n OR: [\n { createdAt: { lt: snapshot.createdAt } },\n {\n createdAt: snapshot.createdAt,\n id: { lt: snapshot.id },\n },\n ],\n },\n orderBy: [{ createdAt: 'desc' }, { id: 'desc' }],\n });\n },\n updateNote: async (id, input) => {\n const { tagIds, properties, ...noteInput } = input;\n\n return models.$transaction(async (tx) => {\n const note = await tx.note.update({\n where: { id },\n data: {\n ...noteInput,\n ...buildNoteSearchProjection({\n title: noteInput.title,\n content: noteInput.content,\n }),\n ...(tagIds !== undefined ? { tags: { set: tagIds.map((tagId) => ({ id: tagId })) } } : {}),\n },\n });\n\n if (properties !== undefined) {\n await tx.noteProperty.deleteMany({ where: { noteId: id } });\n\n for (const property of properties) {\n const definition = await tx.propertyDefinition.upsert({\n where: { key: property.key },\n create: {\n key: property.key,\n name: property.name,\n valueType: property.valueType,\n },\n update: {\n name: property.name,\n valueType: property.valueType,\n },\n });\n\n const option =\n property.valueType === 'select' && property.optionValue\n ? await tx.propertyOption.upsert({\n where: {\n propertyDefinitionId_value: {\n propertyDefinitionId: definition.id,\n value: property.optionValue,\n },\n },\n create: {\n propertyDefinitionId: definition.id,\n value: property.optionValue,\n label: property.optionLabel ?? property.optionValue,\n color: property.optionColor ?? null,\n },\n update: {\n label: property.optionLabel ?? property.optionValue,\n color: property.optionColor ?? null,\n },\n })\n : null;\n\n await tx.noteProperty.create({\n data: {\n noteId: id,\n propertyDefinitionId: definition.id,\n optionId: option?.id ?? null,\n textValue: property.textValue ?? null,\n textValueNormalized: property.textValueNormalized ?? null,\n numberValue: property.numberValue ?? null,\n dateValue: property.dateValue ? new Date(property.dateValue) : null,\n boolValue: property.boolValue ?? null,\n },\n });\n }\n }\n\n return note;\n });\n },\n});\n\nexport const captureNoteBaseline = async (input: {\n noteId: number;\n editSessionId?: string;\n meta?: string;\n baseline?: NoteRecord;\n force?: boolean;\n}) => {\n return defaultNoteSnapshotService.captureBaseline(input);\n};\n\nexport const listNoteSnapshots = async (noteId: number, limit?: number) => {\n return defaultNoteSnapshotService.listSnapshots(noteId, limit);\n};\n\nexport const getNoteSnapshot = async (snapshotId: number) => {\n return defaultNoteSnapshotService.getSnapshot(snapshotId);\n};\n\nexport const diffNoteSnapshot = async (\n snapshotId: number,\n options?: {\n compareToSnapshotId?: number;\n target?: NoteSnapshotDiffTarget;\n contextLines?: number | null;\n },\n) => {\n return defaultNoteSnapshotService.diffSnapshot(snapshotId, options);\n};\n\nexport const restoreNoteSnapshot = async (snapshotId: number, options?: { meta?: string }) => {\n return defaultNoteSnapshotService.restoreSnapshot(snapshotId, options);\n};\n\nexport const purgeExpiredNoteSnapshots = async () => {\n return defaultPurgeExpiredSnapshots(createRetentionCutoff(SNAPSHOT_RETENTION_DAYS), RECOVERY_CLEANUP_BATCH_LIMIT);\n};\n"],"mappings":"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","names":["note"]}
|