text-version
v2.0.1
Published
A lightweight text version management system with differential storage and version rollback capabilities. Similar to Git's version management mechanism, but specifically optimized for text content.
Downloads
269
Maintainers
Readme
Text-Version
本文档也有中文版本
A lightweight text version management system with differential storage and version rollback capabilities. Similar to Git's version management mechanism, but specifically optimized for text content.
Online preview: https://ravelloh.github.io/text-version
Features
commit: Submit new version (similar to git commit)show: Display specified version content (similar to git show)log: Display version history (similar to git log)latest: Get latest version contentreset: Reset to specified version (similar to git reset --hard), keep the target version and all versions before it, delete versions after itsquash: Keep the target version and all versions after it, delete versions before it to reduce storage space
Storage Format Optimization Features
- Storage format optimization: Uses compact differential storage format
- Duplicate detection: Automatically avoids storing identical content
- Version references: Uses reference syntax to save space for identical content
- Compression support: Optional data compression interface
- Smart differencing: Uses LCS algorithm to calculate optimal differences
- Version name deduplication: Automatically handles duplicate version names by adding # suffixes
- Optimal storage selection: Compares all historical versions and automatically selects the storage method with minimum space
- Hybrid references: Supports combination of version references and differential operations for further storage efficiency optimization
Installation
npm install text-version // or
pnpm install text-version // or
yarn add text-versionBasic Usage
Import
// ES6 modules
import { TextVersion } from 'text-version';
// CommonJS
const { TextVersion } = require('text-version');Create Instance
const tv = new TextVersion();Usage Example
// Import
import { TextVersion } from 'text-version';
// Or CommonJS
// const { TextVersion } = require('text-version');
// Create instance
const tv = new TextVersion();
// Submit new version
tv.commit('Hello, World!', 'v1');
tv.commit('Hello, World!\\nThis is the second line.', 'v2');
tv.commit('Hello, TypeScript!\\nThis is the second line.');
// View version history
console.log(tv.log());
//[
// { version: 'v1', isSnapshot: false }, // diff
// { version: 'v2', isSnapshot: false }, // diff
// { version: 'ycdf93', isSnapshot: true } // snapshot (latest)
//]
// View specified version
console.log(tv.show('v1'));
// "Hello, World!"
// View latest version
console.log(tv.latest());
// "Hello, TypeScript!\\nThis is the second line."
// Export version data (monolithic mode - default)
const storage = tv.export();
// Or explicitly specify monolithic mode
const storage2 = tv.export("monolithic");
console.log(storage);
// 2:v1:R6D7
// 2:v2:R3D10I2:world
// :6:ycdf93:hello, TypeScript!\nThis is the second line.
// Reset to specified version
tv.reset('v2');
// Compress storage space - set v2 as snapshot, delete v1
tv.squash('v2'); // v1 version will be permanently deleted, v2 becomes new starting snapshot
// Load from existing storage
const tv2 = new TextVersion(storage);
console.log(tv2.latest()); // Can access the saved dataAdvanced Usage
Storage Space Optimization
When version history becomes too long, you can use the squash method to optimize storage space:
const tv = new TextVersion();
// Create multiple versions
tv.commit('First version', 'v1');
tv.commit('Second version', 'v2');
tv.commit('Third version', 'v3');
tv.commit('Fourth version', 'v4');
const storage = tv.export();
console.log('Original storage size:', storage.length);
console.log('Version count:', tv.log().length); // 4 versions
// Compress to v2, delete v1
tv.squash('v2');
const newStorage = tv.export();
console.log('Compressed storage size:', newStorage.length);
console.log('Version count:', tv.log().length); // 3 versions: v2, v3, v4
// v1 version has been deleted and cannot be accessed
console.log(tv.show('v1')); // null
// v2 and later versions can still be accessed normally
console.log(tv.show('v2')); // "Second version"
// Note: After squash, v4 (latest version) is a snapshot, v2 and v3 are diffsCustom Compression
You can provide custom compression algorithms to further reduce storage space:
import { TextVersion } from 'text-version';
// Compression usage example
const compressionProvider = {
compress: (data) => /* compression algorithm */ data,
decompress: (data) => /* decompression algorithm */ data
};
const tv = new TextVersion('', compressionProvider);
tv.commit('This is a very long text...');
console.log(tv.latest());Separate Storage
When snapshot content is very large, you can use separate storage for more flexible data management:
const tv = new TextVersion();
tv.commit('First version', 'v1');
tv.commit('Second version', 'v2');
tv.commit('Very very very long latest version content...', 'v3');
// Separate export
const result = tv.export("separate");
// result = {
// metadata: "2:v1:D6I4:original text\n2:v2:D5\n:2:v3:##[[abc12345]]##",
// snapshot: "Very very very long latest version content..."
// }
// Note: ##[[abc12345]]## is a hash placeholder for snapshot content, used for integrity verification
// metadata contains placeholder, snapshot stored separately
console.log(result.metadata.length); // small
console.log(result.snapshot.length); // large
// Create instance with separated data (auto-validates hash)
const tv2 = new TextVersion(result.metadata, result.snapshot);
console.log(tv2.latest()); // "Very very very long latest version content..."
// Error will be thrown if snapshot hash doesn't match (prevents data tampering)
try {
new TextVersion(result.metadata, 'wrong snapshot content');
} catch (e) {
console.error('Hash validation failed'); // Snapshot content doesn't match hash in metadata
}Use Cases:
- Large snapshot content: When latest version content is very large, snapshot can be stored separately in filesystem or database
- CDN optimization: metadata can be served from CDN, snapshot loaded on demand
- Cache strategy: Use different cache strategies for metadata and snapshot
Storage Format Description
Uses length-prefixed format internally:
:version_name_length:version_name:content (snapshot version)
version_name_length:version_name:operation_sequence (diff version)
version_name_length:version_name:=version_name (version reference)
version_name_length:version_name:=version_name:operation_sequence (hybrid reference)Diff operation format:
R number- Retain N charactersI length:text- Insert text of specified lengthD number- Delete N characters
Version Name Duplication Handling
When version name duplication occurs during submission, the system automatically adds # suffixes:
- Duplicate with latest version name: If the new version name is the same as the most recent submission, adds one #, e.g.,
v1→v1# - Duplicate with previous version name: If the new version name is the same as any historical version name, adds multiple # as needed, e.g.,
v1→v1#→v1##
Optimal Storage Selection
The system automatically compares the following storage methods and selects the one with minimum space:
- Normal diff: Reverse difference with previous version
version_name:R6I5:old_content - Hybrid reference: Reference to historical version + diff
version_name:=historical_version:R6I5:new_content
Important: Under the new strategy, the latest version is always a snapshot, historical versions store reverse diffs from new to old.
Example:
2:v1:R8D8I13:original text # v1 is reverse diff (from v2 to v1)
2:v2:R4I8:modified content D2 # v2 is reverse diff (from v3 to v2)
:2:v3:This is latest modified content # v3 is snapshot (latest version)
2:v1#:=v1 # Reference to v1CDN Usage
Besides npm installation, you can also use directly via CDN:
Include via CDN
Note: Text-Version requires the diff-match-patch library as a dependency. You need to include it before the text-version script.
<!-- Include diff-match-patch dependency first -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/diff_match_patch/20121119/diff_match_patch.js"></script>
<!-- Then include text-version UMD version -->
<script src="https://cdn.jsdelivr.net/npm/text-version/dist/index.umd.js"></script>
<!-- Or using unpkg CDN -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/diff_match_patch/20121119/diff_match_patch.js"></script>
<script src="https://unpkg.com/text-version/dist/index.umd.js"></script>Minimal Example
<!DOCTYPE html>
<html>
<head>
<title>Text-Version CDN Example</title>
</head>
<body>
<h1>Text-Version Demo</h1>
<textarea id="input" placeholder="Enter text content..." rows="5" cols="50">Hello, World!</textarea><br><br>
<button onclick="commitVersion()">Commit Version</button>
<button onclick="showLatest()">Show Latest</button>
<button onclick="showLog()">Show Log</button><br><br>
<div>
<h3>Output:</h3>
<pre id="output"></pre>
</div>
<!-- Include diff-match-patch dependency first -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/diff_match_patch/20121119/diff_match_patch.js"></script>
<!-- Then include text-version -->
<script src="https://cdn.jsdelivr.net/npm/text-version/dist/index.umd.js"></script>
<script>
// TextVersion available through global variable window.TextVersion
const tv = new window.TextVersion.TextVersion();
let versionCounter = 1;
function commitVersion() {
const text = document.getElementById('input').value;
const version = `v${versionCounter++}`;
tv.commit(text, version);
document.getElementById('output').textContent =
`Version ${version} committed\nCurrent storage: ${tv.export()}`;
}
function showLatest() {
const latest = tv.latest();
document.getElementById('output').textContent =
`Latest version content:\n${latest}`;
}
function showLog() {
const log = tv.log();
const logText = log.map(info =>
`${info.version} (${info.isSnapshot ? 'snapshot' : 'diff'})`
).join('\n');
document.getElementById('output').textContent =
`Version history:\n${logText}`;
}
</script>
</body>
</html>API Reference
TextVersion
Constructor
new TextVersion(initialStorage?: string, compressionProvider?: CompressionProvider)
new TextVersion(metadata: string, snapshot: string, compressionProvider?: CompressionProvider)Parameters:
Normal Import:
initialStorage(optional): Initial version data string to loadcompressionProvider(optional): Custom compression provider
Separate Import:
metadata: Version record string (containing placeholder)snapshot: Snapshot content stringcompressionProvider(optional): Custom compression provider
Note: When using separate import, the hash value of snapshot content will be validated. An error will be thrown if it doesn't match.
API Methods
commit(text: string, version?: string): this
Submit new version, save text changes.
text: Text content to saveversion: Optional version name, defaults to content hash- Returns:
thisfor method chaining
show(version: string): string | null
Display text content of specified version.
version: Version name to view- Returns: Text content, null if version doesn't exist
log(): VersionInfo[]
Display version history log, get all version information.
- Returns: Array of version information
latest(): string
Get text content of latest version.
- Returns: Text content of latest version
reset(targetVersion: string): this
Reset to specified version, keep the target version and all versions before it, delete all versions after the target version.
targetVersion: Version to reset to (kept)- Returns:
thisfor method chaining
Note: After reset, the target version automatically becomes a snapshot (as it becomes the latest version), and previous versions are converted to differential storage.
squash(targetVersion: string): this
Keep the target version and all versions after it, delete all versions before the specified version, used to reduce storage space.
targetVersion: Starting version to keep (kept, all versions before this will be deleted)- Returns:
thisfor method chaining
Note: This operation is irreversible and will permanently delete all version history before the target version. After squash, the latest version becomes a snapshot, while the target version and intermediate versions are stored as diffs. Suitable for storage space optimization when version history becomes too long.
export(mode?: "monolithic" | "separate"): string | { metadata: string; snapshot: string }
Export current version data.
mode(optional): Export mode"monolithic": Monolithic mode, returns complete version data string (default)"separate": Separate storage, returns object containingmetadataandsnapshot- Defaults to
"monolithic"when not provided
- Returns:
- Monolithic mode: Complete version data string
- Separate mode: Object containing
metadataandsnapshot
Separate Export Explanation:
metadata: Version record string, snapshot content replaced with##[[hash]]##placeholdersnapshot: Complete snapshot content of latest versionhash: 8-character short hash for validating snapshot content integrity
Type Definitions
interface VersionInfo {
version: string; // Version name
isSnapshot: boolean; // Whether it's a snapshot version
}
interface CompressionProvider {
compress(data: string): string;
decompress(data: string): string;
}
interface DiffOperation {
type: 'retain' | 'insert' | 'delete';
length?: number; // Number of characters for retain and delete operations
text?: string; // Text content for insert operations
}Performance Considerations
- Space efficiency: Differential storage significantly reduces storage space, especially for small modifications
- Time complexity:
- Latest version: O(1) time complexity (direct snapshot read) ⚡
- Historical versions: Requires reverse application of diffs from latest snapshot, time depends on version distance
- Snapshot strategy: Latest version is always a snapshot, historical versions store reverse diffs
- Compression: Can be further optimized through custom compression providers
- Storage optimization: Use
squashmethod periodically to clean up historical versions and prevent unlimited storage growth - Use cases: Particularly suitable for applications that frequently access the latest version (e.g., real-time editors, collaborative documents)
Best Practices
- Periodic compression: Use
squashmethod to compress history when version history becomes too long - Reasonable snapshots: Consider keeping important milestone versions as snapshots
- Batch operations: Avoid frequent small modifications, try to commit in batches
- Version naming: Use meaningful version names for easier management and compression operations
License
MIT
Contributing
Issues and Pull Requests are welcome!
