@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 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) or "group" (buffered, ~12x faster)
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
- **Safety:** Forced to `"immediate"` when `agentId` is set (multi-writer mode). Other agents can't see buffered ops, so group commit is single-writer only.
168
+ ### Async Mode
169
169
 
170
- **Tradeoff:** A crash can lose ops that haven't been flushed yet (up to `groupCommitMs` milliseconds of data). Use `"immediate"` (default) when every write must be durable.
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
- /** Flush buffered group commit ops to disk in a single write. */
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("opslog: writeMode 'group' is not compatible with multi-writer (agentId). Using 'immediate'.");
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
- await this.flushGroupBuffer();
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
- /** Flush buffered group commit ops to disk in a single write. */
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 (~50x faster writes). Forced to "immediate" when agentId is set. */
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.0",
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",