@backloghq/opslog 0.5.0 → 0.5.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/README.md +28 -3
- package/dist/store.d.ts +12 -1
- package/dist/store.js +28 -5
- package/dist/types.d.ts +2 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -134,7 +134,7 @@ await store.open(dir, {
|
|
|
134
134
|
version: 1, // Schema version
|
|
135
135
|
migrate: (record, fromVersion) => record, // Migration function
|
|
136
136
|
readOnly: false, // Open in read-only mode (default: false)
|
|
137
|
-
writeMode: "immediate", // "immediate" (default)
|
|
137
|
+
writeMode: "immediate", // "immediate" (default), "group" (~12x faster), or "async" (~50x faster, lossy on crash)
|
|
138
138
|
groupCommitSize: 50, // Group: flush after N ops (default: 50)
|
|
139
139
|
groupCommitMs: 100, // Group: flush after N ms (default: 100)
|
|
140
140
|
agentId: "agent-A", // Enable multi-writer mode (optional)
|
|
@@ -165,9 +165,34 @@ await store.flush();
|
|
|
165
165
|
await store.close();
|
|
166
166
|
```
|
|
167
167
|
|
|
168
|
-
|
|
168
|
+
### Async Mode
|
|
169
169
|
|
|
170
|
-
|
|
170
|
+
For maximum write throughput, use `writeMode: "async"`. Writes resolve immediately after buffering in memory — no disk I/O on the hot path. ~50x faster than immediate mode.
|
|
171
|
+
|
|
172
|
+
```typescript
|
|
173
|
+
const store = new Store();
|
|
174
|
+
await store.open("./data", {
|
|
175
|
+
writeMode: "async",
|
|
176
|
+
groupCommitSize: 50,
|
|
177
|
+
groupCommitMs: 100,
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
await store.set("a", valueA); // Returns instantly — buffered in memory
|
|
181
|
+
await store.set("b", valueB); // Same
|
|
182
|
+
|
|
183
|
+
// Ensure durability before exit
|
|
184
|
+
await store.sync();
|
|
185
|
+
await store.close();
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
**Crash semantics:** Data buffered since the last flush is **lost** on unclean shutdown. Call `sync()` before process exit for durability. `close()` always flushes.
|
|
189
|
+
|
|
190
|
+
**Safety:** Forced to `"immediate"` when `agentId` is set (multi-writer mode). Other agents can't see buffered ops, so group/async commit is single-writer only.
|
|
191
|
+
|
|
192
|
+
**When to use which mode:**
|
|
193
|
+
- `"immediate"` (default) — every write is durable. Use when data loss is unacceptable.
|
|
194
|
+
- `"group"` — writes are batched but caller still waits for flush. ~12x faster. Crash loses up to `groupCommitMs` ms of data.
|
|
195
|
+
- `"async"` — writes return instantly. ~50x faster. Crash loses all unflushed data. Best for high-throughput, latency-sensitive, crash-tolerant workloads.
|
|
171
196
|
|
|
172
197
|
## Multi-Writer Mode
|
|
173
198
|
|
package/dist/store.d.ts
CHANGED
|
@@ -18,6 +18,7 @@ export declare class Store<T = Record<string, unknown>> {
|
|
|
18
18
|
private agentId?;
|
|
19
19
|
private clock;
|
|
20
20
|
private groupCommit;
|
|
21
|
+
private asyncMode;
|
|
21
22
|
private groupBuffer;
|
|
22
23
|
private groupSize;
|
|
23
24
|
private groupMs;
|
|
@@ -93,8 +94,18 @@ export declare class Store<T = Record<string, unknown>> {
|
|
|
93
94
|
private applyOp;
|
|
94
95
|
private reverseOp;
|
|
95
96
|
private persistOp;
|
|
96
|
-
/**
|
|
97
|
+
/**
|
|
98
|
+
* Flush buffered ops to disk in a single write.
|
|
99
|
+
* In group/async mode: drains the in-memory buffer to disk.
|
|
100
|
+
* Safe to call at any time. No-op if nothing is buffered.
|
|
101
|
+
*/
|
|
97
102
|
flush(): Promise<void>;
|
|
103
|
+
/**
|
|
104
|
+
* Ensure all buffered operations are durably persisted to disk.
|
|
105
|
+
* Alias for flush(). Use before process exit when using async write mode
|
|
106
|
+
* to prevent data loss.
|
|
107
|
+
*/
|
|
108
|
+
sync(): Promise<void>;
|
|
98
109
|
private flushGroupBuffer;
|
|
99
110
|
private defaultPeriod;
|
|
100
111
|
}
|
package/dist/store.js
CHANGED
|
@@ -29,6 +29,7 @@ export class Store {
|
|
|
29
29
|
clock = null;
|
|
30
30
|
// Group commit state
|
|
31
31
|
groupCommit = false;
|
|
32
|
+
asyncMode = false;
|
|
32
33
|
groupBuffer = [];
|
|
33
34
|
groupSize = 50;
|
|
34
35
|
groupMs = 100;
|
|
@@ -58,13 +59,14 @@ export class Store {
|
|
|
58
59
|
this.backend = backend;
|
|
59
60
|
if (agentId)
|
|
60
61
|
this.agentId = agentId;
|
|
61
|
-
// Group commit: enabled when writeMode is "group" AND not multi-writer
|
|
62
|
-
if (writeMode === "group") {
|
|
62
|
+
// Group/async commit: enabled when writeMode is "group"/"async" AND not multi-writer
|
|
63
|
+
if (writeMode === "group" || writeMode === "async") {
|
|
63
64
|
if (agentId) {
|
|
64
|
-
console.error(
|
|
65
|
+
console.error(`opslog: writeMode '${writeMode}' is not compatible with multi-writer (agentId). Using 'immediate'.`);
|
|
65
66
|
}
|
|
66
67
|
else {
|
|
67
68
|
this.groupCommit = true;
|
|
69
|
+
this.asyncMode = writeMode === "async";
|
|
68
70
|
if (groupCommitSize)
|
|
69
71
|
this.groupSize = groupCommitSize;
|
|
70
72
|
if (groupCommitMs)
|
|
@@ -687,13 +689,22 @@ export class Store {
|
|
|
687
689
|
// Buffer the op, flush when buffer is full or timer fires
|
|
688
690
|
this.groupBuffer.push(op);
|
|
689
691
|
if (this.groupBuffer.length >= this.groupSize) {
|
|
690
|
-
|
|
692
|
+
if (this.asyncMode) {
|
|
693
|
+
// Async: trigger flush in background, don't await
|
|
694
|
+
this.serialize(() => this.flushGroupBuffer()).catch(() => { });
|
|
695
|
+
}
|
|
696
|
+
else {
|
|
697
|
+
await this.flushGroupBuffer();
|
|
698
|
+
}
|
|
691
699
|
}
|
|
692
700
|
else if (!this.groupTimer) {
|
|
693
701
|
this.groupTimer = setTimeout(() => {
|
|
694
702
|
this.serialize(() => this.flushGroupBuffer()).catch(() => { });
|
|
695
703
|
}, this.groupMs);
|
|
696
704
|
}
|
|
705
|
+
// Async mode: return immediately without waiting for disk I/O
|
|
706
|
+
if (this.asyncMode)
|
|
707
|
+
return;
|
|
697
708
|
}
|
|
698
709
|
else {
|
|
699
710
|
// Immediate: write to disk now
|
|
@@ -703,12 +714,24 @@ export class Store {
|
|
|
703
714
|
await this._compact();
|
|
704
715
|
}
|
|
705
716
|
}
|
|
706
|
-
/**
|
|
717
|
+
/**
|
|
718
|
+
* Flush buffered ops to disk in a single write.
|
|
719
|
+
* In group/async mode: drains the in-memory buffer to disk.
|
|
720
|
+
* Safe to call at any time. No-op if nothing is buffered.
|
|
721
|
+
*/
|
|
707
722
|
async flush() {
|
|
708
723
|
if (!this.groupCommit || this.groupBuffer.length === 0)
|
|
709
724
|
return;
|
|
710
725
|
return this.serialize(() => this.flushGroupBuffer());
|
|
711
726
|
}
|
|
727
|
+
/**
|
|
728
|
+
* Ensure all buffered operations are durably persisted to disk.
|
|
729
|
+
* Alias for flush(). Use before process exit when using async write mode
|
|
730
|
+
* to prevent data loss.
|
|
731
|
+
*/
|
|
732
|
+
async sync() {
|
|
733
|
+
return this.flush();
|
|
734
|
+
}
|
|
712
735
|
async flushGroupBuffer() {
|
|
713
736
|
if (this.groupBuffer.length === 0)
|
|
714
737
|
return;
|
package/dist/types.d.ts
CHANGED
|
@@ -58,8 +58,8 @@ export interface StoreOptions {
|
|
|
58
58
|
backend?: StorageBackend;
|
|
59
59
|
/** Agent ID for multi-writer mode. Enables per-agent WAL streams and LWW conflict resolution. */
|
|
60
60
|
agentId?: string;
|
|
61
|
-
/** Write mode: "immediate" flushes every op (default, safe for multi-writer). "group" buffers ops and flushes periodically (~
|
|
62
|
-
writeMode?: "immediate" | "group";
|
|
61
|
+
/** Write mode: "immediate" flushes every op (default, safe for multi-writer). "group" buffers ops and flushes periodically (~12x faster writes). "async" buffers ops and resolves immediately without waiting for flush (~50x faster, data lost on crash). Forced to "immediate" when agentId is set. */
|
|
62
|
+
writeMode?: "immediate" | "group" | "async";
|
|
63
63
|
/** Group commit: max ops to buffer before flush (default: 50). Only used when writeMode is "group". */
|
|
64
64
|
groupCommitSize?: number;
|
|
65
65
|
/** Group commit: max milliseconds before flush (default: 100). Only used when writeMode is "group". */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@backloghq/opslog",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.1",
|
|
4
4
|
"description": "Embedded event-sourced document store. Append-only operation log with immutable snapshots, zero native dependencies.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|