@backloghq/opslog 0.7.1 → 0.8.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 +12 -0
- package/dist/backend.d.ts +1 -0
- package/dist/backend.js +15 -0
- package/dist/snapshot.js +20 -3
- package/dist/types.d.ts +2 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -140,6 +140,18 @@ store.stats() // { activeRecords, opsCount, archiveSegments
|
|
|
140
140
|
await store.refresh() // Reload from all agent WALs (multi-writer only)
|
|
141
141
|
```
|
|
142
142
|
|
|
143
|
+
### Blob Storage (via StorageBackend)
|
|
144
|
+
|
|
145
|
+
```typescript
|
|
146
|
+
await backend.writeBlob("data/file.jsonl", buffer); // Write at relative path
|
|
147
|
+
const buf = await backend.readBlob("data/file.jsonl"); // Read full file
|
|
148
|
+
const range = await backend.readBlobRange("data/file.jsonl", 1024, 256); // Byte-range read
|
|
149
|
+
const names = await backend.listBlobs("data"); // List files under prefix
|
|
150
|
+
await backend.deleteBlob("data/file.jsonl"); // Delete file
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
`readBlobRange` enables O(1) point lookups in JSONL record stores — seek to byte offset, read exact length. FsBackend uses `fs.read` with file offset. S3Backend uses HTTP Range header.
|
|
154
|
+
|
|
143
155
|
## Options
|
|
144
156
|
|
|
145
157
|
```typescript
|
package/dist/backend.d.ts
CHANGED
|
@@ -29,6 +29,7 @@ export declare class FsBackend implements StorageBackend {
|
|
|
29
29
|
getManifestVersion(): Promise<string | null>;
|
|
30
30
|
writeBlob(relativePath: string, content: Buffer): Promise<void>;
|
|
31
31
|
readBlob(relativePath: string): Promise<Buffer>;
|
|
32
|
+
readBlobRange(relativePath: string, offset: number, length: number): Promise<Buffer>;
|
|
32
33
|
listBlobs(prefix: string): Promise<string[]>;
|
|
33
34
|
deleteBlob(relativePath: string): Promise<void>;
|
|
34
35
|
deleteBlobDir(prefix: string): Promise<void>;
|
package/dist/backend.js
CHANGED
|
@@ -140,6 +140,21 @@ export class FsBackend {
|
|
|
140
140
|
async readBlob(relativePath) {
|
|
141
141
|
return readFile(join(this.dir, relativePath));
|
|
142
142
|
}
|
|
143
|
+
async readBlobRange(relativePath, offset, length) {
|
|
144
|
+
if (offset < 0 || length < 0)
|
|
145
|
+
throw new Error("readBlobRange: offset and length must be non-negative");
|
|
146
|
+
if (length === 0)
|
|
147
|
+
return Buffer.alloc(0);
|
|
148
|
+
const fd = await open(join(this.dir, relativePath), "r");
|
|
149
|
+
try {
|
|
150
|
+
const buf = Buffer.alloc(length);
|
|
151
|
+
const { bytesRead } = await fd.read(buf, 0, length, offset);
|
|
152
|
+
return bytesRead < length ? buf.subarray(0, bytesRead) : buf;
|
|
153
|
+
}
|
|
154
|
+
finally {
|
|
155
|
+
await fd.close();
|
|
156
|
+
}
|
|
157
|
+
}
|
|
143
158
|
async listBlobs(prefix) {
|
|
144
159
|
try {
|
|
145
160
|
return await readdir(join(this.dir, prefix));
|
package/dist/snapshot.js
CHANGED
|
@@ -27,9 +27,19 @@ export async function loadSnapshot(dir, relativePath) {
|
|
|
27
27
|
// Detect format: JSONL (first line is header without "records" key) vs legacy JSON
|
|
28
28
|
const firstNewline = content.indexOf("\n");
|
|
29
29
|
const firstLine = firstNewline === -1 ? content : content.slice(0, firstNewline);
|
|
30
|
-
|
|
30
|
+
let parsed;
|
|
31
|
+
try {
|
|
32
|
+
parsed = JSON.parse(firstLine);
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
// First line isn't valid JSON (e.g., pretty-printed legacy JSON starts with "{")
|
|
36
|
+
// Fall back to parsing the full content as legacy JSON
|
|
37
|
+
const snapshot = validateSnapshot(JSON.parse(content));
|
|
38
|
+
const records = new Map(Object.entries(snapshot.records));
|
|
39
|
+
return { records, version: snapshot.version };
|
|
40
|
+
}
|
|
31
41
|
if ("records" in parsed) {
|
|
32
|
-
// Legacy monolithic JSON format
|
|
42
|
+
// Legacy monolithic JSON format (single-line)
|
|
33
43
|
const snapshot = validateSnapshot(parsed);
|
|
34
44
|
const records = new Map(Object.entries(snapshot.records));
|
|
35
45
|
return { records, version: snapshot.version };
|
|
@@ -58,7 +68,14 @@ export async function* streamSnapshotFile(dir, relativePath) {
|
|
|
58
68
|
const content = await readFile(path, "utf-8");
|
|
59
69
|
const firstNewline = content.indexOf("\n");
|
|
60
70
|
const firstLine = firstNewline === -1 ? content : content.slice(0, firstNewline);
|
|
61
|
-
|
|
71
|
+
let parsed;
|
|
72
|
+
try {
|
|
73
|
+
parsed = JSON.parse(firstLine);
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
// Pretty-printed legacy JSON — first line isn't valid JSON
|
|
77
|
+
parsed = JSON.parse(content);
|
|
78
|
+
}
|
|
62
79
|
if ("records" in parsed) {
|
|
63
80
|
// Legacy JSON: must load all, then yield
|
|
64
81
|
const snapshot = validateSnapshot(parsed);
|
package/dist/types.d.ts
CHANGED
|
@@ -115,6 +115,8 @@ export interface StorageBackend {
|
|
|
115
115
|
writeBlob(relativePath: string, content: Buffer): Promise<void>;
|
|
116
116
|
/** Read a blob from a relative path. */
|
|
117
117
|
readBlob(relativePath: string): Promise<Buffer>;
|
|
118
|
+
/** Read a byte range from a blob. For O(1) point lookups in record stores. */
|
|
119
|
+
readBlobRange(relativePath: string, offset: number, length: number): Promise<Buffer>;
|
|
118
120
|
/** List blob names under a prefix directory. */
|
|
119
121
|
listBlobs(prefix: string): Promise<string[]>;
|
|
120
122
|
/** Delete a single blob. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@backloghq/opslog",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.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",
|