-
Notifications
You must be signed in to change notification settings - Fork 4
fix(resume-library): add deterministic tiebreaker for same-millisecond saves #908
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 3 commits
24f4b44
811a39f
b0812a9
a6cfae9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -169,6 +169,18 @@ export async function putRecordIntoExisting<T extends StoredRecord>( | |
| return putRecordVia(getExistingDB, store, record, options); | ||
| } | ||
|
|
||
| let lastTimestamp = 0; | ||
|
|
||
| function monotonicNow(): number { | ||
| const now = Date.now(); | ||
| if (now <= lastTimestamp) { | ||
| lastTimestamp += 1; | ||
| return lastTimestamp; | ||
| } | ||
| lastTimestamp = now; | ||
| return now; | ||
| } | ||
|
|
||
| async function putRecordVia<T extends StoredRecord>( | ||
| opener: () => Promise<IDBPDatabase<any>>, | ||
| store: StoreName, | ||
|
|
@@ -177,7 +189,7 @@ async function putRecordVia<T extends StoredRecord>( | |
| options: { touch?: boolean } = {}, | ||
| ): Promise<T> { | ||
| const db = await looseDB(opener); | ||
| const now = Date.now(); | ||
| const now = monotonicNow(); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Secondary — Concrete failure: several jobs saved back-to-back under contention drift Suggest routing |
||
| const existing = (await db.get(store, record.id)) as T | undefined; | ||
| const written = { | ||
| ...record, | ||
|
|
@@ -325,7 +337,7 @@ export async function softDeleteRecord( | |
| const db = await looseDB(); | ||
| const existing = (await db.get(store, id)) as StoredRecord | undefined; | ||
| if (existing === undefined || !isLive(existing)) return false; | ||
| const now = Date.now(); | ||
| const now = monotonicNow(); | ||
| await db.put(store, { ...existing, deletedAt: now, updatedAt: now }); | ||
| emitChange(store); | ||
| return true; | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Blocking: this test still doesn't verify the tiebreaker. Reverted only
a.id.localeCompare(b.id)inresume-library.tsand re-ran this test 5x — still passes every time.Root cause:
getAllResumes()→getAllRecords()→db.getAll(store)(crud.ts:244) has no index, so IndexedDB itself already returns tied records in ascending primary-key order. The test's ids ("id-a","id-b") are already ascending, soArray.prototype.sort's stability alone reproduces["id-a", "id-b"]with or without the explicit tiebreak — the test can't distinguish "the comparator works" from "IDB's own order happened to already match it."Fix: stub
getAllResumes/db.getAllto return the tied pair in descending id order, then assertlistLibrary()still comes back["id-a", "id-b"]— that actually exercises the comparator instead of agreeing with the store's incidental order.