commit ff821e23caaa26e685eedce17504785f005c3b58
parent 159fa81d9dd96c992a66f3ce216b7647f8505908
Author: Florian Dold <dold@taler.net>
Date: Thu, 20 Aug 2026 14:22:52 +0200
idb-bridge: support cursor updates with compound keys
Diffstat:
2 files changed, 82 insertions(+), 9 deletions(-)
diff --git a/packages/idb-bridge/src/bridge-idb.ts b/packages/idb-bridge/src/bridge-idb.ts
@@ -46,7 +46,6 @@ import {
IDBTransactionMode,
IDBValidKey,
} from "./idbtypes.js";
-import { canInjectKey } from "./util/canInjectKey.js";
import { compareKeys } from "./util/cmp.js";
import { extractKey } from "./util/extractKey.js";
import { enforceRange } from "./util/enforceRange.js";
@@ -395,6 +394,9 @@ export class BridgeIDBCursor implements IDBCursor {
}
if (os._objectStoreMeta.keyPath !== null) {
+ // A cursor update keeps the existing primary key; it never generates
+ // or injects one. Extracting and comparing the inline key is therefore
+ // the complete validation, including for compound key paths.
const key2 = extractKey(os._objectStoreMeta.keyPath, value);
if (compareKeys(key, key2) !== 0) {
throw new DataError(
@@ -402,14 +404,6 @@ export class BridgeIDBCursor implements IDBCursor {
);
}
}
-
- if (os.keyPath !== null && os.keyPath !== undefined) {
- if (!canInjectKey(os.keyPath, value)) {
- throw new DataError(
- "The value cannot accept a key at the object store's in-line key path.",
- );
- }
- }
} catch (err) {
throw addErrorContext(err, "IDBCursor.update", this);
}
diff --git a/packages/idb-bridge/src/idbcursor-update.test.ts b/packages/idb-bridge/src/idbcursor-update.test.ts
@@ -0,0 +1,79 @@
+/*
+ Copyright 2026 Taler Systems SA
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+*/
+
+import assert from "node:assert";
+import { before, test } from "node:test";
+import {
+ createDatabase,
+ initTestIndexedDB,
+ promiseForRequest,
+ promiseForTransaction,
+} from "./idb-wpt-ported/wptsupport.js";
+
+before(initTestIndexedDB);
+
+async function createCompoundKeyDatabase(t: any) {
+ return createDatabase(t, (db) => {
+ const store = db.createObjectStore("records", {
+ keyPath: ["scope", "id"],
+ });
+ store.add({ scope: "exchange", id: 1, value: "before" });
+ });
+}
+
+test("cursor update accepts an unchanged inline compound key", async (t) => {
+ const db = await createCompoundKeyDatabase(t);
+ const tx = db.transaction("records", "readwrite");
+ const txDone = promiseForTransaction(t, tx);
+ const cursor = await promiseForRequest(
+ t,
+ tx.objectStore("records").openCursor(),
+ );
+ assert(cursor);
+
+ await promiseForRequest(
+ t,
+ cursor.update({ ...cursor.value, value: "after" }),
+ );
+ await txDone;
+
+ const readTx = db.transaction("records", "readonly");
+ const record = await promiseForRequest(
+ t,
+ readTx.objectStore("records").get(["exchange", 1]),
+ );
+ assert.deepStrictEqual(record, {
+ scope: "exchange",
+ id: 1,
+ value: "after",
+ });
+});
+
+test("cursor update rejects a changed inline compound key", async (t) => {
+ const db = await createCompoundKeyDatabase(t);
+ const tx = db.transaction("records", "readwrite");
+ const txDone = promiseForTransaction(t, tx);
+ const cursor = await promiseForRequest(
+ t,
+ tx.objectStore("records").openCursor(),
+ );
+ assert(cursor);
+
+ assert.throws(() => cursor.update({ ...cursor.value, id: 2 }), {
+ name: "DataError",
+ });
+ await txDone;
+});