Skip to content

Latest commit

 

History

History
410 lines (299 loc) · 7.67 KB

File metadata and controls

410 lines (299 loc) · 7.67 KB

TONL Modification API Guide v2.0.6

Version: 2.0.6 Status: Stable & Production Ready Last Updated: 2025-11-16

Complete guide to modifying TONL documents with the Modification API.


📋 Table of Contents

  1. Basic Operations
  2. Path Creation
  3. Array Operations
  4. Change Tracking
  5. File Operations
  6. Best Practices

Basic Operations

Set Values

The set() method creates or updates values at any path:

const doc = TONLDocument.fromJSON({ user: { name: 'Alice' } });

// Update existing
doc.set('user.name', 'Alice Smith');

// Add new property
doc.set('user.email', 'alice@example.com');

// Method chaining
doc
  .set('user.age', 30)
  .set('user.active', true)
  .set('user.role', 'admin');

Delete Values

The delete() method removes values:

// Delete property
doc.delete('user.temp');

// Delete from nested object
doc.delete('user.profile.old Data');

// Delete array element (with reindexing)
doc.delete('users[0]');

Check Existence

if (doc.exists('user.email')) {
  console.log('Email exists:', doc.get('user.email'));
}

Path Creation

TONL automatically creates intermediate objects/arrays when setting deep paths:

const doc = TONLDocument.fromJSON({});

// Creates: { user: { profile: { settings: { theme: 'dark' } } } }
doc.set('user.profile.settings.theme', 'dark');

// Creates: { items: ['first'] }
doc.set('items[0]', 'first');

// Mixed nesting
doc.set('data.list[0].name', 'Item 1');
// Creates: { data: { list: [{ name: 'Item 1' }] } }

Array Operations

Push & Pop

const doc = TONLDocument.fromJSON({ items: [] });

// Push (add to end)
const newLength = doc.push('items', 1, 2, 3);
console.log('New length:', newLength); // 3

// Pop (remove from end)
const last = doc.pop('items');
console.log('Popped:', last); // 3

Merge Objects

doc.merge('user', {
  email: 'alice@example.com',
  verified: true,
  lastLogin: '2025-01-01'
});
// Shallow merge into user object

Change Tracking

Create Snapshots

// Take a snapshot
const snapshot = doc.snapshot();

// Make changes
doc.set('version', '2.0.0');
doc.set('features.newFeature', true);

// snapshot is unchanged - independent copy
console.log(snapshot.get('version')); // Still '1.0.0'

Generate Diffs

const before = TONLDocument.fromJSON({ a: 1, b: 2, c: 3 });
const after = TONLDocument.fromJSON({ a: 1, b: 3, d: 4 });

const diff = before.diff(after);

console.log(diff.summary);
// {
//   added: 1,      // d was added
//   modified: 1,   // b was changed
//   deleted: 1,    // c was removed
//   total: 3
// }

console.log(diff.changes);
// [
//   { path: 'b', type: 'modified', oldValue: 2, newValue: 3 },
//   { path: 'c', type: 'deleted', oldValue: 3 },
//   { path: 'd', type: 'added', newValue: 4 }
// ]

Human-Readable Diff

console.log(before.diffString(after));
// Changes: 3
//   Added: 1
//   Modified: 1
//   Deleted: 1
//
// ~ b: 2 → 3
// - c (was: 3)
// + d = 4

File Operations

Save Documents

// Synchronous
doc.saveSync('data.tonl');

// Asynchronous
await doc.save('data.tonl');

// With encoding options
await doc.save('data.tonl', {
  delimiter: '|',
  indent: 2,
  includeTypes: true
});

Atomic File Editing

import { FileEditor } from 'tonl';

// Open file (creates backup automatically)
const editor = await FileEditor.open('config.tonl', {
  backup: true,
  backupSuffix: '.bak'
});

// Modify data
editor.data.app.version = '2.0.0';
editor.data.users.push({ name: 'New User' });

// Check if modified
if (editor.isModified()) {
  // Save atomically (temp file + rename)
  await editor.save();
}

// Restore from backup if needed
await editor.restoreBackup();

Best Practices

1. Use Snapshots for Transactions

function updateWithRollback(doc: TONLDocument, updates: () => void) {
  const backup = doc.snapshot();

  try {
    updates();
    return { success: true };
  } catch (error) {
    // Rollback: create new doc from backup
    const restored = backup.snapshot();
    return { success: false, error, backup: restored };
  }
}

// Usage
const result = updateWithRollback(doc, () => {
  doc.set('config.timeout', 5000);
  doc.set('config.retries', 3);
});

2. Track All Changes for Audit

const auditLog: any[] = [];
const initial = doc.snapshot();

// Make changes throughout the day
doc.set('user.lastLogin', new Date().toISOString());
// ... more changes ...

// At end of day, generate audit log
const dailyChanges = doc.diff(initial);
auditLog.push({
  date: new Date(),
  changes: dailyChanges.changes
});

3. Atomic Saves for Critical Data

import { FileEditor } from 'tonl';

// Always use FileEditor for important files
const editor = await FileEditor.open('critical-data.tonl', {
  backup: true
});

try {
  // Make changes
  editor.data.balance += 100;

  // Validate
  if (editor.data.balance < 0) {
    throw new Error('Invalid balance');
  }

  // Save (atomic write + backup)
  await editor.save();
} catch (error) {
  console.error('Failed to update, backup preserved');
}

4. Method Chaining for Readability

doc
  .set('user.name', 'Alice')
  .set('user.email', 'alice@example.com')
  .set('user.verified', true)
  .delete('user.tempToken');

🎓 Advanced Patterns

Conditional Updates

// Only update if exists
if (doc.exists('user.preferences')) {
  doc.merge('user.preferences', { theme: 'dark' });
}

// Update with validation
function safeSet(doc: TONLDocument, path: string, value: any) {
  const type = doc.typeOf(path);

  if (type && typeof value !== type) {
    throw new Error(`Type mismatch: expected ${type}, got ${typeof value}`);
  }

  doc.set(path, value);
}

Bulk Updates

// Update multiple paths
const updates = [
  ['user.firstName', 'Alice'],
  ['user.lastName', 'Smith'],
  ['user.email', 'alice@example.com']
];

for (const [path, value] of updates) {
  doc.set(path, value);
}

Safe Concurrent Modifications

// Lock-free update with retry
function optimisticUpdate(filePath: string, updateFn: (doc: TONLDocument) => void) {
  const original = TONLDocument.fromFileSync(filePath);
  const updated = original.snapshot();

  updateFn(updated);

  // Save and verify no concurrent changes
  updated.saveSync(filePath);
}

🚨 Common Pitfalls

❌ Don't modify returned objects directly

// BAD - modifies internal state without cache reset
const user = doc.get('user');
user.name = 'Bob';  // ❌ Won't work correctly

// GOOD - use set()
doc.set('user.name', 'Bob');  // ✅ Correct

❌ Don't forget to save

// BAD - changes lost
doc.set('important', 'data');
// ... program exits ...

// GOOD - always save
doc.set('important', 'data');
await doc.save('file.tonl');

❌ Don't use delete on root

// BAD - can't delete root
doc.delete('$');  // ❌ Error

// GOOD - delete specific properties
doc.delete('propertyName');  // ✅ Works

📊 Performance Tips

  1. Batch related changes before saving
  2. Use indices for repeated lookups
  3. Use snapshots sparingly (they create deep copies)
  4. Use FileEditor for large files (atomic operations)
  5. Stream when processing multi-GB files

🔗 Related


Happy modifying! 🎉