0
# OpLog System
1
2
The OpLog (Operation Log) is OrbitDB's core data structure - an immutable, cryptographically verifiable, operation-based CRDT (Conflict-free Replicated Data Type) that enables distributed consensus without coordination. All OrbitDB databases are built on top of the OpLog.
3
4
## Capabilities
5
6
### Log Operations
7
8
The Log provides the fundamental operations for building distributed data structures.
9
10
```javascript { .api }
11
/**
12
* Appends an entry to the log
13
* @param entry The entry to append
14
* @returns Promise resolving to the entry hash
15
*/
16
append(entry: Entry): Promise<string>;
17
18
/**
19
* Joins this log with another log, merging their histories
20
* @param log The log to join with
21
* @returns Promise resolving to the merged log
22
*/
23
join(log: Log): Promise<Log>;
24
25
interface Log {
26
/** Current heads of the log (latest entries) */
27
heads: Entry[];
28
/** All entries in the log */
29
entries: Entry[];
30
/** Number of entries in the log */
31
length: number;
32
/** Unique identifier for this log */
33
id: string;
34
/** Access controller for this log */
35
accessController: AccessController;
36
}
37
```
38
39
**Usage Examples:**
40
41
```javascript
42
import { Log } from '@orbitdb/core';
43
44
// Logs are typically accessed through database instances
45
const ipfs = await createHelia();
46
const orbitdb = await createOrbitDB({ ipfs });
47
const db = await orbitdb.open('my-events');
48
49
// Access the underlying log
50
const log = db.log;
51
52
console.log('Log heads:', log.heads);
53
console.log('Total entries:', log.length);
54
55
// Add data (which creates log entries)
56
await db.add('Hello World');
57
console.log('New length:', log.length);
58
```
59
60
### Entry Structure
61
62
Log entries are the atomic units of data in OrbitDB, containing the operation, metadata, and cryptographic proofs.
63
64
```javascript { .api }
65
interface Entry {
66
/** Unique hash identifying this entry */
67
hash: string;
68
/** The operation payload */
69
payload: {
70
/** Operation type (ADD, PUT, DEL, etc.) */
71
op: string;
72
/** Operation key (null for some operations) */
73
key: string | null;
74
/** Operation value */
75
value: any;
76
};
77
/** Identity that created this entry */
78
identity: string;
79
/** Cryptographic signature */
80
sig: string;
81
/** Logical clock for ordering */
82
clock: Clock;
83
/** References to previous entries */
84
refs: string[];
85
/** Additional entry metadata */
86
meta?: object;
87
}
88
```
89
90
**Usage Examples:**
91
92
```javascript
93
// Entries are created automatically when you perform database operations
94
const db = await orbitdb.open('my-events');
95
const hash = await db.add('My event data');
96
97
// Find the entry by hash
98
const entries = db.log.entries;
99
const entry = entries.find(e => e.hash === hash);
100
101
console.log('Entry hash:', entry.hash);
102
console.log('Operation:', entry.payload.op);
103
console.log('Value:', entry.payload.value);
104
console.log('Creator:', entry.identity);
105
console.log('Clock:', entry.clock);
106
```
107
108
### Entry Creation and Verification
109
110
Entries can be created and verified independently of the database context.
111
112
```javascript { .api }
113
/**
114
* Creates a new log entry
115
* @param identity The identity creating the entry
116
* @param operation The operation data
117
* @param clock Optional logical clock
118
* @returns Promise resolving to new Entry
119
*/
120
Entry.create(
121
identity: Identity,
122
operation: { op: string; key?: string; value?: any },
123
clock?: Clock
124
): Promise<Entry>;
125
126
/**
127
* Verifies an entry's signature and integrity
128
* @param entry The entry to verify
129
* @returns Promise resolving to true if valid
130
*/
131
Entry.verify(entry: Entry): Promise<boolean>;
132
```
133
134
**Usage Examples:**
135
136
```javascript
137
import { Entry } from '@orbitdb/core';
138
139
// Create a custom entry (typically done internally)
140
const identity = orbitdb.identity;
141
const operation = { op: 'ADD', value: 'Custom data' };
142
const entry = await Entry.create(identity, operation);
143
144
// Verify entry integrity
145
const isValid = await Entry.verify(entry);
146
console.log('Entry is valid:', isValid);
147
```
148
149
### Logical Clocks
150
151
Logical clocks provide causal ordering for distributed operations without requiring synchronized time.
152
153
```javascript { .api }
154
interface Clock {
155
/** Unique identifier for the clock (usually identity ID) */
156
id: string;
157
/** Logical time value */
158
time: number;
159
}
160
161
/**
162
* Creates a new clock
163
* @param id Clock identifier
164
* @param time Initial time (default: 0)
165
* @returns New Clock instance
166
*/
167
Clock.create(id: string, time?: number): Clock;
168
169
/**
170
* Compares two clocks for ordering
171
* @param a First clock
172
* @param b Second clock
173
* @returns -1, 0, or 1 for less than, equal, or greater than
174
*/
175
Clock.compare(a: Clock, b: Clock): number;
176
177
/**
178
* Advances a clock
179
* @param clock Clock to advance
180
* @param other Optional other clock to sync with
181
* @returns New advanced clock
182
*/
183
Clock.tick(clock: Clock, other?: Clock): Clock;
184
```
185
186
**Usage Examples:**
187
188
```javascript
189
import { Clock } from '@orbitdb/core';
190
191
// Clocks are typically managed automatically
192
const db = await orbitdb.open('my-events');
193
await db.add('First event');
194
await db.add('Second event');
195
196
// Examine the logical clocks in entries
197
const entries = db.log.entries;
198
entries.forEach((entry, index) => {
199
console.log(`Entry ${index}:`, entry.clock);
200
});
201
202
// Create custom clocks (advanced usage)
203
const clock1 = Clock.create('peer1', 0);
204
const clock2 = Clock.create('peer2', 0);
205
const clock3 = Clock.tick(clock1); // Advances time to 1
206
```
207
208
### Default Access Controller
209
210
The OpLog includes a default access controller that allows all operations.
211
212
```javascript { .api }
213
/**
214
* Creates a default access controller that allows all operations
215
* @returns Promise resolving to access controller instance
216
*/
217
function DefaultAccessController(): Promise<AccessController>;
218
219
interface AccessController {
220
/** Checks if an entry can be appended (always returns true for default) */
221
canAppend(entry: Entry): Promise<boolean>;
222
}
223
```
224
225
**Usage Examples:**
226
227
```javascript
228
import { DefaultAccessController } from '@orbitdb/core';
229
230
// The default access controller is used automatically
231
// when no specific access controller is provided
232
const defaultAC = await DefaultAccessController();
233
console.log(await defaultAC.canAppend(someEntry)); // Always true
234
```
235
236
### Conflict Resolution
237
238
The OpLog system automatically handles conflicts when merging concurrent operations from different peers.
239
240
```javascript { .api }
241
interface ConflictResolution {
242
/** Resolves conflicts between concurrent entries */
243
resolve(entries: Entry[]): Entry[];
244
}
245
```
246
247
**Usage Examples:**
248
249
```javascript
250
// Conflict resolution happens automatically during log joins
251
const db1 = await orbitdb1.open('/orbitdb/shared-address');
252
const db2 = await orbitdb2.open('/orbitdb/shared-address');
253
254
// Both peers add concurrent entries
255
await db1.add('From peer 1');
256
await db2.add('From peer 2');
257
258
// When peers sync, conflicts are resolved automatically
259
await db1.sync(db2.log.heads);
260
const allEntries = await db1.all();
261
console.log('Merged data:', allEntries); // Contains both entries
262
```
263
264
### Log Synchronization
265
266
Logs can be synchronized between peers to maintain eventual consistency.
267
268
```javascript { .api }
269
/**
270
* Synchronizes this log with heads from another log
271
* @param heads Array of head entries from another log
272
* @returns Promise resolving when sync is complete
273
*/
274
sync(heads: Entry[]): Promise<void>;
275
276
/**
277
* Gets the difference between this log and another
278
* @param other Another log to compare with
279
* @returns Array of entries that are in other but not in this log
280
*/
281
difference(other: Log): Entry[];
282
```
283
284
**Usage Examples:**
285
286
```javascript
287
// Manual synchronization between database instances
288
const db1 = await orbitdb1.open('shared-db');
289
const db2 = await orbitdb2.open('shared-db');
290
291
// Add data to first database
292
await db1.add('Data from peer 1');
293
294
// Sync second database with first
295
await db2.sync(db1.log.heads);
296
297
// Both databases now have the same data
298
const data1 = await db1.all();
299
const data2 = await db2.all();
300
console.log('Synchronized:', JSON.stringify(data1) === JSON.stringify(data2));
301
```
302
303
### Advanced Log Operations
304
305
Access lower-level log operations for custom database implementations.
306
307
```javascript { .api }
308
interface Log {
309
/** Traverse log entries with optional filtering */
310
traverse(options?: {
311
amount?: number;
312
gt?: string;
313
gte?: string;
314
lt?: string;
315
lte?: string;
316
}): AsyncIterable<Entry>;
317
318
/** Get entries by their hashes */
319
get(hashes: string[]): Entry[];
320
321
/** Check if log contains specific entries */
322
has(hash: string): boolean;
323
324
/** Get log statistics */
325
stats(): {
326
length: number;
327
heads: number;
328
unique: number;
329
};
330
}
331
```
332
333
**Usage Examples:**
334
335
```javascript
336
const db = await orbitdb.open('my-events');
337
338
// Add some data
339
await db.add('Event 1');
340
await db.add('Event 2');
341
await db.add('Event 3');
342
343
const log = db.log;
344
345
// Traverse recent entries
346
for await (const entry of log.traverse({ amount: 2 })) {
347
console.log('Recent entry:', entry.payload.value);
348
}
349
350
// Check log statistics
351
const stats = log.stats();
352
console.log('Log stats:', stats);
353
354
// Check if specific entry exists
355
const hasEntry = log.has(someEntryHash);
356
console.log('Entry exists:', hasEntry);
357
```
358
359
### Custom Operations
360
361
When building custom database types, you can define custom operation types.
362
363
```javascript
364
// Example: Custom counter database operations
365
const CounterOperations = {
366
INCREMENT: 'INCREMENT',
367
DECREMENT: 'DECREMENT',
368
RESET: 'RESET'
369
};
370
371
// Custom database implementation
372
const CounterDB = () => async (params) => {
373
const database = await Database(params);
374
const { addOperation } = database;
375
376
const increment = async () => {
377
return addOperation({
378
op: CounterOperations.INCREMENT,
379
key: null,
380
value: 1
381
});
382
};
383
384
const decrement = async () => {
385
return addOperation({
386
op: CounterOperations.DECREMENT,
387
key: null,
388
value: -1
389
});
390
};
391
392
const reset = async () => {
393
return addOperation({
394
op: CounterOperations.RESET,
395
key: null,
396
value: 0
397
});
398
};
399
400
return {
401
...database,
402
increment,
403
decrement,
404
reset
405
};
406
};
407
```
408
409
## Error Handling
410
411
Handle common OpLog-related errors:
412
413
```javascript
414
try {
415
const db = await orbitdb.open('my-db');
416
await db.add('Some data');
417
} catch (error) {
418
if (error.message.includes('Access denied')) {
419
console.error('No permission to write to this database');
420
} else if (error.message.includes('Invalid entry')) {
421
console.error('Entry validation failed');
422
} else {
423
console.error('Unexpected error:', error.message);
424
}
425
}
426
```
427
428
The OpLog system provides the foundation for all OrbitDB operations, ensuring data integrity, conflict resolution, and eventual consistency across distributed peers.