@claudexor/journal 3.10.0 → 3.10.2
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/dist/index.d.ts +18 -39
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +139 -129
- package/dist/index.js.map +1 -1
- package/dist/journal-background-compaction.d.ts +23 -0
- package/dist/journal-background-compaction.d.ts.map +1 -0
- package/dist/journal-background-compaction.js +189 -0
- package/dist/journal-background-compaction.js.map +1 -0
- package/dist/journal-compaction.d.ts +18 -2
- package/dist/journal-compaction.d.ts.map +1 -1
- package/dist/journal-compaction.js +76 -64
- package/dist/journal-compaction.js.map +1 -1
- package/dist/journal-cursor.d.ts +1 -0
- package/dist/journal-cursor.d.ts.map +1 -1
- package/dist/journal-cursor.js +28 -0
- package/dist/journal-cursor.js.map +1 -1
- package/dist/journal-files.d.ts +8 -0
- package/dist/journal-files.d.ts.map +1 -1
- package/dist/journal-files.js +71 -1
- package/dist/journal-files.js.map +1 -1
- package/dist/journal-recovery-state.d.ts +40 -0
- package/dist/journal-recovery-state.d.ts.map +1 -0
- package/dist/journal-recovery-state.js +35 -0
- package/dist/journal-recovery-state.js.map +1 -0
- package/dist/journal-recovery.d.ts +9 -0
- package/dist/journal-recovery.d.ts.map +1 -0
- package/dist/journal-recovery.js +42 -0
- package/dist/journal-recovery.js.map +1 -0
- package/package.json +2 -2
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { constants } from "node:fs";
|
|
3
|
+
import { open, unlink } from "node:fs/promises";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { Readable, Writable } from "node:stream";
|
|
6
|
+
import { pipeline } from "node:stream/promises";
|
|
7
|
+
import { setImmediate } from "node:timers/promises";
|
|
8
|
+
import { createGzip } from "node:zlib";
|
|
9
|
+
import { encodeJournalPayload } from "./append-batch.js";
|
|
10
|
+
import { HASH_BYTES, MAX_COMPACTED_LOGICAL_BYTES, MAX_PAYLOAD_BYTES, encodeFrame, } from "./frame-codec.js";
|
|
11
|
+
import { compactedJournalRecord, encodeCompactionSnapshot, isCompactionCapacityError, logicalRecord, } from "./journal-compaction.js";
|
|
12
|
+
// A working batch, never a history retention limit or a user setting.
|
|
13
|
+
const STREAM_BATCH_BYTES = 64 * 1024;
|
|
14
|
+
const COMPACTION_FILE_PREFIX = "journal-compaction-";
|
|
15
|
+
/** The journal owns freshness and installation; this helper owns only its
|
|
16
|
+
* temporary file and immutable logical-record references. */
|
|
17
|
+
export async function prepareBackgroundCompaction(input) {
|
|
18
|
+
let handle = null;
|
|
19
|
+
let path = null;
|
|
20
|
+
try {
|
|
21
|
+
await setImmediate(undefined, { signal: input.signal });
|
|
22
|
+
const compressed = await compressRecords(input.entries, input.prefix.count, input.signal);
|
|
23
|
+
const snapshot = encodeCompactionSnapshot({
|
|
24
|
+
partition: input.partition,
|
|
25
|
+
epoch: input.epoch,
|
|
26
|
+
time: input.time,
|
|
27
|
+
count: input.prefix.count,
|
|
28
|
+
compressed,
|
|
29
|
+
});
|
|
30
|
+
if (!snapshot || snapshot.frame.length >= input.prefix.knownFileBytes)
|
|
31
|
+
return null;
|
|
32
|
+
input.signal.throwIfAborted();
|
|
33
|
+
const records = [];
|
|
34
|
+
for (let index = 0; index < input.prefix.count; index += 1) {
|
|
35
|
+
records.push(compactedJournalRecord(input.entries[index], input.partition, input.epoch, index, snapshot.frameHash));
|
|
36
|
+
if ((index + 1) % (STREAM_BATCH_BYTES / 128) === 0)
|
|
37
|
+
await setImmediate(undefined, { signal: input.signal });
|
|
38
|
+
}
|
|
39
|
+
input.signal.throwIfAborted();
|
|
40
|
+
path = join(input.stagingDir, `${COMPACTION_FILE_PREFIX}${randomUUID()}.compact`);
|
|
41
|
+
handle = await open(path, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL, 0o600);
|
|
42
|
+
await writeBytes(handle, snapshot.frame, input.signal);
|
|
43
|
+
const candidate = {
|
|
44
|
+
path,
|
|
45
|
+
records,
|
|
46
|
+
epoch: input.epoch,
|
|
47
|
+
nextSeq: input.prefix.nextSeq,
|
|
48
|
+
previousFrameHash: snapshot.frameHash,
|
|
49
|
+
knownFileBytes: snapshot.frame.length,
|
|
50
|
+
receipt: {
|
|
51
|
+
beforeBytes: input.prefix.knownFileBytes,
|
|
52
|
+
afterBytes: snapshot.frame.length,
|
|
53
|
+
records: input.prefix.count,
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
for (;;) {
|
|
57
|
+
input.signal.throwIfAborted();
|
|
58
|
+
// appendBatch publishes its entries/counters together, after ACK. Capture
|
|
59
|
+
// only complete batches, even when encoding yields partway through one.
|
|
60
|
+
const boundary = input.current();
|
|
61
|
+
await appendTail(handle, candidate, input.entries, boundary.count, input.signal);
|
|
62
|
+
await handle.sync();
|
|
63
|
+
input.signal.throwIfAborted();
|
|
64
|
+
if (!sameBoundary(boundary, input.current()))
|
|
65
|
+
continue;
|
|
66
|
+
// Windows requires both the old canonical writer and stage handle closed
|
|
67
|
+
// before rename. Closing can await; appends during it must be caught too.
|
|
68
|
+
await handle.close();
|
|
69
|
+
handle = null;
|
|
70
|
+
input.signal.throwIfAborted();
|
|
71
|
+
candidate.receipt = {
|
|
72
|
+
beforeBytes: boundary.knownFileBytes,
|
|
73
|
+
afterBytes: candidate.knownFileBytes,
|
|
74
|
+
records: candidate.records.length,
|
|
75
|
+
};
|
|
76
|
+
if (input.install(candidate, boundary))
|
|
77
|
+
return candidate.receipt;
|
|
78
|
+
input.signal.throwIfAborted();
|
|
79
|
+
handle = await open(path, constants.O_WRONLY | constants.O_APPEND | constants.O_NOFOLLOW);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
catch (error) {
|
|
83
|
+
if ((input.signal.aborted &&
|
|
84
|
+
(error === input.signal.reason ||
|
|
85
|
+
(error instanceof Error && error.name === "AbortError"))) ||
|
|
86
|
+
isCompactionCapacityError(error))
|
|
87
|
+
return null;
|
|
88
|
+
throw error;
|
|
89
|
+
}
|
|
90
|
+
finally {
|
|
91
|
+
try {
|
|
92
|
+
await handle?.close();
|
|
93
|
+
}
|
|
94
|
+
finally {
|
|
95
|
+
if (path)
|
|
96
|
+
await unlink(path).catch((error) => {
|
|
97
|
+
if (error.code !== "ENOENT")
|
|
98
|
+
throw error;
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
export function sameBoundary(a, b) {
|
|
104
|
+
return (a.count === b.count &&
|
|
105
|
+
a.nextSeq === b.nextSeq &&
|
|
106
|
+
a.previousFrameHash === b.previousFrameHash &&
|
|
107
|
+
a.knownFileBytes === b.knownFileBytes);
|
|
108
|
+
}
|
|
109
|
+
async function compressRecords(entries, count, signal) {
|
|
110
|
+
const chunks = [];
|
|
111
|
+
let compressedBytes = 0;
|
|
112
|
+
await pipeline(Readable.from(serializeRecords(entries, count, signal), {
|
|
113
|
+
objectMode: false,
|
|
114
|
+
highWaterMark: STREAM_BATCH_BYTES,
|
|
115
|
+
}), createGzip(), new Writable({
|
|
116
|
+
write(chunk, _encoding, done) {
|
|
117
|
+
compressedBytes += chunk.length;
|
|
118
|
+
if (compressedBytes > MAX_PAYLOAD_BYTES)
|
|
119
|
+
return done(capacityError("compressed bytes"));
|
|
120
|
+
chunks.push(chunk);
|
|
121
|
+
done();
|
|
122
|
+
},
|
|
123
|
+
}), { signal });
|
|
124
|
+
return Buffer.concat(chunks, compressedBytes);
|
|
125
|
+
}
|
|
126
|
+
async function* serializeRecords(entries, count, signal) {
|
|
127
|
+
let totalBytes = 2; // brackets
|
|
128
|
+
let batchBytes = 0;
|
|
129
|
+
yield "[";
|
|
130
|
+
for (let index = 0; index < count; index += 1) {
|
|
131
|
+
signal.throwIfAborted();
|
|
132
|
+
const json = JSON.stringify(logicalRecord(entries[index]));
|
|
133
|
+
const bytes = Buffer.byteLength(json, "utf8") + (index === 0 ? 0 : 1);
|
|
134
|
+
totalBytes += bytes;
|
|
135
|
+
if (totalBytes > MAX_COMPACTED_LOGICAL_BYTES)
|
|
136
|
+
throw capacityError("logical bytes");
|
|
137
|
+
if (index > 0)
|
|
138
|
+
yield ",";
|
|
139
|
+
yield json;
|
|
140
|
+
batchBytes += bytes;
|
|
141
|
+
if (batchBytes >= STREAM_BATCH_BYTES) {
|
|
142
|
+
await setImmediate(undefined, { signal });
|
|
143
|
+
batchBytes = 0;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
yield "]";
|
|
147
|
+
}
|
|
148
|
+
async function appendTail(handle, candidate, entries, count, signal) {
|
|
149
|
+
for (let index = candidate.records.length; index < count; index += 1) {
|
|
150
|
+
signal.throwIfAborted();
|
|
151
|
+
const record = entries[index];
|
|
152
|
+
const header = {
|
|
153
|
+
partition: record.partition,
|
|
154
|
+
epoch: record.epoch,
|
|
155
|
+
seq: record.seq,
|
|
156
|
+
previousFrameHash: candidate.previousFrameHash,
|
|
157
|
+
time: record.time,
|
|
158
|
+
type: record.type,
|
|
159
|
+
};
|
|
160
|
+
const frame = encodeFrame(header, encodeJournalPayload(record.payload));
|
|
161
|
+
const frameHash = frame.subarray(frame.length - HASH_BYTES).toString("hex");
|
|
162
|
+
await writeBytes(handle, frame, signal);
|
|
163
|
+
candidate.records.push({
|
|
164
|
+
...header,
|
|
165
|
+
frameHash,
|
|
166
|
+
payload: record.payload,
|
|
167
|
+
byteOffset: candidate.knownFileBytes,
|
|
168
|
+
});
|
|
169
|
+
candidate.previousFrameHash = frameHash;
|
|
170
|
+
candidate.knownFileBytes += frame.length;
|
|
171
|
+
candidate.nextSeq = record.seq + 1;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
async function writeBytes(handle, bytes, signal) {
|
|
175
|
+
let offset = 0;
|
|
176
|
+
while (offset < bytes.length) {
|
|
177
|
+
signal.throwIfAborted();
|
|
178
|
+
const { bytesWritten } = await handle.write(bytes, offset, bytes.length - offset);
|
|
179
|
+
if (bytesWritten === 0)
|
|
180
|
+
throw new Error("journal compaction write made no progress");
|
|
181
|
+
offset += bytesWritten;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
function capacityError(kind) {
|
|
185
|
+
return Object.assign(new Error(`journal compaction exceeds the existing ${kind} cap`), {
|
|
186
|
+
code: "journal_compaction_capacity",
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
//# sourceMappingURL=journal-background-compaction.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"journal-background-compaction.js","sourceRoot":"","sources":["../src/journal-background-compaction.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import { type JournalRecord } from "./frame-codec.js";
|
|
1
|
+
import { type CompactedRecord, type JournalRecord } from "./frame-codec.js";
|
|
2
2
|
export interface JournalCompactionResult {
|
|
3
|
+
path: string;
|
|
3
4
|
receipt: {
|
|
4
5
|
beforeBytes: number;
|
|
5
6
|
afterBytes: number;
|
|
@@ -11,11 +12,26 @@ export interface JournalCompactionResult {
|
|
|
11
12
|
previousFrameHash: string;
|
|
12
13
|
knownFileBytes: number;
|
|
13
14
|
}
|
|
14
|
-
|
|
15
|
+
/** Prepare a fsynced candidate. Only DurableJournal installs canonical bytes. */
|
|
16
|
+
export declare function prepareJournalCompaction(input: {
|
|
15
17
|
path: string;
|
|
16
18
|
partition: string;
|
|
17
19
|
entries: readonly JournalRecord[];
|
|
18
20
|
knownFileBytes: number;
|
|
19
21
|
now: () => Date;
|
|
20
22
|
}): JournalCompactionResult | null;
|
|
23
|
+
export declare function logicalRecord(record: CompactedRecord, payload?: unknown): CompactedRecord;
|
|
24
|
+
/** Both producers share the versioned envelope and existing materialization caps. */
|
|
25
|
+
export declare function encodeCompactionSnapshot(input: {
|
|
26
|
+
partition: string;
|
|
27
|
+
epoch: string;
|
|
28
|
+
time: string;
|
|
29
|
+
count: number;
|
|
30
|
+
compressed: Buffer;
|
|
31
|
+
}): {
|
|
32
|
+
frame: Buffer;
|
|
33
|
+
frameHash: string;
|
|
34
|
+
} | null;
|
|
35
|
+
export declare function compactedJournalRecord(record: CompactedRecord, partition: string, epoch: string, index: number, frameHash: string, payload?: unknown): JournalRecord;
|
|
36
|
+
export declare function isCompactionCapacityError(error: unknown): boolean;
|
|
21
37
|
//# sourceMappingURL=journal-compaction.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"journal-compaction.d.ts","sourceRoot":"","sources":["../src/journal-compaction.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"journal-compaction.d.ts","sourceRoot":"","sources":["../src/journal-compaction.ts"],"names":[],"mappings":"AAIA,OAAO,EAOL,KAAK,eAAe,EAGpB,KAAK,aAAa,EACnB,MAAM,kBAAkB,CAAC;AAG1B,MAAM,WAAW,uBAAuB;IACtC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE;QAAE,WAAW,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,CAAC;IACtE,OAAO,EAAE,aAAa,EAAE,CAAC;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,iFAAiF;AACjF,wBAAgB,wBAAwB,CAAC,KAAK,EAAE;IAC9C,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,SAAS,aAAa,EAAE,CAAC;IAClC,cAAc,EAAE,MAAM,CAAC;IACvB,GAAG,EAAE,MAAM,IAAI,CAAC;CACjB,GAAG,uBAAuB,GAAG,IAAI,CA8EjC;AAED,wBAAgB,aAAa,CAAC,MAAM,EAAE,eAAe,EAAE,OAAO,UAAiB,GAAG,eAAe,CAEhG;AAED,qFAAqF;AACrF,wBAAgB,wBAAwB,CAAC,KAAK,EAAE;IAC9C,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;CACpB,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CA6B9C;AAED,wBAAgB,sBAAsB,CACpC,MAAM,EAAE,eAAe,EACvB,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,EACb,SAAS,EAAE,MAAM,EACjB,OAAO,UAAiB,GACvB,aAAa,CAYf;AAED,wBAAgB,yBAAyB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAgBjE"}
|
|
@@ -1,21 +1,16 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
|
-
import { closeSync, constants, openSync,
|
|
3
|
-
import { dirname } from "node:path";
|
|
2
|
+
import { closeSync, constants, openSync, rmSync } from "node:fs";
|
|
4
3
|
import { gzipSync } from "node:zlib";
|
|
5
|
-
import { fsyncDirectory } from "@claudexor/util";
|
|
6
4
|
import { encodeJournalPayload } from "./append-batch.js";
|
|
7
5
|
import { COMPACTED_SNAPSHOT, HASH_BYTES, MAX_COMPACTED_LOGICAL_BYTES, MAX_PAYLOAD_BYTES, ZERO_HASH, encodeFrame, } from "./frame-codec.js";
|
|
8
6
|
import { appendAndSync } from "./journal-files.js";
|
|
9
|
-
|
|
7
|
+
/** Prepare a fsynced candidate. Only DurableJournal installs canonical bytes. */
|
|
8
|
+
export function prepareJournalCompaction(input) {
|
|
10
9
|
if (input.entries.length === 0)
|
|
11
10
|
return null;
|
|
12
11
|
let logical;
|
|
13
12
|
try {
|
|
14
|
-
logical = input.entries.map((record) => (
|
|
15
|
-
time: record.time,
|
|
16
|
-
type: record.type,
|
|
17
|
-
payload: cloneJson(record.payload),
|
|
18
|
-
}));
|
|
13
|
+
logical = input.entries.map((record) => logicalRecord(record, cloneJson(record.payload)));
|
|
19
14
|
}
|
|
20
15
|
catch (error) {
|
|
21
16
|
if (isCompactionCapacityError(error))
|
|
@@ -46,61 +41,20 @@ export function compactJournalFile(input) {
|
|
|
46
41
|
return null;
|
|
47
42
|
throw error;
|
|
48
43
|
}
|
|
49
|
-
// A base64 representation plus its JSON envelope is always larger than the
|
|
50
|
-
// compressed bytes, so this snapshot cannot fit the frame payload. Avoid
|
|
51
|
-
// creating an unnecessarily large string before returning the no-op.
|
|
52
|
-
if (compressed.length > MAX_PAYLOAD_BYTES)
|
|
53
|
-
return null;
|
|
54
|
-
// Base64 and payload JSON are also materialization steps. Keep them inside
|
|
55
|
-
// the same capacity boundary so an otherwise readable journal remains the
|
|
56
|
-
// authoritative file when either conversion hits a runtime limit.
|
|
57
|
-
let payload;
|
|
58
|
-
let payloadBytes;
|
|
59
|
-
try {
|
|
60
|
-
payload = {
|
|
61
|
-
version: 1,
|
|
62
|
-
count: logical.length,
|
|
63
|
-
encoding: "gzip-base64",
|
|
64
|
-
data: compressed.toString("base64"),
|
|
65
|
-
};
|
|
66
|
-
payloadBytes = encodeJournalPayload(payload);
|
|
67
|
-
}
|
|
68
|
-
catch (error) {
|
|
69
|
-
if (isCompactionCapacityError(error))
|
|
70
|
-
return null;
|
|
71
|
-
throw error;
|
|
72
|
-
}
|
|
73
|
-
if (payloadBytes.length > MAX_PAYLOAD_BYTES)
|
|
74
|
-
return null;
|
|
75
44
|
const epoch = randomUUID();
|
|
76
|
-
const
|
|
45
|
+
const snapshot = encodeCompactionSnapshot({
|
|
77
46
|
partition: input.partition,
|
|
78
47
|
epoch,
|
|
79
|
-
seq: 1,
|
|
80
|
-
previousFrameHash: ZERO_HASH,
|
|
81
48
|
time: input.now().toISOString(),
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
};
|
|
85
|
-
|
|
86
|
-
if (frame.length >= input.knownFileBytes)
|
|
49
|
+
count: logical.length,
|
|
50
|
+
compressed,
|
|
51
|
+
});
|
|
52
|
+
if (!snapshot || snapshot.frame.length >= input.knownFileBytes)
|
|
87
53
|
return null;
|
|
88
|
-
const frameHash =
|
|
89
|
-
// Materialize the replacement records before the rename. If cloning a
|
|
90
|
-
// payload fails, the existing journal must remain the authoritative file.
|
|
54
|
+
const { frame, frameHash } = snapshot;
|
|
91
55
|
let records;
|
|
92
56
|
try {
|
|
93
|
-
records = logical.map((record, index) => (
|
|
94
|
-
partition: input.partition,
|
|
95
|
-
epoch,
|
|
96
|
-
seq: index + 1,
|
|
97
|
-
previousFrameHash: index === 0 ? ZERO_HASH : frameHash,
|
|
98
|
-
frameHash,
|
|
99
|
-
time: record.time,
|
|
100
|
-
type: record.type,
|
|
101
|
-
payload: cloneJson(record.payload),
|
|
102
|
-
byteOffset: 0,
|
|
103
|
-
}));
|
|
57
|
+
records = logical.map((record, index) => compactedJournalRecord(record, input.partition, epoch, index, frameHash, cloneJson(record.payload)));
|
|
104
58
|
}
|
|
105
59
|
catch (error) {
|
|
106
60
|
if (isCompactionCapacityError(error))
|
|
@@ -110,14 +64,19 @@ export function compactJournalFile(input) {
|
|
|
110
64
|
const temp = `${input.path}.${randomUUID()}.compact`;
|
|
111
65
|
const tempFd = openSync(temp, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL, 0o600);
|
|
112
66
|
try {
|
|
113
|
-
|
|
67
|
+
try {
|
|
68
|
+
appendAndSync(tempFd, frame);
|
|
69
|
+
}
|
|
70
|
+
finally {
|
|
71
|
+
closeSync(tempFd);
|
|
72
|
+
}
|
|
114
73
|
}
|
|
115
|
-
|
|
116
|
-
|
|
74
|
+
catch (error) {
|
|
75
|
+
rmSync(temp, { force: true });
|
|
76
|
+
throw error;
|
|
117
77
|
}
|
|
118
|
-
renameSync(temp, input.path);
|
|
119
|
-
fsyncDirectory(dirname(input.path));
|
|
120
78
|
return {
|
|
79
|
+
path: temp,
|
|
121
80
|
receipt: {
|
|
122
81
|
beforeBytes: input.knownFileBytes,
|
|
123
82
|
afterBytes: frame.length,
|
|
@@ -130,7 +89,58 @@ export function compactJournalFile(input) {
|
|
|
130
89
|
knownFileBytes: frame.length,
|
|
131
90
|
};
|
|
132
91
|
}
|
|
133
|
-
function
|
|
92
|
+
export function logicalRecord(record, payload = record.payload) {
|
|
93
|
+
return { time: record.time, type: record.type, payload };
|
|
94
|
+
}
|
|
95
|
+
/** Both producers share the versioned envelope and existing materialization caps. */
|
|
96
|
+
export function encodeCompactionSnapshot(input) {
|
|
97
|
+
if (input.compressed.length > MAX_PAYLOAD_BYTES)
|
|
98
|
+
return null;
|
|
99
|
+
let payload;
|
|
100
|
+
let payloadBytes;
|
|
101
|
+
try {
|
|
102
|
+
payload = {
|
|
103
|
+
version: 1,
|
|
104
|
+
count: input.count,
|
|
105
|
+
encoding: "gzip-base64",
|
|
106
|
+
data: input.compressed.toString("base64"),
|
|
107
|
+
};
|
|
108
|
+
payloadBytes = encodeJournalPayload(payload);
|
|
109
|
+
}
|
|
110
|
+
catch (error) {
|
|
111
|
+
if (isCompactionCapacityError(error))
|
|
112
|
+
return null;
|
|
113
|
+
throw error;
|
|
114
|
+
}
|
|
115
|
+
if (payloadBytes.length > MAX_PAYLOAD_BYTES)
|
|
116
|
+
return null;
|
|
117
|
+
const header = {
|
|
118
|
+
partition: input.partition,
|
|
119
|
+
epoch: input.epoch,
|
|
120
|
+
seq: 1,
|
|
121
|
+
previousFrameHash: ZERO_HASH,
|
|
122
|
+
time: input.time,
|
|
123
|
+
type: COMPACTED_SNAPSHOT,
|
|
124
|
+
logicalSpan: input.count,
|
|
125
|
+
};
|
|
126
|
+
const frame = encodeFrame(header, payloadBytes);
|
|
127
|
+
const frameHash = frame.subarray(frame.length - HASH_BYTES).toString("hex");
|
|
128
|
+
return { frame, frameHash };
|
|
129
|
+
}
|
|
130
|
+
export function compactedJournalRecord(record, partition, epoch, index, frameHash, payload = record.payload) {
|
|
131
|
+
return {
|
|
132
|
+
partition,
|
|
133
|
+
epoch,
|
|
134
|
+
seq: index + 1,
|
|
135
|
+
previousFrameHash: index === 0 ? ZERO_HASH : frameHash,
|
|
136
|
+
frameHash,
|
|
137
|
+
time: record.time,
|
|
138
|
+
type: record.type,
|
|
139
|
+
payload,
|
|
140
|
+
byteOffset: 0,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
export function isCompactionCapacityError(error) {
|
|
134
144
|
if (error instanceof RangeError &&
|
|
135
145
|
(error.message === "Invalid string length" ||
|
|
136
146
|
("code" in error && error.code === "ERR_STRING_TOO_LONG"))) {
|
|
@@ -139,7 +149,9 @@ function isCompactionCapacityError(error) {
|
|
|
139
149
|
return (typeof error === "object" &&
|
|
140
150
|
error !== null &&
|
|
141
151
|
"code" in error &&
|
|
142
|
-
(error.code === "ERR_BUFFER_TOO_LARGE" ||
|
|
152
|
+
(error.code === "ERR_BUFFER_TOO_LARGE" ||
|
|
153
|
+
error.code === "ERR_STRING_TOO_LONG" ||
|
|
154
|
+
error.code === "journal_compaction_capacity"));
|
|
143
155
|
}
|
|
144
156
|
function cloneJson(value) {
|
|
145
157
|
return JSON.parse(JSON.stringify(value));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"journal-compaction.js","sourceRoot":"","sources":["../src/journal-compaction.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,QAAQ,EAAE,
|
|
1
|
+
{"version":3,"file":"journal-compaction.js","sourceRoot":"","sources":["../src/journal-compaction.ts"],"names":[],"mappings":"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"}
|
package/dist/journal-cursor.d.ts
CHANGED
|
@@ -6,4 +6,5 @@ export declare class JournalCursorError extends Error {
|
|
|
6
6
|
}
|
|
7
7
|
export declare function encodeCursor(partition: string, epoch: string, seq: number): string;
|
|
8
8
|
export declare function cursorError(detail: string): JournalCursorError;
|
|
9
|
+
export declare function sequenceAfterCursor(cursor: string | null | undefined, partition: string, epoch: string, nextSeq: number): number;
|
|
9
10
|
//# sourceMappingURL=journal-cursor.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"journal-cursor.d.ts","sourceRoot":"","sources":["../src/journal-cursor.ts"],"names":[],"mappings":"AAAA,qBAAa,kBAAmB,SAAQ,KAAK;IAC3C,QAAQ,CAAC,IAAI,4BAA4B;IACzC,QAAQ,CAAC,MAAM,OAAO;IACtB,QAAQ,CAAC,SAAS,QAAQ;IAC1B,QAAQ,CAAC,eAAe,WAAkB;CAC3C;AAED,wBAAgB,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAIlF;AAED,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,kBAAkB,CAE9D"}
|
|
1
|
+
{"version":3,"file":"journal-cursor.d.ts","sourceRoot":"","sources":["../src/journal-cursor.ts"],"names":[],"mappings":"AAAA,qBAAa,kBAAmB,SAAQ,KAAK;IAC3C,QAAQ,CAAC,IAAI,4BAA4B;IACzC,QAAQ,CAAC,MAAM,OAAO;IACtB,QAAQ,CAAC,SAAS,QAAQ;IAC1B,QAAQ,CAAC,eAAe,WAAkB;CAC3C;AAED,wBAAgB,YAAY,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAIlF;AAED,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,kBAAkB,CAE9D;AAED,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,EACjC,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,MAAM,GACd,MAAM,CAoBR"}
|
package/dist/journal-cursor.js
CHANGED
|
@@ -10,4 +10,32 @@ export function encodeCursor(partition, epoch, seq) {
|
|
|
10
10
|
export function cursorError(detail) {
|
|
11
11
|
return new JournalCursorError(`journal cursor is ${detail}; resnapshot is required`);
|
|
12
12
|
}
|
|
13
|
+
export function sequenceAfterCursor(cursor, partition, epoch, nextSeq) {
|
|
14
|
+
if (!cursor)
|
|
15
|
+
return 0;
|
|
16
|
+
if (!/^[A-Za-z0-9_-]{1,4096}$/.test(cursor))
|
|
17
|
+
throw cursorError("malformed");
|
|
18
|
+
let value;
|
|
19
|
+
try {
|
|
20
|
+
value = JSON.parse(Buffer.from(cursor, "base64url").toString("utf8"));
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
throw cursorError("malformed");
|
|
24
|
+
}
|
|
25
|
+
if (!isRecord(value) || Object.keys(value).sort().join(",") !== "e,p,s,v" || value.v !== 1) {
|
|
26
|
+
throw cursorError("unsupported");
|
|
27
|
+
}
|
|
28
|
+
if (value.p !== partition || value.e !== epoch)
|
|
29
|
+
throw cursorError("stale epoch");
|
|
30
|
+
if (!Number.isSafeInteger(value.s) || Number(value.s) < 0 || Number(value.s) >= nextSeq) {
|
|
31
|
+
throw cursorError("ahead of the durable partition");
|
|
32
|
+
}
|
|
33
|
+
if (encodeCursor(value.p, value.e, value.s) !== cursor) {
|
|
34
|
+
throw cursorError("not canonically encoded");
|
|
35
|
+
}
|
|
36
|
+
return value.s;
|
|
37
|
+
}
|
|
38
|
+
function isRecord(value) {
|
|
39
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
40
|
+
}
|
|
13
41
|
//# sourceMappingURL=journal-cursor.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"journal-cursor.js","sourceRoot":"","sources":["../src/journal-cursor.ts"],"names":[],"mappings":"AAAA,MAAM,OAAO,kBAAmB,SAAQ,KAAK;IAClC,IAAI,GAAG,wBAAwB,CAAC;IAChC,MAAM,GAAG,GAAG,CAAC;IACb,SAAS,GAAG,IAAI,CAAC;IACjB,eAAe,GAAG,CAAC,YAAY,CAAC,CAAC;CAC3C;AAED,MAAM,UAAU,YAAY,CAAC,SAAiB,EAAE,KAAa,EAAE,GAAW;IACxE,OAAO,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,QAAQ,CACnF,WAAW,CACZ,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,MAAc;IACxC,OAAO,IAAI,kBAAkB,CAAC,qBAAqB,MAAM,0BAA0B,CAAC,CAAC;AACvF,CAAC"}
|
|
1
|
+
{"version":3,"file":"journal-cursor.js","sourceRoot":"","sources":["../src/journal-cursor.ts"],"names":[],"mappings":"AAAA,MAAM,OAAO,kBAAmB,SAAQ,KAAK;IAClC,IAAI,GAAG,wBAAwB,CAAC;IAChC,MAAM,GAAG,GAAG,CAAC;IACb,SAAS,GAAG,IAAI,CAAC;IACjB,eAAe,GAAG,CAAC,YAAY,CAAC,CAAC;CAC3C;AAED,MAAM,UAAU,YAAY,CAAC,SAAiB,EAAE,KAAa,EAAE,GAAW;IACxE,OAAO,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,SAAS,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,CAAC,CAAC,QAAQ,CACnF,WAAW,CACZ,CAAC;AACJ,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,MAAc;IACxC,OAAO,IAAI,kBAAkB,CAAC,qBAAqB,MAAM,0BAA0B,CAAC,CAAC;AACvF,CAAC;AAED,MAAM,UAAU,mBAAmB,CACjC,MAAiC,EACjC,SAAiB,EACjB,KAAa,EACb,OAAe;IAEf,IAAI,CAAC,MAAM;QAAE,OAAO,CAAC,CAAC;IACtB,IAAI,CAAC,yBAAyB,CAAC,IAAI,CAAC,MAAM,CAAC;QAAE,MAAM,WAAW,CAAC,WAAW,CAAC,CAAC;IAC5E,IAAI,KAAc,CAAC;IACnB,IAAI,CAAC;QACH,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC;IACxE,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,WAAW,CAAC,WAAW,CAAC,CAAC;IACjC,CAAC;IACD,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,EAAE,CAAC,IAAI,CAAC,GAAG,CAAC,KAAK,SAAS,IAAI,KAAK,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC;QAC3F,MAAM,WAAW,CAAC,aAAa,CAAC,CAAC;IACnC,CAAC;IACD,IAAI,KAAK,CAAC,CAAC,KAAK,SAAS,IAAI,KAAK,CAAC,CAAC,KAAK,KAAK;QAAE,MAAM,WAAW,CAAC,aAAa,CAAC,CAAC;IACjF,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,OAAO,EAAE,CAAC;QACxF,MAAM,WAAW,CAAC,gCAAgC,CAAC,CAAC;IACtD,CAAC;IACD,IAAI,YAAY,CAAC,KAAK,CAAC,CAAW,EAAE,KAAK,CAAC,CAAW,EAAE,KAAK,CAAC,CAAW,CAAC,KAAK,MAAM,EAAE,CAAC;QACrF,MAAM,WAAW,CAAC,yBAAyB,CAAC,CAAC;IAC/C,CAAC;IACD,OAAO,KAAK,CAAC,CAAW,CAAC;AAC3B,CAAC;AAED,SAAS,QAAQ,CAAC,KAAc;IAC9B,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;AAC9E,CAAC"}
|
package/dist/journal-files.d.ts
CHANGED
|
@@ -1,9 +1,17 @@
|
|
|
1
|
+
import { type Stats } from "node:fs";
|
|
1
2
|
export interface AppendIntent {
|
|
2
3
|
v: 1;
|
|
3
4
|
offset: number;
|
|
4
5
|
length: number;
|
|
5
6
|
}
|
|
7
|
+
export declare function sameJournalFile(expected: Stats, actual: Stats, bytes?: number): boolean;
|
|
8
|
+
export declare function openJournalWriter(path: string): number;
|
|
9
|
+
/** A failed rename may resume only the exact original canonical file. */
|
|
10
|
+
export declare function reopenOriginalWriter(path: string, original: Stats): number;
|
|
6
11
|
export declare function appendAndSync(fd: number, bytes: Buffer): void;
|
|
12
|
+
/** Truncate only an already validated pending suffix. Windows append handles
|
|
13
|
+
* lack FILE_WRITE_DATA, required by NtSetInformationFile's EOF operation. */
|
|
14
|
+
export declare function truncatePendingSuffix(fd: number, path: string, offset: number, bytes: number): void;
|
|
7
15
|
export declare function ensurePrivateFile(path: string): void;
|
|
8
16
|
export declare function readDescriptor(fd: number): Buffer;
|
|
9
17
|
export declare function readIntent(path: string): AppendIntent | null;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"journal-files.d.ts","sourceRoot":"","sources":["../src/journal-files.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"journal-files.d.ts","sourceRoot":"","sources":["../src/journal-files.ts"],"names":[],"mappings":"AACA,OAAO,EAcL,KAAK,KAAK,EACX,MAAM,SAAS,CAAC;AAIjB,MAAM,WAAW,YAAY;IAC3B,CAAC,EAAE,CAAC,CAAC;IACL,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,wBAAgB,eAAe,CAAC,QAAQ,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,SAAgB,GAAG,OAAO,CAQ9F;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CActD;AAED,yEAAyE;AACzE,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,GAAG,MAAM,CAW1E;AAED,wBAAgB,aAAa,CAAC,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAI7D;AAED;6EAC6E;AAC7E,wBAAgB,qBAAqB,CACnC,EAAE,EAAE,MAAM,EACV,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,MAAM,GACZ,IAAI,CA6BN;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAcpD;AAED,wBAAgB,cAAc,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAMjD;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,GAAG,IAAI,CAqB5D;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY,GAAG,IAAI,CAanE;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAI7C"}
|
package/dist/journal-files.js
CHANGED
|
@@ -1,13 +1,83 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
|
-
import { closeSync, constants, existsSync, fstatSync, fsyncSync, lstatSync, openSync, readFileSync, renameSync, rmSync, writeSync, } from "node:fs";
|
|
2
|
+
import { closeSync, constants, existsSync, fchmodSync, fstatSync, ftruncateSync, fsyncSync, lstatSync, openSync, readFileSync, renameSync, rmSync, writeSync, } from "node:fs";
|
|
3
3
|
import { dirname } from "node:path";
|
|
4
4
|
import { fsyncDirectory } from "@claudexor/util";
|
|
5
|
+
export function sameJournalFile(expected, actual, bytes = expected.size) {
|
|
6
|
+
return (actual.isFile() &&
|
|
7
|
+
actual.nlink === 1 &&
|
|
8
|
+
actual.dev === expected.dev &&
|
|
9
|
+
actual.ino === expected.ino &&
|
|
10
|
+
actual.size === bytes);
|
|
11
|
+
}
|
|
12
|
+
export function openJournalWriter(path) {
|
|
13
|
+
const fd = openSync(path, constants.O_RDWR | constants.O_APPEND | constants.O_NOFOLLOW);
|
|
14
|
+
try {
|
|
15
|
+
const stat = fstatSync(fd);
|
|
16
|
+
if (!stat.isFile() || stat.nlink !== 1)
|
|
17
|
+
throw new Error("journal file is not privately owned");
|
|
18
|
+
if ((stat.mode & 0o777) !== 0o600) {
|
|
19
|
+
fchmodSync(fd, 0o600);
|
|
20
|
+
fsyncSync(fd);
|
|
21
|
+
}
|
|
22
|
+
return fd;
|
|
23
|
+
}
|
|
24
|
+
catch (error) {
|
|
25
|
+
closeSync(fd);
|
|
26
|
+
throw error;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/** A failed rename may resume only the exact original canonical file. */
|
|
30
|
+
export function reopenOriginalWriter(path, original) {
|
|
31
|
+
let fd = -1;
|
|
32
|
+
try {
|
|
33
|
+
if (!sameJournalFile(original, lstatSync(path)))
|
|
34
|
+
return -1;
|
|
35
|
+
fd = openJournalWriter(path);
|
|
36
|
+
if (sameJournalFile(original, fstatSync(fd)))
|
|
37
|
+
return fd;
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
/* the caller enters its typed recovery path */
|
|
41
|
+
}
|
|
42
|
+
if (fd >= 0)
|
|
43
|
+
closeSync(fd);
|
|
44
|
+
return -1;
|
|
45
|
+
}
|
|
5
46
|
export function appendAndSync(fd, bytes) {
|
|
6
47
|
let offset = 0;
|
|
7
48
|
while (offset < bytes.length)
|
|
8
49
|
offset += writeSync(fd, bytes, offset, bytes.length - offset);
|
|
9
50
|
fsyncSync(fd);
|
|
10
51
|
}
|
|
52
|
+
/** Truncate only an already validated pending suffix. Windows append handles
|
|
53
|
+
* lack FILE_WRITE_DATA, required by NtSetInformationFile's EOF operation. */
|
|
54
|
+
export function truncatePendingSuffix(fd, path, offset, bytes) {
|
|
55
|
+
if (process.platform !== "win32") {
|
|
56
|
+
ftruncateSync(fd, offset);
|
|
57
|
+
fsyncSync(fd);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
const original = fstatSync(fd);
|
|
61
|
+
const recoveryFd = openSync(path, constants.O_RDWR | constants.O_NOFOLLOW);
|
|
62
|
+
try {
|
|
63
|
+
if (!sameJournalFile(original, fstatSync(fd), bytes) ||
|
|
64
|
+
!sameJournalFile(original, fstatSync(recoveryFd), bytes) ||
|
|
65
|
+
!sameJournalFile(original, lstatSync(path), bytes)) {
|
|
66
|
+
throw new Error("journal identity changed before pending suffix recovery");
|
|
67
|
+
}
|
|
68
|
+
ftruncateSync(recoveryFd, offset);
|
|
69
|
+
fsyncSync(recoveryFd);
|
|
70
|
+
if (!sameJournalFile(original, fstatSync(fd), offset) ||
|
|
71
|
+
!sameJournalFile(original, fstatSync(recoveryFd), offset) ||
|
|
72
|
+
!sameJournalFile(original, lstatSync(path), offset)) {
|
|
73
|
+
throw new Error("journal identity changed during pending suffix recovery");
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
finally {
|
|
77
|
+
// A close failure must also leave the intent in place and refuse readiness.
|
|
78
|
+
closeSync(recoveryFd);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
11
81
|
export function ensurePrivateFile(path) {
|
|
12
82
|
if (!existsSync(path)) {
|
|
13
83
|
const fd = openSync(path, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL, 0o600);
|