Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 20 additions & 7 deletions src/database.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,10 @@ import * as searchMetrics from './search-metrics.js';
import { normalizeStatusValue } from './status-stage-rules.js';

const UNIQUE_TIME_LENGTH = 9;
const UNIQUE_RANDOM_BYTES = 4;
const UNIQUE_RANDOM_LENGTH = 7;
const UNIQUE_ID_LENGTH = UNIQUE_TIME_LENGTH + UNIQUE_RANDOM_LENGTH;
const UNIQUE_SEQUENCE_LENGTH = 2;
const UNIQUE_RANDOM_BYTES = 3;
const UNIQUE_RANDOM_LENGTH = 5;
const UNIQUE_ID_LENGTH = UNIQUE_TIME_LENGTH + UNIQUE_SEQUENCE_LENGTH + UNIQUE_RANDOM_LENGTH;
const MAX_ID_GENERATION_ATTEMPTS = 10;

export class WorklogDatabase {
Expand All @@ -27,6 +28,8 @@ export class WorklogDatabase {
private autoSync: boolean;
private syncProvider?: () => Promise<void>;
private lockPath: string;
private _lastIdTime: number = 0;
private _idSequence: number = 0;

constructor(
prefix: string = 'WI',
Expand Down Expand Up @@ -677,18 +680,28 @@ export class WorklogDatabase {
}

/**
* Generate a globally unique, human-readable identifier
* Generate a globally unique, human-readable identifier.
* Uses a sequence counter to ensure deterministic ordering when multiple
* IDs are generated within the same millisecond.
*/
private generateUniqueId(): string {
const timeRaw = Date.now().toString(36).toUpperCase();
const now = Date.now();
if (now !== this._lastIdTime) {
this._lastIdTime = now;
this._idSequence = 0;
} else {
this._idSequence++;
}
const timeRaw = now.toString(36).toUpperCase();
if (timeRaw.length > UNIQUE_TIME_LENGTH) {
throw new Error('Timestamp overflow while generating unique ID');
}
const timePart = timeRaw.padStart(UNIQUE_TIME_LENGTH, '0');
const randomBytesValue = randomBytes(UNIQUE_RANDOM_BYTES);
const randomNumber = randomBytesValue.readUInt32BE(0);
const randomNumber = randomBytesValue.readUIntBE(0, UNIQUE_RANDOM_BYTES);
const randomPart = randomNumber.toString(36).toUpperCase().padStart(UNIQUE_RANDOM_LENGTH, '0');
const id = `${timePart}${randomPart}`;
const sequencePart = this._idSequence.toString(36).toUpperCase().padStart(2, '0');
const id = `${timePart}${sequencePart}${randomPart}`;
if (id.length !== UNIQUE_ID_LENGTH) {
throw new Error('Generated unique ID has unexpected length');
}
Expand Down
3 changes: 3 additions & 0 deletions tests/database.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ describe('WorklogDatabase', () => {
tempDir = createTempDir();
dbPath = createTempDbPath(tempDir);
jsonlPath = createTempJsonlPath(tempDir);
if (fs.existsSync(jsonlPath)) {
fs.unlinkSync(jsonlPath);
}
db = new WorklogDatabase('TEST', dbPath, jsonlPath, true, true);
});

Expand Down
3 changes: 3 additions & 0 deletions tests/next-regression.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ describe('wl next regression tests (WL-0MM2FKKOW1H0C0G4)', () => {
tempDir = createTempDir();
dbPath = createTempDbPath(tempDir);
jsonlPath = createTempJsonlPath(tempDir);
if (fs.existsSync(jsonlPath)) {
fs.unlinkSync(jsonlPath);
}
db = new WorklogDatabase('TEST', dbPath, jsonlPath, true, true);
});

Expand Down
Loading