refactor: move the OPFS journal onto the IndexedDB kernel, and make the fake
IndexedDB enforce unique indexes 저널은 이제 트랜잭션 안 요청 28곳이 각자 오류를 보고한다. 코드 줄은 1744 -> 1724로 줄었고 전체 줄이 1817 -> 1881로 는 것은 주석이다. 이행 전 이 파일의 주석은 6줄이었다. 사양의 OP-1 전제는 틀렸다. "중복 put의 답이 달라진다"고 봤으나 이행 전후 모두 CONFLICT / retryable:false / recovery:NONE이다. 요청 오류를 아무도 처리하지 않으면 스토어가 바로 그 에러로 abort해서 transaction.error가 request.error와 같기 때문이다. 바뀐 것은 답이 아니라 출처다. 그 과정에서 가짜 IndexedDB가 unique 인덱스를 전혀 강제하지 않는 것을 찾았다. 보강 전에는 중복 begin이 ok:true로 성공했다 — 브라우저가 거부할 상태를 테스트가 조용히 허용하고 있었다. put/add에 검사를 넣었다. unique:true 인덱스는 레포 전체에서 이 저널의 2개뿐이라 반경이 좁고, 전체 test:unit으로 파급이 없음을 확인했다. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
3366a81f0f
commit
217c1dd52a
@@ -146,6 +146,48 @@ function compareKeys(first: IDBValidKey, second: IDBValidKey): number {
|
||||
return String(left) < String(right) ? -1 : 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* A unique index rejects a second row whose index key already belongs to
|
||||
* another primary key, which is how a browser reports it: a request-level
|
||||
* `ConstraintError` that then aborts the transaction.
|
||||
*
|
||||
* The fake enforced primary keys only, so the one `unique: true` index in the
|
||||
* repository — `indexeddb-opfs-journal.ts`'s logical-key index, whose store is
|
||||
* keyed by `transactionId` instead — could never fail here the way it fails in
|
||||
* a browser. Every other index in `src/adapters` is non-unique, so this check
|
||||
* is a no-op for them.
|
||||
*/
|
||||
function assertUniqueIndexes(
|
||||
state: StoreState,
|
||||
key: string,
|
||||
value: unknown,
|
||||
): void {
|
||||
for (const [name, index] of state.indexes) {
|
||||
if (!index.unique || typeof index.keyPath !== "string") continue;
|
||||
const indexKey = readPath(value, index.keyPath);
|
||||
// An absent index key keeps the row out of the index entirely, so it
|
||||
// cannot collide with anything.
|
||||
if (indexKey === undefined) continue;
|
||||
for (const [otherKey, otherValue] of state.data) {
|
||||
// Replacing a row never collides with the row it replaces.
|
||||
if (otherKey === key) continue;
|
||||
const otherIndexKey = readPath(otherValue, index.keyPath);
|
||||
if (otherIndexKey === undefined) continue;
|
||||
if (
|
||||
compareKeys(
|
||||
indexKey as IDBValidKey,
|
||||
otherIndexKey as IDBValidKey,
|
||||
) === 0
|
||||
) {
|
||||
throw new DOMException(
|
||||
`Unique index ${name} already holds this key.`,
|
||||
"ConstraintError",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class FakeUpgradeTransaction {
|
||||
aborted = false;
|
||||
|
||||
@@ -483,6 +525,7 @@ class FakeObjectStore {
|
||||
this.assertWritable();
|
||||
return this.request(() => {
|
||||
const key = primaryKey(this.state, value);
|
||||
assertUniqueIndexes(this.state, key, value);
|
||||
this.state.data.set(key, structuredClone(value));
|
||||
return key as IDBValidKey;
|
||||
});
|
||||
@@ -495,6 +538,7 @@ class FakeObjectStore {
|
||||
if (this.state.data.has(key)) {
|
||||
throw new DOMException("Key already exists.", "ConstraintError");
|
||||
}
|
||||
assertUniqueIndexes(this.state, key, value);
|
||||
this.state.data.set(key, structuredClone(value));
|
||||
return key as IDBValidKey;
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user