Files
sati.n-api.index-file/lib/idx/writer.ts
2026-01-27 02:30:42 +00:00

141 lines
3.3 KiB
TypeScript

// src/index-file/writer.ts
import * as fs from 'node:fs';
import mmap from '@elilee/mmap-native';
import { INDEX_HEADER_SIZE, FLAG_VALID } from './constants.js';
import { IndexProtocol } from './protocol.js';
import type { IndexFileOptions } from './types.js';
export class IndexWriter {
private fd: number | null = null;
private buffer: Buffer | null = null;
private validCount = 0;
private dataFileSize = 0n;
private lastSequence = 0;
readonly path: string;
readonly maxEntries: number;
readonly fileSize: number;
constructor(path: string, options: IndexFileOptions) {
this.path = path;
this.maxEntries = options.maxEntries;
this.fileSize = IndexProtocol.calcFileSize(options.maxEntries);
}
open(): void {
const isNew = !fs.existsSync(this.path);
this.fd = fs.openSync(this.path, isNew ? 'w+' : 'r+');
if (isNew) {
fs.ftruncateSync(this.fd, this.fileSize);
}
this.buffer = mmap.map(
this.fileSize,
mmap.PROT_READ | mmap.PROT_WRITE,
mmap.MAP_SHARED,
this.fd,
0
);
if (isNew) {
const header = IndexProtocol.createHeader(this.maxEntries);
header.copy(this.buffer, 0);
this.syncHeader();
} else {
const header = IndexProtocol.readHeader(this.buffer);
this.validCount = header.validCount;
this.dataFileSize = header.dataFileSize;
this.lastSequence = header.lastSequence;
}
}
write(
index: number,
sequence: number,
offset: bigint,
length: number,
timestamp?: bigint
): boolean {
if (!this.buffer) throw new Error('Index file not opened');
if (index < 0 || index >= this.maxEntries) return false;
const ts = timestamp ?? BigInt(Date.now()) * 1000000n;
IndexProtocol.writeEntry(this.buffer, index, {
sequence,
timestamp: ts,
offset,
length,
flags: FLAG_VALID,
});
this.validCount++;
if (sequence > this.lastSequence) {
this.lastSequence = sequence;
}
const newDataEnd = offset + BigInt(length);
if (newDataEnd > this.dataFileSize) {
this.dataFileSize = newDataEnd;
}
return true;
}
append(offset: bigint, length: number, timestamp?: bigint): number {
const index = this.validCount;
if (index >= this.maxEntries) return -1;
const sequence = this.lastSequence + 1;
this.write(index, sequence, offset, length, timestamp);
return index;
}
getLastSequence(): number {
return this.lastSequence;
}
getNextSequence(): number {
return this.lastSequence + 1;
}
syncHeader(): void {
if (!this.buffer) return;
IndexProtocol.updateHeaderCounts(
this.buffer,
this.validCount,
this.dataFileSize,
this.lastSequence
);
mmap.sync(this.buffer, 0, INDEX_HEADER_SIZE, mmap.MS_ASYNC);
}
syncAll(): void {
if (!this.buffer) return;
this.syncHeader();
mmap.sync(this.buffer, 0, this.fileSize, mmap.MS_SYNC);
}
close(): void {
if (!this.buffer || this.fd === null) return;
this.syncAll();
mmap.unmap(this.buffer);
fs.closeSync(this.fd);
this.buffer = null;
this.fd = null;
}
getStats() {
return {
path: this.path,
maxEntries: this.maxEntries,
validCount: this.validCount,
dataFileSize: this.dataFileSize,
lastSequence: this.lastSequence,
};
}
}