@steinnes/snippets 0.1.0 → 0.1.2
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/README.md +1 -1
- package/dist/index.js +1 -4
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
package/dist/index.js
CHANGED
|
@@ -1,6 +1,3 @@
|
|
|
1
|
-
// src/store.ts
|
|
2
|
-
import { randomUUID } from "crypto";
|
|
3
|
-
|
|
4
1
|
// src/errors.ts
|
|
5
2
|
var SnippetNotFoundError = class extends Error {
|
|
6
3
|
name = "SnippetNotFoundError";
|
|
@@ -289,7 +286,7 @@ var SnippetStore = class {
|
|
|
289
286
|
if (this._byName.has(name)) {
|
|
290
287
|
throw new SnippetNameConflictError(name);
|
|
291
288
|
}
|
|
292
|
-
const id = randomUUID();
|
|
289
|
+
const id = globalThis.crypto.randomUUID();
|
|
293
290
|
const now = nextDate();
|
|
294
291
|
const snippet = Object.freeze({
|
|
295
292
|
id,
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/store.ts","../src/errors.ts","../src/findMatch.ts","../src/applyEdits.ts","../src/applyPatch.ts","../src/parsePatch.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto';\nimport { SnippetNotFoundError, SnippetNameConflictError, OldTextNotFoundError, PatchApplyError, PatchTargetMismatchError } from './errors.js';\nimport { applyEdits } from './applyEdits.js';\nimport { applyPatch } from './applyPatch.js';\nimport { parsePatch } from './parsePatch.js';\nimport type { Snippet, SnippetStoreOptions, CreateInput, Edit, EditOptions } from './types.js';\n\n// Millisecond timestamps can collide when two mutations happen in quick\n// succession. We pair each timestamp with a monotonic sequence number so that\n// every bump is strictly later regardless of wall-clock resolution.\nlet _seq = 0;\nfunction nextDate (): Date {\n return new Date(Date.now() + _seq++);\n}\n\nexport class SnippetStore {\n private readonly _snippets = new Map<string, Snippet>();\n private readonly _byName = new Map<string, string>(); // name → id\n readonly fuzzy: boolean;\n\n constructor (options: SnippetStoreOptions = {}) {\n this.fuzzy = options.fuzzy ?? true;\n }\n\n create (input: CreateInput): Snippet {\n const name = input.name;\n if (!name) throw new Error('name is required');\n if (this._byName.has(name)) {\n throw new SnippetNameConflictError(name);\n }\n const id = randomUUID();\n const now = nextDate();\n const snippet: Snippet = Object.freeze({\n id,\n name,\n content: input.content ?? '',\n createdAt: now,\n updatedAt: now,\n });\n this._snippets.set(id, snippet);\n this._byName.set(name, id);\n return snippet;\n }\n\n get (id: string): Snippet | undefined {\n return this._snippets.get(id);\n }\n\n getByName (name: string): Snippet | undefined {\n const id = this._byName.get(name);\n if (id === undefined) return undefined;\n return this._snippets.get(id);\n }\n\n has (id: string): boolean {\n return this._snippets.has(id);\n }\n\n list (): Snippet[] {\n return Array.from(this._snippets.values());\n }\n\n rename (id: string, newName: string): Snippet {\n const snippet = this._snippets.get(id);\n if (snippet === undefined) {\n throw new SnippetNotFoundError(id);\n }\n // Allow renaming to the current name (no-op that still bumps updatedAt).\n // Only throw for a genuinely different name that is already taken.\n if (newName !== snippet.name && this._byName.has(newName)) {\n throw new SnippetNameConflictError(newName);\n }\n this._byName.delete(snippet.name);\n this._byName.set(newName, id);\n const updated: Snippet = Object.freeze({ ...snippet, name: newName, updatedAt: nextDate() });\n this._snippets.set(id, updated);\n return updated;\n }\n\n delete (id: string): boolean {\n const snippet = this._snippets.get(id);\n if (snippet === undefined) return false;\n this._byName.delete(snippet.name);\n this._snippets.delete(id);\n return true;\n }\n\n replace (id: string, content: string): Snippet {\n const snippet = this._snippets.get(id);\n if (snippet === undefined) {\n throw new SnippetNotFoundError(id);\n }\n const updated: Snippet = Object.freeze({ ...snippet, content, updatedAt: nextDate() });\n this._snippets.set(id, updated);\n return updated;\n }\n\n edit (id: string, edits: Edit | readonly Edit[], options?: EditOptions): Snippet {\n const snippet = this._snippets.get(id);\n if (snippet === undefined) {\n throw new SnippetNotFoundError(id);\n }\n\n // Normalize single Edit to array.\n const editsArray = Array.isArray(edits) ? edits : [ edits ];\n\n try {\n const newContent = applyEdits(snippet.content, editsArray, {\n fuzzy: options?.fuzzy ?? this.fuzzy,\n });\n const updated: Snippet = Object.freeze({ ...snippet, content: newContent, updatedAt: nextDate() });\n this._snippets.set(id, updated);\n return updated;\n }\n catch (err) {\n // Re-throw OldTextNotFoundError with snippetId populated.\n if (err instanceof OldTextNotFoundError) {\n const enriched = new OldTextNotFoundError(id, err.oldText, err.editIndex);\n throw enriched;\n }\n throw err;\n }\n }\n\n patch (id: string, patchText: string, options?: EditOptions): Snippet {\n const snippet = this._snippets.get(id);\n if (snippet === undefined) {\n throw new SnippetNotFoundError(id);\n }\n\n // Parse the patch — PatchParseError propagates unchanged if malformed.\n const { targetName, chunks } = parsePatch(patchText);\n\n // Strict target-name validation: if the patch carries *** Update File: <name>,\n // it must match the current snippet name.\n if (targetName !== undefined && targetName !== snippet.name) {\n throw new PatchTargetMismatchError(snippet.id, snippet.name, targetName);\n }\n\n try {\n const newContent = applyPatch(snippet.content, chunks, {\n fuzzy: options?.fuzzy ?? this.fuzzy,\n });\n const updated: Snippet = Object.freeze({ ...snippet, content: newContent, updatedAt: nextDate() });\n this._snippets.set(id, updated);\n return updated;\n }\n catch (err) {\n // Re-throw PatchApplyError with snippetId populated.\n if (err instanceof PatchApplyError) {\n const enriched = new PatchApplyError(id, err.chunkIndex, err.detail);\n throw enriched;\n }\n throw err;\n }\n }\n}\n","export class SnippetNotFoundError extends Error {\n readonly name = 'SnippetNotFoundError';\n readonly snippetId: string;\n\n constructor (snippetId: string) {\n super(`Snippet not found: ${snippetId}`);\n this.snippetId = snippetId;\n }\n}\n\nexport class SnippetNameConflictError extends Error {\n readonly name = 'SnippetNameConflictError';\n readonly conflictName: string;\n\n constructor (name: string) {\n super(`Snippet name already taken: ${name}`);\n this.conflictName = name;\n }\n}\n\nexport class OldTextNotFoundError extends Error {\n readonly name = 'OldTextNotFoundError';\n readonly snippetId: string;\n readonly oldText: string;\n readonly editIndex: number;\n\n constructor (snippetId: string, oldText: string, editIndex: number) {\n super(`Snippet ${snippetId}: oldText \"${oldText}\" not found at edit index ${editIndex}`);\n this.snippetId = snippetId;\n this.oldText = oldText;\n this.editIndex = editIndex;\n }\n}\n\nexport class PatchParseError extends Error {\n readonly name = 'PatchParseError';\n\n constructor (message: string) {\n super(message);\n }\n}\n\nexport class PatchApplyError extends Error {\n readonly name = 'PatchApplyError';\n readonly snippetId: string;\n readonly chunkIndex: number;\n readonly detail: string;\n\n constructor (snippetId: string, chunkIndex: number, detail: string) {\n super(`Snippet ${snippetId}: chunk ${chunkIndex} — ${detail}`);\n this.snippetId = snippetId;\n this.chunkIndex = chunkIndex;\n this.detail = detail;\n }\n}\n\nexport class PatchTargetMismatchError extends Error {\n readonly name = 'PatchTargetMismatchError';\n readonly snippetId: string;\n readonly snippetName: string;\n readonly patchTargetName: string;\n\n constructor (snippetId: string, snippetName: string, patchTargetName: string) {\n super(`Patch targets '${patchTargetName}' but snippet ${snippetId} is named '${snippetName}'.`);\n this.snippetId = snippetId;\n this.snippetName = snippetName;\n this.patchTargetName = patchTargetName;\n }\n}\n","export type FindMatchResult = {\n readonly start: number;\n readonly end: number;\n};\n\nexport type FindMatchOptions = {\n readonly fuzzy?: boolean; // default true; false = exact only\n readonly searchOffset?: number; // default 0\n};\n\n// --- Representation anchor ---\ntype LineSpan = {\n readonly start: number; // char offset in original content where line starts\n readonly length: number; // line text length, excluding the trailing '\\n'\n readonly text: string; // the line text, excluding the trailing '\\n'\n};\n\nfunction splitIntoSpans (content: string): LineSpan[] {\n const spans: LineSpan[] = [];\n let start = 0;\n for (let i = 0; i < content.length; i++) {\n if (content[i] === '\\n') {\n spans.push({ start, length: i - start, text: content.slice(start, i) });\n start = i + 1;\n }\n }\n spans.push({ start, length: content.length - start, text: content.slice(start) });\n return spans;\n}\n\n// --- Fuzzy tier comparators ---\nfunction rstripEquals (a: string, b: string): boolean {\n return a.replace(/\\s+$/u, '') === b.replace(/\\s+$/u, '');\n}\n\nfunction trimEquals (a: string, b: string): boolean {\n return a.trim() === b.trim();\n}\n\nfunction nfcEquals (a: string, b: string): boolean {\n return a.normalize('NFC') === b.normalize('NFC');\n}\n\nexport function findMatch (\n content: string,\n oldText: string,\n options?: FindMatchOptions,\n): FindMatchResult | null {\n const searchOffset = options?.searchOffset ?? 0;\n const fuzzy = options?.fuzzy ?? true;\n\n // Empty oldText: match at the search cursor position (half-open span)\n if (oldText === '') {\n return { start: searchOffset, end: searchOffset };\n }\n\n // Tier 1: exact substring match\n const exactStart = content.indexOf(oldText, searchOffset);\n if (exactStart !== -1) {\n return { start: exactStart, end: exactStart + oldText.length };\n }\n\n // Fuzzy tiers only when fuzzy !== false\n if (!fuzzy) {\n return null;\n }\n\n const contentSpans = splitIntoSpans(content);\n const patternLines = oldText.split('\\n');\n\n // Find the first span whose end is >= searchOffset (skip spans entirely before searchOffset)\n let lineIdx = 0;\n while (\n lineIdx < contentSpans.length &&\n contentSpans[lineIdx].start + contentSpans[lineIdx].length < searchOffset\n ) {\n lineIdx++;\n }\n\n // Try each fuzzy tier in priority order\n const tiers: readonly ((a: string, b: string) => boolean)[] = [\n rstripEquals,\n trimEquals,\n nfcEquals,\n ];\n\n for (const equals of tiers) {\n // Normalize pattern lines for this tier\n const normalizedPattern = patternLines.map(line => {\n if (equals === rstripEquals) return line.replace(/\\s+$/u, '');\n if (equals === trimEquals) return line.trim();\n if (equals === nfcEquals) return line.normalize('NFC');\n return line; // should never reach\n });\n\n // Try to match starting from each possible line\n // Last valid hit: contentSpans.length - patternLines.length\n // Need hit < contentSpans.length - patternLines.length + 1\n for (\n let hit = lineIdx;\n hit < contentSpans.length - patternLines.length + 1;\n hit++\n ) {\n let allMatch = true;\n for (let j = 0; j < patternLines.length; j++) {\n const contentLine = contentSpans[hit + j].text;\n const patternLine = normalizedPattern[j];\n if (!equals(contentLine, patternLine)) {\n allMatch = false;\n break;\n }\n }\n if (allMatch) {\n const start = contentSpans[hit].start;\n const lastSpan = contentSpans[hit + patternLines.length - 1];\n const end = lastSpan.start + lastSpan.length;\n return { start, end };\n }\n }\n }\n\n return null;\n}\n","import { findMatch } from './findMatch.js';\nimport type { Edit } from './types.js';\nimport { OldTextNotFoundError } from './errors.js';\n\nexport type ApplyEditsOptions = {\n readonly fuzzy?: boolean;\n};\n\nexport function applyEdits (\n content: string,\n edits: readonly Edit[],\n options?: ApplyEditsOptions,\n): string {\n // Work on a local string — atomic by construction.\n // No changes escape on failure.\n let working = content;\n let searchOffset = 0;\n\n for (let i = 0; i < edits.length; i++) {\n const edit = edits[i];\n\n const match = findMatch(working, edit.oldText, {\n fuzzy: options?.fuzzy,\n searchOffset,\n });\n\n if (match === null) {\n throw new OldTextNotFoundError('', edit.oldText, i);\n }\n\n // Splice the replacement into the working string.\n working =\n working.slice(0, match.start) +\n edit.newText +\n working.slice(match.end);\n\n // Advance cursor past the replacement in the post-splice content.\n // No-op edits (oldText === newText) still advance the cursor.\n searchOffset = match.start + edit.newText.length;\n }\n\n return working;\n}\n","import { findMatch } from './findMatch.js';\nimport type { Chunk } from './parsePatch.js';\nimport { PatchApplyError } from './errors.js';\n\nexport type ApplyPatchOptions = {\n readonly fuzzy?: boolean;\n};\n\n/**\n * Apply a sequence of patches to content.\n *\n * Each chunk's oldText is located via findMatch with a forward-only cursor:\n * search begins at the current cursor position, which advances past each\n * replacement. This means later chunks cannot match text that appears before\n * the cursor, even if it exists in the original content.\n *\n * Atomic: on any chunk failure, no partial result escapes — the function\n * either returns the fully-patched content or throws PatchApplyError.\n *\n * @param content - The original content string.\n * @param chunks - Array of Patch Chunks to apply in order.\n * @param options - Optional fuzzy matching flag (default: true).\n * @returns The patched content string.\n * @throws PatchApplyError with chunkIndex on first unmatchable chunk,\n * with snippetId = '' (empty string sentinel — store layer\n * re-throws with the real snippetId populated).\n */\nexport function applyPatch (\n content: string,\n chunks: readonly Chunk[],\n options?: ApplyPatchOptions,\n): string {\n // Work on a local string — atomic by construction.\n // No changes escape on failure.\n let working = content;\n let searchOffset = 0;\n\n for (let i = 0; i < chunks.length; i++) {\n const chunk = chunks[i];\n\n const match = findMatch(working, chunk.oldText, {\n fuzzy: options?.fuzzy,\n searchOffset,\n });\n\n if (match === null) {\n throw new PatchApplyError('', i, `oldText \"${chunk.oldText}\" not found`);\n }\n\n // Splice the newText into the working string.\n working =\n working.slice(0, match.start) +\n chunk.newText +\n working.slice(match.end);\n\n // Advance cursor past the replacement in the post-splice content.\n searchOffset = match.start + chunk.newText.length;\n }\n\n return working;\n}\n","import { PatchParseError } from './errors.js';\n\nexport type Chunk = {\n /** Context+removed lines stitched with '\\n', ready to pass to findMatch. */\n oldText: string;\n /** Context+added lines stitched with '\\n', ready to splice in. */\n newText: string;\n};\n\nconst BEGIN_SENTINEL = '*** Begin Patch';\nconst END_SENTINEL = '*** End Patch';\nconst UPDATE_FILE_PREFIX = '*** Update File:';\nconst CHUNK_START = '@@';\n\n/**\n * Parse a single-snippet patch string into the target name and an array of\n * Chunks.\n *\n * Grammar:\n * patch = BEGIN_SENTINEL [UPDATE_FILE] chunk+ END_SENTINEL\n * UPDATE_FILE = \"*** Update File:\" SP name EOL\n * chunk = CHUNK_START line* (CHUNK_START | UPDATE_FILE | END_SENTINEL)\n * line = \" \" content (context — appears in both oldText and newText)\n * | \"-\" content (removed — only in oldText)\n * | \"+\" content (added — only in newText)\n *\n * Multi-file patches (multiple *** Update File: headers) are rejected.\n * Lines without a valid marker inside a chunk are rejected.\n *\n * @returns An object carrying `targetName` (undefined if the *** Update File\n * header was absent) and `chunks`.\n */\nexport function parsePatch (patchText: string): { targetName: string | undefined; chunks: Chunk[] } {\n const lines = patchText.split('\\n');\n\n if (lines.length < 2) {\n throw new PatchParseError('Patch is empty or invalid');\n }\n\n if (lines[0].trim() !== BEGIN_SENTINEL) {\n throw new PatchParseError(`Patch must start with \"${BEGIN_SENTINEL}\"`);\n }\n\n let i = 1;\n const chunks: Chunk[] = [];\n\n // Skip blank lines after the header\n while (i < lines.length && lines[i].trim() === '') {\n i++;\n }\n\n // Optional: *** Update File: <name>\n let targetName: string | undefined;\n if (i < lines.length && lines[i].trim().startsWith(UPDATE_FILE_PREFIX)) {\n const raw = lines[i].trim();\n targetName = raw.slice(UPDATE_FILE_PREFIX.length).trim();\n i++;\n // Skip blank lines after file header\n while (i < lines.length && lines[i].trim() === '') {\n i++;\n }\n }\n\n // Parse chunks until we hit *** End Patch\n while (i < lines.length) {\n const line = lines[i];\n const trimmed = line.trim();\n\n // Skip blank lines between chunks\n if (trimmed === '') {\n i++;\n continue;\n }\n\n // Reached end of patch\n if (trimmed === END_SENTINEL) {\n break;\n }\n\n // Encountered another *** Update File: — multi-file patch\n if (trimmed.startsWith(UPDATE_FILE_PREFIX)) {\n throw new PatchParseError(\n `Multi-file patches are not supported (line ${i + 1})`,\n );\n }\n\n // Must start a chunk\n if (trimmed !== CHUNK_START && !trimmed.startsWith(CHUNK_START + ' ')) {\n throw new PatchParseError(\n `Expected chunk start \"${CHUNK_START}\" at line ${i + 1}, got \"${trimmed}\"`,\n );\n }\n\n // Consume the @@ marker\n i++;\n\n // Collect lines for this chunk, tracking original order\n const oldLines: string[] = [];\n const newLines: string[] = [];\n\n while (i < lines.length) {\n const raw = lines[i];\n const trimmedLine = raw.trim();\n\n // Empty line within chunk — include as empty string in both\n if (raw === '') {\n oldLines.push('');\n newLines.push('');\n i++;\n continue;\n }\n\n // End conditions\n if (\n trimmedLine.startsWith(CHUNK_START) ||\n trimmedLine.startsWith(UPDATE_FILE_PREFIX) ||\n trimmedLine === END_SENTINEL\n ) {\n break;\n }\n\n // Detect invalid lines (lines not starting with space, +, or -)\n const marker = raw[0];\n if (marker !== ' ' && marker !== '+' && marker !== '-') {\n throw new PatchParseError(\n `Invalid line marker \"${marker}\" at line ${i + 1}; expected \" \", \"+\", or \"-\"`,\n );\n }\n\n const body = raw.slice(1);\n\n if (marker === ' ') {\n // Context line — appears in both\n oldLines.push(body);\n newLines.push(body);\n }\n else if (marker === '-') {\n // Removed line — only in oldText\n oldLines.push(body);\n }\n else {\n // marker === '+'\n // Added line — only in newText\n newLines.push(body);\n }\n\n i++;\n }\n\n // Build stitched text for this chunk\n // Join with \\n but DO NOT add a trailing newline\n // (The holy tests pin this: oldText: 'keep\\nold' has no trailing newline)\n const chunk: Chunk = {\n oldText: oldLines.join('\\n'),\n newText: newLines.join('\\n'),\n };\n chunks.push(chunk);\n }\n\n // Validate end sentinel (may be followed by empty lines from trailing newline)\n let lastNonEmpty = lines.length - 1;\n while (lastNonEmpty >= 0 && lines[lastNonEmpty].trim() === '') {\n lastNonEmpty--;\n }\n if (lastNonEmpty < 0 || lines[lastNonEmpty].trim() !== END_SENTINEL) {\n throw new PatchParseError(\n `Patch must end with \"${END_SENTINEL}\" (at line ${lastNonEmpty + 1})`,\n );\n }\n\n return { targetName, chunks };\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/errors.ts","../src/findMatch.ts","../src/applyEdits.ts","../src/applyPatch.ts","../src/parsePatch.ts","../src/store.ts"],"sourcesContent":["export class SnippetNotFoundError extends Error {\n readonly name = 'SnippetNotFoundError';\n readonly snippetId: string;\n\n constructor (snippetId: string) {\n super(`Snippet not found: ${snippetId}`);\n this.snippetId = snippetId;\n }\n}\n\nexport class SnippetNameConflictError extends Error {\n readonly name = 'SnippetNameConflictError';\n readonly conflictName: string;\n\n constructor (name: string) {\n super(`Snippet name already taken: ${name}`);\n this.conflictName = name;\n }\n}\n\nexport class OldTextNotFoundError extends Error {\n readonly name = 'OldTextNotFoundError';\n readonly snippetId: string;\n readonly oldText: string;\n readonly editIndex: number;\n\n constructor (snippetId: string, oldText: string, editIndex: number) {\n super(`Snippet ${snippetId}: oldText \"${oldText}\" not found at edit index ${editIndex}`);\n this.snippetId = snippetId;\n this.oldText = oldText;\n this.editIndex = editIndex;\n }\n}\n\nexport class PatchParseError extends Error {\n readonly name = 'PatchParseError';\n\n constructor (message: string) {\n super(message);\n }\n}\n\nexport class PatchApplyError extends Error {\n readonly name = 'PatchApplyError';\n readonly snippetId: string;\n readonly chunkIndex: number;\n readonly detail: string;\n\n constructor (snippetId: string, chunkIndex: number, detail: string) {\n super(`Snippet ${snippetId}: chunk ${chunkIndex} — ${detail}`);\n this.snippetId = snippetId;\n this.chunkIndex = chunkIndex;\n this.detail = detail;\n }\n}\n\nexport class PatchTargetMismatchError extends Error {\n readonly name = 'PatchTargetMismatchError';\n readonly snippetId: string;\n readonly snippetName: string;\n readonly patchTargetName: string;\n\n constructor (snippetId: string, snippetName: string, patchTargetName: string) {\n super(`Patch targets '${patchTargetName}' but snippet ${snippetId} is named '${snippetName}'.`);\n this.snippetId = snippetId;\n this.snippetName = snippetName;\n this.patchTargetName = patchTargetName;\n }\n}\n","export type FindMatchResult = {\n readonly start: number;\n readonly end: number;\n};\n\nexport type FindMatchOptions = {\n readonly fuzzy?: boolean; // default true; false = exact only\n readonly searchOffset?: number; // default 0\n};\n\n// --- Representation anchor ---\ntype LineSpan = {\n readonly start: number; // char offset in original content where line starts\n readonly length: number; // line text length, excluding the trailing '\\n'\n readonly text: string; // the line text, excluding the trailing '\\n'\n};\n\nfunction splitIntoSpans (content: string): LineSpan[] {\n const spans: LineSpan[] = [];\n let start = 0;\n for (let i = 0; i < content.length; i++) {\n if (content[i] === '\\n') {\n spans.push({ start, length: i - start, text: content.slice(start, i) });\n start = i + 1;\n }\n }\n spans.push({ start, length: content.length - start, text: content.slice(start) });\n return spans;\n}\n\n// --- Fuzzy tier comparators ---\nfunction rstripEquals (a: string, b: string): boolean {\n return a.replace(/\\s+$/u, '') === b.replace(/\\s+$/u, '');\n}\n\nfunction trimEquals (a: string, b: string): boolean {\n return a.trim() === b.trim();\n}\n\nfunction nfcEquals (a: string, b: string): boolean {\n return a.normalize('NFC') === b.normalize('NFC');\n}\n\nexport function findMatch (\n content: string,\n oldText: string,\n options?: FindMatchOptions,\n): FindMatchResult | null {\n const searchOffset = options?.searchOffset ?? 0;\n const fuzzy = options?.fuzzy ?? true;\n\n // Empty oldText: match at the search cursor position (half-open span)\n if (oldText === '') {\n return { start: searchOffset, end: searchOffset };\n }\n\n // Tier 1: exact substring match\n const exactStart = content.indexOf(oldText, searchOffset);\n if (exactStart !== -1) {\n return { start: exactStart, end: exactStart + oldText.length };\n }\n\n // Fuzzy tiers only when fuzzy !== false\n if (!fuzzy) {\n return null;\n }\n\n const contentSpans = splitIntoSpans(content);\n const patternLines = oldText.split('\\n');\n\n // Find the first span whose end is >= searchOffset (skip spans entirely before searchOffset)\n let lineIdx = 0;\n while (\n lineIdx < contentSpans.length &&\n contentSpans[lineIdx].start + contentSpans[lineIdx].length < searchOffset\n ) {\n lineIdx++;\n }\n\n // Try each fuzzy tier in priority order\n const tiers: readonly ((a: string, b: string) => boolean)[] = [\n rstripEquals,\n trimEquals,\n nfcEquals,\n ];\n\n for (const equals of tiers) {\n // Normalize pattern lines for this tier\n const normalizedPattern = patternLines.map(line => {\n if (equals === rstripEquals) return line.replace(/\\s+$/u, '');\n if (equals === trimEquals) return line.trim();\n if (equals === nfcEquals) return line.normalize('NFC');\n return line; // should never reach\n });\n\n // Try to match starting from each possible line\n // Last valid hit: contentSpans.length - patternLines.length\n // Need hit < contentSpans.length - patternLines.length + 1\n for (\n let hit = lineIdx;\n hit < contentSpans.length - patternLines.length + 1;\n hit++\n ) {\n let allMatch = true;\n for (let j = 0; j < patternLines.length; j++) {\n const contentLine = contentSpans[hit + j].text;\n const patternLine = normalizedPattern[j];\n if (!equals(contentLine, patternLine)) {\n allMatch = false;\n break;\n }\n }\n if (allMatch) {\n const start = contentSpans[hit].start;\n const lastSpan = contentSpans[hit + patternLines.length - 1];\n const end = lastSpan.start + lastSpan.length;\n return { start, end };\n }\n }\n }\n\n return null;\n}\n","import { findMatch } from './findMatch.js';\nimport type { Edit } from './types.js';\nimport { OldTextNotFoundError } from './errors.js';\n\nexport type ApplyEditsOptions = {\n readonly fuzzy?: boolean;\n};\n\nexport function applyEdits (\n content: string,\n edits: readonly Edit[],\n options?: ApplyEditsOptions,\n): string {\n // Work on a local string — atomic by construction.\n // No changes escape on failure.\n let working = content;\n let searchOffset = 0;\n\n for (let i = 0; i < edits.length; i++) {\n const edit = edits[i];\n\n const match = findMatch(working, edit.oldText, {\n fuzzy: options?.fuzzy,\n searchOffset,\n });\n\n if (match === null) {\n throw new OldTextNotFoundError('', edit.oldText, i);\n }\n\n // Splice the replacement into the working string.\n working =\n working.slice(0, match.start) +\n edit.newText +\n working.slice(match.end);\n\n // Advance cursor past the replacement in the post-splice content.\n // No-op edits (oldText === newText) still advance the cursor.\n searchOffset = match.start + edit.newText.length;\n }\n\n return working;\n}\n","import { findMatch } from './findMatch.js';\nimport type { Chunk } from './parsePatch.js';\nimport { PatchApplyError } from './errors.js';\n\nexport type ApplyPatchOptions = {\n readonly fuzzy?: boolean;\n};\n\n/**\n * Apply a sequence of patches to content.\n *\n * Each chunk's oldText is located via findMatch with a forward-only cursor:\n * search begins at the current cursor position, which advances past each\n * replacement. This means later chunks cannot match text that appears before\n * the cursor, even if it exists in the original content.\n *\n * Atomic: on any chunk failure, no partial result escapes — the function\n * either returns the fully-patched content or throws PatchApplyError.\n *\n * @param content - The original content string.\n * @param chunks - Array of Patch Chunks to apply in order.\n * @param options - Optional fuzzy matching flag (default: true).\n * @returns The patched content string.\n * @throws PatchApplyError with chunkIndex on first unmatchable chunk,\n * with snippetId = '' (empty string sentinel — store layer\n * re-throws with the real snippetId populated).\n */\nexport function applyPatch (\n content: string,\n chunks: readonly Chunk[],\n options?: ApplyPatchOptions,\n): string {\n // Work on a local string — atomic by construction.\n // No changes escape on failure.\n let working = content;\n let searchOffset = 0;\n\n for (let i = 0; i < chunks.length; i++) {\n const chunk = chunks[i];\n\n const match = findMatch(working, chunk.oldText, {\n fuzzy: options?.fuzzy,\n searchOffset,\n });\n\n if (match === null) {\n throw new PatchApplyError('', i, `oldText \"${chunk.oldText}\" not found`);\n }\n\n // Splice the newText into the working string.\n working =\n working.slice(0, match.start) +\n chunk.newText +\n working.slice(match.end);\n\n // Advance cursor past the replacement in the post-splice content.\n searchOffset = match.start + chunk.newText.length;\n }\n\n return working;\n}\n","import { PatchParseError } from './errors.js';\n\nexport type Chunk = {\n /** Context+removed lines stitched with '\\n', ready to pass to findMatch. */\n oldText: string;\n /** Context+added lines stitched with '\\n', ready to splice in. */\n newText: string;\n};\n\nconst BEGIN_SENTINEL = '*** Begin Patch';\nconst END_SENTINEL = '*** End Patch';\nconst UPDATE_FILE_PREFIX = '*** Update File:';\nconst CHUNK_START = '@@';\n\n/**\n * Parse a single-snippet patch string into the target name and an array of\n * Chunks.\n *\n * Grammar:\n * patch = BEGIN_SENTINEL [UPDATE_FILE] chunk+ END_SENTINEL\n * UPDATE_FILE = \"*** Update File:\" SP name EOL\n * chunk = CHUNK_START line* (CHUNK_START | UPDATE_FILE | END_SENTINEL)\n * line = \" \" content (context — appears in both oldText and newText)\n * | \"-\" content (removed — only in oldText)\n * | \"+\" content (added — only in newText)\n *\n * Multi-file patches (multiple *** Update File: headers) are rejected.\n * Lines without a valid marker inside a chunk are rejected.\n *\n * @returns An object carrying `targetName` (undefined if the *** Update File\n * header was absent) and `chunks`.\n */\nexport function parsePatch (patchText: string): { targetName: string | undefined; chunks: Chunk[] } {\n const lines = patchText.split('\\n');\n\n if (lines.length < 2) {\n throw new PatchParseError('Patch is empty or invalid');\n }\n\n if (lines[0].trim() !== BEGIN_SENTINEL) {\n throw new PatchParseError(`Patch must start with \"${BEGIN_SENTINEL}\"`);\n }\n\n let i = 1;\n const chunks: Chunk[] = [];\n\n // Skip blank lines after the header\n while (i < lines.length && lines[i].trim() === '') {\n i++;\n }\n\n // Optional: *** Update File: <name>\n let targetName: string | undefined;\n if (i < lines.length && lines[i].trim().startsWith(UPDATE_FILE_PREFIX)) {\n const raw = lines[i].trim();\n targetName = raw.slice(UPDATE_FILE_PREFIX.length).trim();\n i++;\n // Skip blank lines after file header\n while (i < lines.length && lines[i].trim() === '') {\n i++;\n }\n }\n\n // Parse chunks until we hit *** End Patch\n while (i < lines.length) {\n const line = lines[i];\n const trimmed = line.trim();\n\n // Skip blank lines between chunks\n if (trimmed === '') {\n i++;\n continue;\n }\n\n // Reached end of patch\n if (trimmed === END_SENTINEL) {\n break;\n }\n\n // Encountered another *** Update File: — multi-file patch\n if (trimmed.startsWith(UPDATE_FILE_PREFIX)) {\n throw new PatchParseError(\n `Multi-file patches are not supported (line ${i + 1})`,\n );\n }\n\n // Must start a chunk\n if (trimmed !== CHUNK_START && !trimmed.startsWith(CHUNK_START + ' ')) {\n throw new PatchParseError(\n `Expected chunk start \"${CHUNK_START}\" at line ${i + 1}, got \"${trimmed}\"`,\n );\n }\n\n // Consume the @@ marker\n i++;\n\n // Collect lines for this chunk, tracking original order\n const oldLines: string[] = [];\n const newLines: string[] = [];\n\n while (i < lines.length) {\n const raw = lines[i];\n const trimmedLine = raw.trim();\n\n // Empty line within chunk — include as empty string in both\n if (raw === '') {\n oldLines.push('');\n newLines.push('');\n i++;\n continue;\n }\n\n // End conditions\n if (\n trimmedLine.startsWith(CHUNK_START) ||\n trimmedLine.startsWith(UPDATE_FILE_PREFIX) ||\n trimmedLine === END_SENTINEL\n ) {\n break;\n }\n\n // Detect invalid lines (lines not starting with space, +, or -)\n const marker = raw[0];\n if (marker !== ' ' && marker !== '+' && marker !== '-') {\n throw new PatchParseError(\n `Invalid line marker \"${marker}\" at line ${i + 1}; expected \" \", \"+\", or \"-\"`,\n );\n }\n\n const body = raw.slice(1);\n\n if (marker === ' ') {\n // Context line — appears in both\n oldLines.push(body);\n newLines.push(body);\n }\n else if (marker === '-') {\n // Removed line — only in oldText\n oldLines.push(body);\n }\n else {\n // marker === '+'\n // Added line — only in newText\n newLines.push(body);\n }\n\n i++;\n }\n\n // Build stitched text for this chunk\n // Join with \\n but DO NOT add a trailing newline\n // (The holy tests pin this: oldText: 'keep\\nold' has no trailing newline)\n const chunk: Chunk = {\n oldText: oldLines.join('\\n'),\n newText: newLines.join('\\n'),\n };\n chunks.push(chunk);\n }\n\n // Validate end sentinel (may be followed by empty lines from trailing newline)\n let lastNonEmpty = lines.length - 1;\n while (lastNonEmpty >= 0 && lines[lastNonEmpty].trim() === '') {\n lastNonEmpty--;\n }\n if (lastNonEmpty < 0 || lines[lastNonEmpty].trim() !== END_SENTINEL) {\n throw new PatchParseError(\n `Patch must end with \"${END_SENTINEL}\" (at line ${lastNonEmpty + 1})`,\n );\n }\n\n return { targetName, chunks };\n}\n","import { SnippetNotFoundError, SnippetNameConflictError, OldTextNotFoundError, PatchApplyError, PatchTargetMismatchError } from './errors.js';\nimport { applyEdits } from './applyEdits.js';\nimport { applyPatch } from './applyPatch.js';\nimport { parsePatch } from './parsePatch.js';\nimport type { Snippet, SnippetStoreOptions, CreateInput, Edit, EditOptions } from './types.js';\n\n// Millisecond timestamps can collide when two mutations happen in quick\n// succession. We pair each timestamp with a monotonic sequence number so that\n// every bump is strictly later regardless of wall-clock resolution.\nlet _seq = 0;\nfunction nextDate (): Date {\n return new Date(Date.now() + _seq++);\n}\n\nexport class SnippetStore {\n private readonly _snippets = new Map<string, Snippet>();\n private readonly _byName = new Map<string, string>(); // name → id\n readonly fuzzy: boolean;\n\n constructor (options: SnippetStoreOptions = {}) {\n this.fuzzy = options.fuzzy ?? true;\n }\n\n create (input: CreateInput): Snippet {\n const name = input.name;\n if (!name) throw new Error('name is required');\n if (this._byName.has(name)) {\n throw new SnippetNameConflictError(name);\n }\n const id = globalThis.crypto.randomUUID();\n const now = nextDate();\n const snippet: Snippet = Object.freeze({\n id,\n name,\n content: input.content ?? '',\n createdAt: now,\n updatedAt: now,\n });\n this._snippets.set(id, snippet);\n this._byName.set(name, id);\n return snippet;\n }\n\n get (id: string): Snippet | undefined {\n return this._snippets.get(id);\n }\n\n getByName (name: string): Snippet | undefined {\n const id = this._byName.get(name);\n if (id === undefined) return undefined;\n return this._snippets.get(id);\n }\n\n has (id: string): boolean {\n return this._snippets.has(id);\n }\n\n list (): Snippet[] {\n return Array.from(this._snippets.values());\n }\n\n rename (id: string, newName: string): Snippet {\n const snippet = this._snippets.get(id);\n if (snippet === undefined) {\n throw new SnippetNotFoundError(id);\n }\n // Allow renaming to the current name (no-op that still bumps updatedAt).\n // Only throw for a genuinely different name that is already taken.\n if (newName !== snippet.name && this._byName.has(newName)) {\n throw new SnippetNameConflictError(newName);\n }\n this._byName.delete(snippet.name);\n this._byName.set(newName, id);\n const updated: Snippet = Object.freeze({ ...snippet, name: newName, updatedAt: nextDate() });\n this._snippets.set(id, updated);\n return updated;\n }\n\n delete (id: string): boolean {\n const snippet = this._snippets.get(id);\n if (snippet === undefined) return false;\n this._byName.delete(snippet.name);\n this._snippets.delete(id);\n return true;\n }\n\n replace (id: string, content: string): Snippet {\n const snippet = this._snippets.get(id);\n if (snippet === undefined) {\n throw new SnippetNotFoundError(id);\n }\n const updated: Snippet = Object.freeze({ ...snippet, content, updatedAt: nextDate() });\n this._snippets.set(id, updated);\n return updated;\n }\n\n edit (id: string, edits: Edit | readonly Edit[], options?: EditOptions): Snippet {\n const snippet = this._snippets.get(id);\n if (snippet === undefined) {\n throw new SnippetNotFoundError(id);\n }\n\n // Normalize single Edit to array.\n const editsArray = Array.isArray(edits) ? edits : [ edits ];\n\n try {\n const newContent = applyEdits(snippet.content, editsArray, {\n fuzzy: options?.fuzzy ?? this.fuzzy,\n });\n const updated: Snippet = Object.freeze({ ...snippet, content: newContent, updatedAt: nextDate() });\n this._snippets.set(id, updated);\n return updated;\n }\n catch (err) {\n // Re-throw OldTextNotFoundError with snippetId populated.\n if (err instanceof OldTextNotFoundError) {\n const enriched = new OldTextNotFoundError(id, err.oldText, err.editIndex);\n throw enriched;\n }\n throw err;\n }\n }\n\n patch (id: string, patchText: string, options?: EditOptions): Snippet {\n const snippet = this._snippets.get(id);\n if (snippet === undefined) {\n throw new SnippetNotFoundError(id);\n }\n\n // Parse the patch — PatchParseError propagates unchanged if malformed.\n const { targetName, chunks } = parsePatch(patchText);\n\n // Strict target-name validation: if the patch carries *** Update File: <name>,\n // it must match the current snippet name.\n if (targetName !== undefined && targetName !== snippet.name) {\n throw new PatchTargetMismatchError(snippet.id, snippet.name, targetName);\n }\n\n try {\n const newContent = applyPatch(snippet.content, chunks, {\n fuzzy: options?.fuzzy ?? this.fuzzy,\n });\n const updated: Snippet = Object.freeze({ ...snippet, content: newContent, updatedAt: nextDate() });\n this._snippets.set(id, updated);\n return updated;\n }\n catch (err) {\n // Re-throw PatchApplyError with snippetId populated.\n if (err instanceof PatchApplyError) {\n const enriched = new PatchApplyError(id, err.chunkIndex, err.detail);\n throw enriched;\n }\n throw err;\n }\n }\n}\n"],"mappings":"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","names":[]}
|