diff --git a/src/browser/js/bridge.zig b/src/browser/js/bridge.zig index 9559d9a94..29cb0cc57 100644 --- a/src/browser/js/bridge.zig +++ b/src/browser/js/bridge.zig @@ -1000,6 +1000,7 @@ pub const PageJsApis = flattenTypes(&.{ @import("../webapi/XPathResult.zig"), @import("../webapi/XPathExpression.zig"), @import("../webapi/XPathEvaluator.zig"), + @import("../webapi/collections/DOMStringList.zig"), }); // APIs available on Worker context globals (constructors like URL, Headers, etc.) @@ -1061,6 +1062,8 @@ pub const WorkerJsApis = flattenTypes(&.{ @import("../webapi/storage/idb/idb.zig"), @import("../webapi/event/CookieChangeEvent.zig"), @import("../webapi/BroadcastChannel.zig"), + @import("../webapi/BroadcastChannel.zig"), + @import("../webapi/collections/DOMStringList.zig"), }); // Master list of ALL JS APIs across all contexts. diff --git a/src/browser/webapi/collections.zig b/src/browser/webapi/collections.zig index eeb884929..4a1fefba3 100644 --- a/src/browser/webapi/collections.zig +++ b/src/browser/webapi/collections.zig @@ -43,9 +43,5 @@ pub fn registerTypes() []const type { DOMTokenList.KeyIterator, DOMTokenList.ValueIterator, DOMTokenList.EntryIterator, - DOMStringList, - DOMStringList.KeyIterator, - DOMStringList.ValueIterator, - DOMStringList.EntryIterator, }; } diff --git a/src/browser/webapi/collections/DOMStringList.zig b/src/browser/webapi/collections/DOMStringList.zig index 99531e6e2..24e4ea1e1 100644 --- a/src/browser/webapi/collections/DOMStringList.zig +++ b/src/browser/webapi/collections/DOMStringList.zig @@ -21,7 +21,6 @@ const lp = @import("lightpanda"); const js = @import("../../js/js.zig"); const Page = @import("../../Page.zig"); -const Frame = @import("../../Frame.zig"); const GenericIterator = @import("iterator.zig").Entry; pub const KeyIterator = GenericIterator(Iterator, "0"); @@ -31,6 +30,17 @@ pub const EntryIterator = GenericIterator(Iterator, null); const Execution = js.Execution; const Allocator = std.mem.Allocator; +// not registered in collections.zig, because this is one of the rare +// collections that's also available in Worker +pub fn registerTypes() []const type { + return &.{ + DOMStringList, + DOMStringList.KeyIterator, + DOMStringList.ValueIterator, + DOMStringList.EntryIterator, + }; +} + pub const DOMStringList = @This(); _rc: lp.RC(u8) = .{}, diff --git a/src/browser/webapi/storage/idb/Engine.zig b/src/browser/webapi/storage/idb/Engine.zig index 6d80c9621..7b5c0b36e 100644 --- a/src/browser/webapi/storage/idb/Engine.zig +++ b/src/browser/webapi/storage/idb/Engine.zig @@ -172,6 +172,10 @@ pub fn close(self: *Engine) void { self.conn.close(); } +pub fn lastError(self: *const Engine) [:0]const u8 { + return self.conn.lastError(); +} + pub fn begin(self: *Engine) !void { return self.conn.exec("begin immediate", .{}); } diff --git a/src/browser/webapi/storage/idb/IDBCursor.zig b/src/browser/webapi/storage/idb/IDBCursor.zig index ac93b47f4..391ffc0d9 100644 --- a/src/browser/webapi/storage/idb/IDBCursor.zig +++ b/src/browser/webapi/storage/idb/IDBCursor.zig @@ -60,6 +60,9 @@ _key: ?[]const u8 = null, _primary_key: ?[]const u8 = null, // Current record's serialized value bytes (null when key-only or exhausted). _value: ?[]const u8 = null, +// The deserialized JS value, cached so repeated `.value` reads return the same +// object (and observe mutations to it). Reset whenever the cursor repositions. +_value_js: ?*js.Value.BareGlobal = null, // Backing storage for _key/_primary_key/_value, reused across positions so a // long scan holds one record's worth of memory, not the whole traversal. @@ -160,7 +163,7 @@ pub fn releaseRef(self: *IDBCursor, page: *Page) void { // Run the deferred seek, staging the positioned cursor (or null) on the request. pub fn runIterate(self: *IDBCursor, seek: Seek, offset: u32, exec: *Execution) !void { self.iterate(seek, offset, exec) catch |err| { - log.warn(.storage, "idb cursor iterate", .{ .err = err }); + log.warn(.storage, "idb cursor iterate", .{ .err = err, .sqlite = self._engine.lastError() }); self._request.setError(err); }; } @@ -222,21 +225,17 @@ pub fn advance(self: *IDBCursor, count: u32, exec: *Execution) !void { } pub fn update(self: *IDBCursor, value: js.Value, exec: *Execution) !*IDBRequest { - if (self._txn._mode == .readonly) { - return error.ReadOnlyError; - } - - if (self._txn._settled == true) { - return error.TransactionInactiveError; - } - - if (self._got_value == false) { - return error.InvalidStateError; - } + try self.assertCanUpdate(); // The record sits at the primary (store) key, even for an index cursor. const current_key = self._primary_key orelse return error.InvalidStateError; + // Structured-clone the value now, synchronously: an unserializable value must + // throw DataCloneError from update() itself, not fail later in the drain. The + // stored clone also decouples the record from any later mutation of the arg. + const serialized = value.serialize() catch return error.TryCatchRethrow; + defer serialized.deinit(); + // For an in-line store, the value's own key must match the record's key. if (self._store._key_path) |kp| { const extracted = Key.evaluatePath(value, kp) orelse return error.DataError; @@ -246,28 +245,24 @@ pub fn update(self: *IDBCursor, value: js.Value, exec: *Execution) !*IDBRequest } } - // Snapshot the record key: the write runs in the drain, by which point a - // `continue` could have moved the cursor's live position (and reused its - // key buffer). + // Snapshot the record key and the serialized clone onto the transaction arena: + // the write runs in the drain, by which point a `continue` could have moved + // the cursor's live position (and reused its key buffer). const key = try self._txn._arena.dupe(u8, current_key); + const bytes = try self._txn._arena.dupe(u8, serialized.bytes()); const request = try self._txn.newRequest(); - const value_global = try self._txn.persist(value); - return request.submit(.{ .cursor_update = .{ .cursor = self, .key = key, .value = value_global } }, exec); + return request.submit(.{ .cursor_update = .{ .cursor = self, .key = key, .value = bytes } }, exec); } -pub fn runUpdate(self: *IDBCursor, request: *IDBRequest, key: []const u8, value_global: *js.Value.BareGlobal, exec: *Execution) !void { - defer value_global.deinit(); +pub fn runUpdate(self: *IDBCursor, request: *IDBRequest, key: []const u8, bytes: []const u8, exec: *Execution) !void { const local = exec.js.local.?; - const value = value_global.local(local); - - const serialized = value.serialize() catch |err| { + const value = js.Value.deserialize(local, bytes) catch |err| { request.setError(err); return; }; - defer serialized.deinit(); - self._store.writeAt(key, value, serialized.bytes(), exec) catch |err| { - log.warn(.storage, "idb cursor update", .{ .err = err }); + self._store.writeAt(key, value, bytes, exec) catch |err| { + log.warn(.storage, "idb cursor update", .{ .err = err, .sqlite = self._engine.lastError() }); request.setError(err); return; }; @@ -275,17 +270,7 @@ pub fn runUpdate(self: *IDBCursor, request: *IDBRequest, key: []const u8, value_ } pub fn delete(self: *IDBCursor, exec: *Execution) !*IDBRequest { - if (self._txn._mode == .readonly) { - return error.ReadOnlyError; - } - - if (self._txn._settled == true) { - return error.TransactionInactiveError; - } - - if (self._got_value == false) { - return error.InvalidStateError; - } + try self.assertCanUpdate(); // Snapshot the record key (see update): the delete runs later, in the drain. const current_key = self._primary_key orelse return error.InvalidStateError; @@ -296,7 +281,7 @@ pub fn delete(self: *IDBCursor, exec: *Execution) !*IDBRequest { pub fn runDelete(self: *IDBCursor, request: *IDBRequest, key: []const u8) !void { self._store.deleteAt(key) catch |err| { - log.warn(.storage, "idb cursor delete", .{ .err = err }); + log.warn(.storage, "idb cursor delete", .{ .err = err, .sqlite = self._engine.lastError() }); request.setError(err); }; } @@ -359,6 +344,7 @@ fn reiterate(self: *IDBCursor, seek: Seek, offset: u32, exec: *Execution) !void fn position(self: *IDBCursor, key: []const u8, primary_key: []const u8, value: ?[]const u8) !void { const arena = self._txn._arena; + self.invalidateValue(); self._key_buf.clearRetainingCapacity(); try self._key_buf.appendSlice(arena, key); @@ -379,12 +365,36 @@ fn position(self: *IDBCursor, key: []const u8, primary_key: []const u8, value: ? } fn exhaust(self: *IDBCursor) void { + self.invalidateValue(); self._key = null; self._primary_key = null; self._value = null; self._request.setNull(); } +// Drop any cached `.value` object; the next read re-deserializes at the new +// position. The persisted slot's handle is reset here so it doesn't pin the old +// value until transaction teardown. +fn invalidateValue(self: *IDBCursor) void { + if (self._value_js) |slot| { + slot.deinit(); + self._value_js = null; + } +} + +// The deserialized current value, created on first read and cached so repeated +// `.value` accesses return the same JS object (see IDBCursorWithValue.getValue). +pub fn getValueJs(self: *IDBCursor, exec: *Execution) !?js.Value { + const bytes = self._value orelse return null; + const local = exec.js.local.?; + if (self._value_js) |slot| { + return slot.local(local); + } + const value = try js.Value.deserialize(local, bytes); + self._value_js = try self._txn.persist(value); + return value; +} + // validate the state before we can advance/continue fn prepareIterate(self: *IDBCursor) !void { if (self._txn._settled == true) { @@ -398,6 +408,20 @@ fn prepareIterate(self: *IDBCursor) !void { self._got_value = false; } +fn assertCanUpdate(self: *IDBCursor) !void { + try self._txn.assertActive(); + + if (self._txn._mode == .readonly) { + return error.ReadOnlyError; + } + if (self._store._deleted) { + return error.InvalidStateError; + } + if (self._got_value == false) { + return error.InvalidStateError; + } +} + pub const JsApi = struct { pub const bridge = js.Bridge(IDBCursor); diff --git a/src/browser/webapi/storage/idb/IDBCursorWithValue.zig b/src/browser/webapi/storage/idb/IDBCursorWithValue.zig index a3ef220fd..aee1bf1cc 100644 --- a/src/browser/webapi/storage/idb/IDBCursorWithValue.zig +++ b/src/browser/webapi/storage/idb/IDBCursorWithValue.zig @@ -33,8 +33,7 @@ pub fn init(cursor: *IDBCursor) !*IDBCursorWithValue { } pub fn getValue(self: *const IDBCursorWithValue, exec: *Execution) !?js.Value { - const bytes = self._proto._value orelse return null; - return try js.Value.deserialize(exec.js.local.?, bytes); + return self._proto.getValueJs(exec); } pub const JsApi = struct { diff --git a/src/browser/webapi/storage/idb/IDBFactory.zig b/src/browser/webapi/storage/idb/IDBFactory.zig index 36abc8d73..d0d503890 100644 --- a/src/browser/webapi/storage/idb/IDBFactory.zig +++ b/src/browser/webapi/storage/idb/IDBFactory.zig @@ -131,7 +131,7 @@ const OpenContext = struct { } const upgrading = self.runOpen(engine) catch |err| blk: { - log.warn(.storage, "idb open", .{ .err = err, .name = self.name }); + log.warn(.storage, "idb open", .{ .err = err, .name = self.name, .sqlite = engine.lastError() }); self.request.setError(err); self.request.deliver(self.exec) catch {}; break :blk false; @@ -350,7 +350,7 @@ const DeleteContext = struct { defer _ = engine.releaseGate(&self._gate_waiter); self.runDelete(engine) catch |err| { - log.warn(.storage, "idb deleteDatabase", .{ .err = err, .name = self.name }); + log.warn(.storage, "idb deleteDatabase", .{ .err = err, .name = self.name, .sqlite = engine.lastError() }); self.request.setError(err); self.request.deliver(self.exec) catch {}; }; diff --git a/src/browser/webapi/storage/idb/IDBIndex.zig b/src/browser/webapi/storage/idb/IDBIndex.zig index 03c532342..be83891b1 100644 --- a/src/browser/webapi/storage/idb/IDBIndex.zig +++ b/src/browser/webapi/storage/idb/IDBIndex.zig @@ -82,7 +82,7 @@ pub fn get(self: *IDBIndex, query: js.Value, exec: *Execution) !*IDBRequest { pub fn runGet(self: *IDBIndex, request: *IDBRequest, bounds: Engine.Bounds, exec: *Execution) !void { const arena = exec.call_arena; const bytes = self._engine.indexGetRange(arena, self._store._store_id, self._index_id, bounds) catch |err| { - log.warn(.storage, "idb index get", .{ .err = err }); + log.warn(.storage, "idb index get", .{ .err = err, .sqlite = self._engine.lastError() }); request.setError(err); return; }; @@ -100,7 +100,7 @@ pub fn getKey(self: *IDBIndex, query: js.Value, exec: *Execution) !*IDBRequest { pub fn runGetKey(self: *IDBIndex, request: *IDBRequest, bounds: Engine.Bounds, exec: *Execution) !void { const arena = exec.call_arena; const bytes = self._engine.indexGetKeyRange(arena, self._index_id, bounds) catch |err| { - log.warn(.storage, "idb index getKey", .{ .err = err }); + log.warn(.storage, "idb index getKey", .{ .err = err, .sqlite = self._engine.lastError() }); request.setError(err); return; }; @@ -132,7 +132,7 @@ fn _getAll(self: *IDBIndex, query_or_options: ?js.Value, count_: ?u32, mode: IDB pub fn runGetAll(self: *IDBIndex, request: *IDBRequest, args: IDBKeyRange.GetAllArgs, mode: IDBObjectStore.GetAllMode, exec: *Execution) !void { const arr = self.collectAll(args, mode, exec) catch |err| { - log.warn(.storage, "idb index getAll", .{ .err = err }); + log.warn(.storage, "idb index getAll", .{ .err = err, .sqlite = self._engine.lastError() }); request.setError(err); return; }; @@ -176,7 +176,7 @@ pub fn count(self: *IDBIndex, query: ?js.Value, exec: *Execution) !*IDBRequest { pub fn runCount(self: *IDBIndex, request: *IDBRequest, bounds: Engine.Bounds, exec: *Execution) !void { const n = self._engine.indexCountRange(self._index_id, bounds) catch |err| { - log.warn(.storage, "idb index count", .{ .err = err }); + log.warn(.storage, "idb index count", .{ .err = err, .sqlite = self._engine.lastError() }); request.setError(err); return; }; diff --git a/src/browser/webapi/storage/idb/IDBObjectStore.zig b/src/browser/webapi/storage/idb/IDBObjectStore.zig index e934243be..1451c2178 100644 --- a/src/browser/webapi/storage/idb/IDBObjectStore.zig +++ b/src/browser/webapi/storage/idb/IDBObjectStore.zig @@ -44,6 +44,7 @@ _name: []const u8, _key_path: ?[]const u8, _auto_increment: bool, _txn: *IDBTransaction, +_deleted: bool = false, // identity map, store.indexes('a') === store.index('a') _indexes: std.ArrayList(*IDBIndex) = .empty, @@ -83,6 +84,7 @@ pub fn put(self: *IDBObjectStore, value: js.Value, key: ?js.Value, exec: *Execut } pub fn get(self: *IDBObjectStore, query: js.Value, exec: *Execution) !*IDBRequest { + try self.assertLive(); const txn = self._txn; try txn.assertActive(); const bounds = try IDBKeyRange.resolveQuery(txn._arena, query, exec); @@ -93,7 +95,7 @@ pub fn get(self: *IDBObjectStore, query: js.Value, exec: *Execution) !*IDBReques pub fn runGet(self: *IDBObjectStore, request: *IDBRequest, bounds: Engine.Bounds, exec: *Execution) !void { const arena = exec.call_arena; const bytes = self._engine.getRange(arena, self._store_id, bounds) catch |err| { - log.warn(.storage, "idb get", .{ .err = err }); + log.warn(.storage, "idb get", .{ .err = err, .sqlite = self._engine.lastError() }); request.setError(err); return; }; @@ -102,6 +104,7 @@ pub fn runGet(self: *IDBObjectStore, request: *IDBRequest, bounds: Engine.Bounds } pub fn delete(self: *IDBObjectStore, query: js.Value, exec: *Execution) !*IDBRequest { + try self.assertLive(); const txn = self._txn; if (txn._mode == .readonly) { return error.ReadOnlyError; @@ -114,7 +117,7 @@ pub fn delete(self: *IDBObjectStore, query: js.Value, exec: *Execution) !*IDBReq pub fn runDelete(self: *IDBObjectStore, request: *IDBRequest, bounds: Engine.Bounds, _: *Execution) !void { self.deleteBounds(bounds) catch |err| { - log.warn(.storage, "idb delete", .{ .err = err }); + log.warn(.storage, "idb delete", .{ .err = err, .sqlite = self._engine.lastError() }); request.setError(err); }; } @@ -125,6 +128,7 @@ fn deleteBounds(self: *IDBObjectStore, bounds: Engine.Bounds) !void { } pub fn clear(self: *IDBObjectStore, exec: *Execution) !*IDBRequest { + try self.assertLive(); const txn = self._txn; if (txn._mode == .readonly) { return error.ReadOnlyError; @@ -136,7 +140,7 @@ pub fn clear(self: *IDBObjectStore, exec: *Execution) !*IDBRequest { pub fn runClear(self: *IDBObjectStore, request: *IDBRequest, _: *Execution) !void { self.clearAll() catch |err| { - log.warn(.storage, "idb clear", .{ .err = err }); + log.warn(.storage, "idb clear", .{ .err = err, .sqlite = self._engine.lastError() }); request.setError(err); }; } @@ -147,6 +151,7 @@ fn clearAll(self: *IDBObjectStore) !void { } pub fn count(self: *IDBObjectStore, query: ?js.Value, exec: *Execution) !*IDBRequest { + try self.assertLive(); const txn = self._txn; try txn.assertActive(); const bounds = try IDBKeyRange.resolveQuery(txn._arena, query, exec); @@ -156,7 +161,7 @@ pub fn count(self: *IDBObjectStore, query: ?js.Value, exec: *Execution) !*IDBReq pub fn runCount(self: *IDBObjectStore, request: *IDBRequest, bounds: Engine.Bounds, exec: *Execution) !void { const n = self._engine.countRange(self._store_id, bounds) catch |err| { - log.warn(.storage, "idb count", .{ .err = err }); + log.warn(.storage, "idb count", .{ .err = err, .sqlite = self._engine.lastError() }); request.setError(err); return; }; @@ -175,6 +180,7 @@ pub fn getAllKeys(self: *IDBObjectStore, query_or_options: ?js.Value, count_: ?u } pub fn getAllRecords(self: *IDBObjectStore, options: ?js.Value, exec: *Execution) !*IDBRequest { + try self.assertLive(); const txn = self._txn; try txn.assertActive(); const args = try IDBKeyRange.resolveGetAllOptions(txn._arena, options, exec); @@ -183,6 +189,7 @@ pub fn getAllRecords(self: *IDBObjectStore, options: ?js.Value, exec: *Execution } fn _getAll(self: *IDBObjectStore, query_or_options: ?js.Value, count_: ?u32, mode: GetAllMode, exec: *Execution) !*IDBRequest { + try self.assertLive(); const txn = self._txn; try txn.assertActive(); const args = try IDBKeyRange.resolveGetAll(txn._arena, query_or_options, count_, exec); @@ -192,7 +199,7 @@ fn _getAll(self: *IDBObjectStore, query_or_options: ?js.Value, count_: ?u32, mod pub fn runGetAll(self: *IDBObjectStore, request: *IDBRequest, args: IDBKeyRange.GetAllArgs, mode: GetAllMode, exec: *Execution) !void { const arr = self.collectAll(exec, args, mode) catch |err| { - log.warn(.storage, "idb getAll", .{ .err = err }); + log.warn(.storage, "idb getAll", .{ .err = err, .sqlite = self._engine.lastError() }); request.setError(err); return; }; @@ -225,6 +232,7 @@ fn collectAll(self: *IDBObjectStore, exec: *Execution, args: IDBKeyRange.GetAllA } pub fn getKey(self: *IDBObjectStore, query: js.Value, exec: *Execution) !*IDBRequest { + try self.assertLive(); const txn = self._txn; try txn.assertActive(); const bounds = try IDBKeyRange.resolveQuery(txn._arena, query, exec); @@ -235,7 +243,7 @@ pub fn getKey(self: *IDBObjectStore, query: js.Value, exec: *Execution) !*IDBReq pub fn runGetKey(self: *IDBObjectStore, request: *IDBRequest, bounds: Engine.Bounds, exec: *Execution) !void { const arena = exec.call_arena; const found = self._engine.getKeyRange(arena, self._store_id, bounds) catch |err| { - log.warn(.storage, "idb getKey", .{ .err = err }); + log.warn(.storage, "idb getKey", .{ .err = err, .sqlite = self._engine.lastError() }); request.setError(err); return; }; @@ -244,11 +252,13 @@ pub fn runGetKey(self: *IDBObjectStore, request: *IDBRequest, bounds: Engine.Bou } pub fn openCursor(self: *IDBObjectStore, query: ?js.Value, direction: ?IDBCursor.Direction, exec: *Execution) !*IDBRequest { + try self.assertLive(); const bounds = try IDBKeyRange.resolveQuery(self._txn._arena, query, exec); return IDBCursor.init(self, bounds, direction orelse .next, false, exec); } pub fn openKeyCursor(self: *IDBObjectStore, query: ?js.Value, direction: ?IDBCursor.Direction, exec: *Execution) !*IDBRequest { + try self.assertLive(); const bounds = try IDBKeyRange.resolveQuery(self._txn._arena, query, exec); return IDBCursor.init(self, bounds, direction orelse .next, true, exec); } @@ -285,6 +295,7 @@ pub const PreparedKey = union(enum) { }; fn write(self: *IDBObjectStore, value: js.Value, key_arg: ?js.Value, kind: WriteKind, exec: *Execution) !*IDBRequest { + try self.assertLive(); const txn = self._txn; if (txn._mode == .readonly) { return error.ReadOnlyError; @@ -345,7 +356,7 @@ pub fn runWrite(self: *IDBObjectStore, request: *IDBRequest, kind: WriteKind, va defer value_global.deinit(); self.writeInner(request, kind, value_global, prepared, exec) catch |err| { if (err != error.Constraint) { - log.warn(.storage, "idb write", .{ .err = err, .kind = kind }); + log.warn(.storage, "idb write", .{ .err = err, .kind = kind, .sqlite = self._engine.lastError() }); } request.setError(err); }; @@ -463,10 +474,13 @@ const CreateIndexOptions = struct { // Only callable during an upgrade (versionchange transaction). pub fn createIndex(self: *IDBObjectStore, name: []const u8, key_path: []const u8, options: ?CreateIndexOptions, exec: *Execution) !*IDBIndex { + try self.assertLive(); const txn = self._txn; if (txn._mode != .versionchange) { return error.InvalidStateError; } + // Spec order: the transaction-state check precedes the index-name check. + try txn.assertActive(); const opts = options orelse CreateIndexOptions{}; try self._engine.savepoint(); @@ -508,10 +522,13 @@ pub fn createIndex(self: *IDBObjectStore, name: []const u8, key_path: []const u8 // Only callable during an upgrade (versionchange transaction). pub fn deleteIndex(self: *IDBObjectStore, name: []const u8, _: *Execution) !void { + try self.assertLive(); const txn = self._txn; if (txn._mode != .versionchange) { return error.InvalidStateError; } + // Spec order: the transaction-state check precedes the index-name check. + try txn.assertActive(); self._engine.deleteIndexRow(self._store_id, name) catch |err| switch (err) { error.NotFound => return error.NotFoundError, else => return err, @@ -525,6 +542,7 @@ pub fn deleteIndex(self: *IDBObjectStore, name: []const u8, _: *Execution) !void } pub fn index(self: *IDBObjectStore, name: []const u8, _: *Execution) !*IDBIndex { + try self.assertLive(); for (self._indexes.items) |idx| { if (std.mem.eql(u8, idx._name, name)) { return idx; @@ -549,6 +567,12 @@ pub fn getIndexNames(self: *IDBObjectStore, exec: *Execution) !*DOMStringList { return list; } +fn assertLive(self: *const IDBObjectStore) !void { + if (self._deleted) { + return error.InvalidStateError; + } +} + pub const JsApi = struct { pub const bridge = js.Bridge(IDBObjectStore); diff --git a/src/browser/webapi/storage/idb/IDBRequest.zig b/src/browser/webapi/storage/idb/IDBRequest.zig index 2a95d2555..e6e7ae5ca 100644 --- a/src/browser/webapi/storage/idb/IDBRequest.zig +++ b/src/browser/webapi/storage/idb/IDBRequest.zig @@ -361,7 +361,7 @@ pub const Operation = union(enum) { const IndexQuery = struct { index: *IDBIndex, bounds: Engine.Bounds }; const IndexGetAll = struct { index: *IDBIndex, args: IDBKeyRange.GetAllArgs, mode: IDBObjectStore.GetAllMode }; const CursorIterate = struct { cursor: *IDBCursor, seek: IDBCursor.Seek, offset: u32 }; - const CursorUpdate = struct { cursor: *IDBCursor, key: []const u8, value: *js.Value.BareGlobal }; + const CursorUpdate = struct { cursor: *IDBCursor, key: []const u8, value: []const u8 }; const CursorDelete = struct { cursor: *IDBCursor, key: []const u8 }; }; diff --git a/src/browser/webapi/storage/idb/IDBTransaction.zig b/src/browser/webapi/storage/idb/IDBTransaction.zig index 16b2da278..aa65f7515 100644 --- a/src/browser/webapi/storage/idb/IDBTransaction.zig +++ b/src/browser/webapi/storage/idb/IDBTransaction.zig @@ -307,7 +307,7 @@ pub fn settleStep(self: *IDBTransaction, exec: *Execution) bool { fn commitAndComplete(self: *IDBTransaction, exec: *Execution) void { if (self._begun) { self._engine.commit() catch |err| { - log.warn(.storage, "idb commit", .{ .err = err, .sqlite = self._engine.conn.lastError() }); + log.warn(.storage, "idb commit", .{ .err = err, .sqlite = self._engine.lastError() }); self._engine.rollback(); self._begun = false; _ = self._engine.releaseGate(&self._gate_waiter); @@ -385,6 +385,7 @@ pub fn cacheStore(self: *IDBTransaction, store: *IDBObjectStore) !void { pub fn uncacheStore(self: *IDBTransaction, name: []const u8) void { for (self._stores.items, 0..) |store, i| { if (std.mem.eql(u8, store._name, name)) { + store._deleted = true; _ = self._stores.swapRemove(i); return; }