From e105bc42370c611e5ffe9cfc5957dd78f919944d Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 24 Aug 2021 15:31:18 +0200 Subject: [PATCH 1/2] fix lint warnings --- src/domain/session/room/timeline/TilesCollection.js | 2 +- src/domain/session/room/timeline/TimelineViewModel.js | 4 ++-- src/domain/session/room/timeline/tiles/SimpleTile.js | 6 +++--- src/matrix/room/timeline/persistence/RelationWriter.js | 2 +- src/observable/map/MappedMap.js | 2 +- src/observable/map/ObservableMap.js | 2 +- src/platform/web/Platform.js | 2 +- src/platform/web/ui/general/ListView.js | 4 ++-- src/platform/web/ui/session/room/MessageComposer.js | 1 - src/platform/web/ui/session/room/RoomArchivedView.js | 4 ++-- .../web/ui/session/room/timeline/TextMessageView.js | 2 +- src/utils/EventEmitter.js | 4 ++-- 12 files changed, 17 insertions(+), 18 deletions(-) diff --git a/src/domain/session/room/timeline/TilesCollection.js b/src/domain/session/room/timeline/TilesCollection.js index 0acb2859..10062af2 100644 --- a/src/domain/session/room/timeline/TilesCollection.js +++ b/src/domain/session/room/timeline/TilesCollection.js @@ -219,7 +219,7 @@ export class TilesCollection extends BaseObservableList { } } - onMove(fromIdx, toIdx, value) { + onMove(/*fromIdx, toIdx, value*/) { // this ... cannot happen in the timeline? // perhaps we can use this event to support a local echo (in a different fragment) // to be moved to the key of the remote echo, so we don't loose state ... ? diff --git a/src/domain/session/room/timeline/TimelineViewModel.js b/src/domain/session/room/timeline/TimelineViewModel.js index 7b2765f5..a08ab060 100644 --- a/src/domain/session/room/timeline/TimelineViewModel.js +++ b/src/domain/session/room/timeline/TimelineViewModel.js @@ -59,7 +59,7 @@ export class TimelineViewModel extends ViewModel { } } - unloadAtTop(tileAmount) { + unloadAtTop(/*tileAmount*/) { // get lowerSortKey for tile at index tileAmount - 1 // tell timeline to unload till there (included given key) } @@ -68,7 +68,7 @@ export class TimelineViewModel extends ViewModel { } - unloadAtBottom(tileAmount) { + unloadAtBottom(/*tileAmount*/) { // get upperSortKey for tile at index tiles.length - tileAmount // tell timeline to unload till there (included given key) } diff --git a/src/domain/session/room/timeline/tiles/SimpleTile.js b/src/domain/session/room/timeline/tiles/SimpleTile.js index 68037a14..3c370b72 100644 --- a/src/domain/session/room/timeline/tiles/SimpleTile.js +++ b/src/domain/session/room/timeline/tiles/SimpleTile.js @@ -101,7 +101,7 @@ export class SimpleTile extends ViewModel { // return whether the tile should be removed // as SimpleTile only has one entry, the tile should be removed - removeEntry(entry) { + removeEntry(/*entry*/) { return true; } @@ -110,12 +110,12 @@ export class SimpleTile extends ViewModel { return false; } // let item know it has a new sibling - updatePreviousSibling(prev) { + updatePreviousSibling(/*prev*/) { } // let item know it has a new sibling - updateNextSibling(next) { + updateNextSibling(/*next*/) { } diff --git a/src/matrix/room/timeline/persistence/RelationWriter.js b/src/matrix/room/timeline/persistence/RelationWriter.js index 4944cc64..afc99e6d 100644 --- a/src/matrix/room/timeline/persistence/RelationWriter.js +++ b/src/matrix/room/timeline/persistence/RelationWriter.js @@ -147,7 +147,7 @@ export class RelationWriter { return true; } - _aggregateAnnotation(annotationEvent, targetStorageEntry, log) { + _aggregateAnnotation(annotationEvent, targetStorageEntry/*, log*/) { // TODO: do we want to verify it is a m.reaction event somehow? const relation = getRelation(annotationEvent); if (!relation) { diff --git a/src/observable/map/MappedMap.js b/src/observable/map/MappedMap.js index 47013df8..2a810058 100644 --- a/src/observable/map/MappedMap.js +++ b/src/observable/map/MappedMap.js @@ -42,7 +42,7 @@ export class MappedMap extends BaseObservableMap { this.emitAdd(key, mappedValue); } - onRemove(key, _value) { + onRemove(key/*, _value*/) { const mappedValue = this._mappedValues.get(key); if (this._mappedValues.delete(key)) { this.emitRemove(key, mappedValue); diff --git a/src/observable/map/ObservableMap.js b/src/observable/map/ObservableMap.js index b72cd039..8f5a0922 100644 --- a/src/observable/map/ObservableMap.js +++ b/src/observable/map/ObservableMap.js @@ -156,7 +156,7 @@ export function tests() { assert.equal(key, 1); assert.deepEqual(value, {value: 5}); }, - onUpdate(key, value, params) { + onUpdate(key, value/*, params*/) { update_fired += 1; assert.equal(key, 1); assert.deepEqual(value, {value: 7}); diff --git a/src/platform/web/Platform.js b/src/platform/web/Platform.js index 7e28f36f..40f47101 100644 --- a/src/platform/web/Platform.js +++ b/src/platform/web/Platform.js @@ -221,7 +221,7 @@ export class Platform { if (mimeType) { input.setAttribute("accept", mimeType); } - const promise = new Promise((resolve, reject) => { + const promise = new Promise(resolve => { const checkFile = () => { input.removeEventListener("change", checkFile, true); const file = input.files[0]; diff --git a/src/platform/web/ui/general/ListView.js b/src/platform/web/ui/general/ListView.js index 884eedc4..74aa9d87 100644 --- a/src/platform/web/ui/general/ListView.js +++ b/src/platform/web/ui/general/ListView.js @@ -121,7 +121,7 @@ export class ListView { this.onListChanged(); } - onRemove(idx, _value) { + onRemove(idx/*, _value*/) { this.onBeforeListChanged(); const [child] = this._childInstances.splice(idx, 1); child.root().remove(); @@ -129,7 +129,7 @@ export class ListView { this.onListChanged(); } - onMove(fromIdx, toIdx, value) { + onMove(fromIdx, toIdx/*, value*/) { this.onBeforeListChanged(); const [child] = this._childInstances.splice(fromIdx, 1); this._childInstances.splice(toIdx, 0, child); diff --git a/src/platform/web/ui/session/room/MessageComposer.js b/src/platform/web/ui/session/room/MessageComposer.js index e1d8f13c..c6e8a1ee 100644 --- a/src/platform/web/ui/session/room/MessageComposer.js +++ b/src/platform/web/ui/session/room/MessageComposer.js @@ -17,7 +17,6 @@ limitations under the License. import {TemplateView} from "../../general/TemplateView.js"; import {Popup} from "../../general/Popup.js"; import {Menu} from "../../general/Menu.js"; -import {TextMessageView} from "./timeline/TextMessageView.js"; import {viewClassForEntry} from "./TimelineList.js" export class MessageComposer extends TemplateView { diff --git a/src/platform/web/ui/session/room/RoomArchivedView.js b/src/platform/web/ui/session/room/RoomArchivedView.js index e5e489ed..80b18a08 100644 --- a/src/platform/web/ui/session/room/RoomArchivedView.js +++ b/src/platform/web/ui/session/room/RoomArchivedView.js @@ -17,7 +17,7 @@ limitations under the License. import {TemplateView} from "../../general/TemplateView.js"; export class RoomArchivedView extends TemplateView { - render(t, vm) { + render(t) { return t.div({className: "RoomArchivedView"}, t.h3(vm => vm.description)); } -} \ No newline at end of file +} diff --git a/src/platform/web/ui/session/room/timeline/TextMessageView.js b/src/platform/web/ui/session/room/timeline/TextMessageView.js index ef4d61d5..fcafaf27 100644 --- a/src/platform/web/ui/session/room/timeline/TextMessageView.js +++ b/src/platform/web/ui/session/room/timeline/TextMessageView.js @@ -97,7 +97,7 @@ const formatFunction = { link: linkPart => tag.a({href: linkPart.url, className: "link", target: "_blank", rel: "noopener" }, renderParts(linkPart.inlines)), pill: renderPill, format: formatPart => tag[formatPart.format](renderParts(formatPart.children)), - rule: rulePart => tag.hr(), + rule: () => tag.hr(), list: renderList, image: renderImage, newline: () => tag.br() diff --git a/src/utils/EventEmitter.js b/src/utils/EventEmitter.js index 2d2e4458..5dd56ac3 100644 --- a/src/utils/EventEmitter.js +++ b/src/utils/EventEmitter.js @@ -55,9 +55,9 @@ export class EventEmitter { } } - onFirstSubscriptionAdded(name) {} + onFirstSubscriptionAdded(/* name */) {} - onLastSubscriptionRemoved(name) {} + onLastSubscriptionRemoved(/* name */) {} } export function tests() { From cb9606a87bebdebebc5933b9b30ab7b326dace3c Mon Sep 17 00:00:00 2001 From: Bruno Windels Date: Tue, 24 Aug 2021 15:33:41 +0200 Subject: [PATCH 2/2] remove dead code for incomplete memory store --- .eslintignore | 1 - snowpack.config.js | 1 - src/matrix/storage/memory/Storage.js | 53 ---- src/matrix/storage/memory/Transaction.js | 73 ------ .../memory/stores/RoomTimelineStore.js | 237 ------------------ src/matrix/storage/memory/stores/Store.js | 43 ---- 6 files changed, 408 deletions(-) delete mode 100644 .eslintignore delete mode 100644 src/matrix/storage/memory/Storage.js delete mode 100644 src/matrix/storage/memory/Transaction.js delete mode 100644 src/matrix/storage/memory/stores/RoomTimelineStore.js delete mode 100644 src/matrix/storage/memory/stores/Store.js diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index 732865cb..00000000 --- a/.eslintignore +++ /dev/null @@ -1 +0,0 @@ -src/matrix/storage/memory \ No newline at end of file diff --git a/snowpack.config.js b/snowpack.config.js index 3de1d723..68f33242 100644 --- a/snowpack.config.js +++ b/snowpack.config.js @@ -17,7 +17,6 @@ module.exports = { '**/scripts/**', '**/target/**', '**/prototypes/**', - '**/src/matrix/storage/memory/**', '**/src/platform/web/legacy-polyfill.js', '**/src/platform/web/worker/polyfill.js' ], diff --git a/src/matrix/storage/memory/Storage.js b/src/matrix/storage/memory/Storage.js deleted file mode 100644 index c1c0fe3c..00000000 --- a/src/matrix/storage/memory/Storage.js +++ /dev/null @@ -1,53 +0,0 @@ -/* -Copyright 2020 Bruno Windels - -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 {Transaction} from "./Transaction.js"; -import { STORE_MAP, STORE_NAMES } from "../common.js"; - -export class Storage { - constructor(initialStoreValues = {}) { - this._validateStoreNames(Object.keys(initialStoreValues)); - this.storeNames = STORE_MAP; - this._storeValues = STORE_NAMES.reduce((values, name) => { - values[name] = initialStoreValues[name] || null; - }, {}); - } - - _validateStoreNames(storeNames) { - const idx = storeNames.findIndex(name => !STORE_MAP.hasOwnProperty(name)); - if (idx !== -1) { - throw new Error(`Invalid store name ${storeNames[idx]}`); - } - } - - _createTxn(storeNames, writable) { - this._validateStoreNames(storeNames); - const storeValues = storeNames.reduce((values, name) => { - return values[name] = this._storeValues[name]; - }, {}); - return Promise.resolve(new Transaction(storeValues, writable)); - } - - readTxn(storeNames) { - // TODO: avoid concurrency - return this._createTxn(storeNames, false); - } - - readWriteTxn(storeNames) { - // TODO: avoid concurrency - return this._createTxn(storeNames, true); - } -} diff --git a/src/matrix/storage/memory/Transaction.js b/src/matrix/storage/memory/Transaction.js deleted file mode 100644 index 894db805..00000000 --- a/src/matrix/storage/memory/Transaction.js +++ /dev/null @@ -1,73 +0,0 @@ -/* -Copyright 2020 Bruno Windels - -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 {RoomTimelineStore} from "./stores/RoomTimelineStore.js"; - -export class Transaction { - constructor(storeValues, writable) { - this._storeValues = storeValues; - this._txnStoreValues = {}; - this._writable = writable; - } - - _store(name, mapper) { - if (!this._txnStoreValues.hasOwnProperty(name)) { - if (!this._storeValues.hasOwnProperty(name)) { - throw new Error(`Transaction wasn't opened for store ${name}`); - } - const store = mapper(this._storeValues[name]); - const clone = store.cloneStoreValue(); - // extra prevention for writing - if (!this._writable) { - Object.freeze(clone); - } - this._txnStoreValues[name] = clone; - } - return mapper(this._txnStoreValues[name]); - } - - get session() { - throw new Error("not yet implemented"); - // return this._store("session", storeValue => new SessionStore(storeValue)); - } - - get roomSummary() { - throw new Error("not yet implemented"); - // return this._store("roomSummary", storeValue => new RoomSummaryStore(storeValue)); - } - - get roomTimeline() { - return this._store("roomTimeline", storeValue => new RoomTimelineStore(storeValue)); - } - - get roomState() { - throw new Error("not yet implemented"); - // return this._store("roomState", storeValue => new RoomStateStore(storeValue)); - } - - complete() { - for(let name of Object.keys(this._txnStoreValues)) { - this._storeValues[name] = this._txnStoreValues[name]; - } - this._txnStoreValues = null; - return Promise.resolve(); - } - - abort() { - this._txnStoreValues = null; - return Promise.resolve(); - } -} diff --git a/src/matrix/storage/memory/stores/RoomTimelineStore.js b/src/matrix/storage/memory/stores/RoomTimelineStore.js deleted file mode 100644 index 5be20eae..00000000 --- a/src/matrix/storage/memory/stores/RoomTimelineStore.js +++ /dev/null @@ -1,237 +0,0 @@ -/* -Copyright 2020 Bruno Windels - -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 {SortKey} from "../../room/timeline/SortKey.js"; -import {sortedIndex} from "../../../utils/sortedIndex.js"; -import {Store} from "./Store.js"; - -function compareKeys(key, entry) { - if (key.roomId === entry.roomId) { - return key.sortKey.compare(entry.sortKey); - } else { - return key.roomId < entry.roomId ? -1 : 1; - } -} - -class Range { - constructor(timeline, lower, upper, lowerOpen, upperOpen) { - this._timeline = timeline; - this._lower = lower; - this._upper = upper; - this._lowerOpen = lowerOpen; - this._upperOpen = upperOpen; - } - - /** projects the range onto the timeline array */ - project(roomId, maxCount = Number.MAX_SAFE_INTEGER) { - // determine lowest and highest allowed index. - // Important not to bleed into other roomIds here. - const lowerKey = {roomId, sortKey: this._lower || SortKey.minKey }; - // apply lower key being open (excludes given key) - let minIndex = sortedIndex(this._timeline, lowerKey, compareKeys); - if (this._lowerOpen && minIndex < this._timeline.length && compareKeys(lowerKey, this._timeline[minIndex]) === 0) { - minIndex += 1; - } - const upperKey = {roomId, sortKey: this._upper || SortKey.maxKey }; - // apply upper key being open (excludes given key) - let maxIndex = sortedIndex(this._timeline, upperKey, compareKeys); - if (this._upperOpen && maxIndex < this._timeline.length && compareKeys(upperKey, this._timeline[maxIndex]) === 0) { - maxIndex -= 1; - } - // find out from which edge we should grow - // if upper or lower bound - // again, important not to go below minIndex or above maxIndex - // to avoid bleeding into other rooms - let startIndex, endIndex; - if (!this._lower && this._upper) { - startIndex = Math.max(minIndex, maxIndex - maxCount); - endIndex = maxIndex; - } else if (this._lower && !this._upper) { - startIndex = minIndex; - endIndex = Math.min(maxIndex, minIndex + maxCount); - } else { - startIndex = minIndex; - endIndex = maxIndex; - } - - // if startIndex is out of range, make range empty - if (startIndex === this._timeline.length) { - startIndex = endIndex = 0; - } - const count = endIndex - startIndex; - return {startIndex, count}; - } - - select(roomId, maxCount) { - const {startIndex, count} = this.project(roomId, this._timeline, maxCount); - return this._timeline.slice(startIndex, startIndex + count); - } -} - -export class RoomTimelineStore extends Store { - constructor(timeline, writable) { - super(timeline || [], writable); - } - - get _timeline() { - return this._storeValue; - } - - /** Creates a range that only includes the given key - * @param {SortKey} sortKey the key - * @return {Range} the created range - */ - onlyRange(sortKey) { - return new Range(this._timeline, sortKey, sortKey); - } - - /** Creates a range that includes all keys before sortKey, and optionally also the key itself. - * @param {SortKey} sortKey the key - * @param {boolean} [open=false] whether the key is included (false) or excluded (true) from the range at the upper end. - * @return {Range} the created range - */ - upperBoundRange(sortKey, open=false) { - return new Range(this._timeline, undefined, sortKey, undefined, open); - } - - /** Creates a range that includes all keys after sortKey, and optionally also the key itself. - * @param {SortKey} sortKey the key - * @param {boolean} [open=false] whether the key is included (false) or excluded (true) from the range at the lower end. - * @return {Range} the created range - */ - lowerBoundRange(sortKey, open=false) { - return new Range(this._timeline, sortKey, undefined, open); - } - - /** Creates a range that includes all keys between `lower` and `upper`, and optionally the given keys as well. - * @param {SortKey} lower the lower key - * @param {SortKey} upper the upper key - * @param {boolean} [lowerOpen=false] whether the lower key is included (false) or excluded (true) from the range. - * @param {boolean} [upperOpen=false] whether the upper key is included (false) or excluded (true) from the range. - * @return {Range} the created range - */ - boundRange(lower, upper, lowerOpen=false, upperOpen=false) { - return new Range(this._timeline, lower, upper, lowerOpen, upperOpen); - } - - /** Looks up the last `amount` entries in the timeline for `roomId`. - * @param {string} roomId - * @param {number} amount - * @return {Promise} a promise resolving to an array with 0 or more entries, in ascending order. - */ - lastEvents(roomId, amount) { - return this.eventsBefore(roomId, SortKey.maxKey, amount); - } - - /** Looks up the first `amount` entries in the timeline for `roomId`. - * @param {string} roomId - * @param {number} amount - * @return {Promise} a promise resolving to an array with 0 or more entries, in ascending order. - */ - firstEvents(roomId, amount) { - return this.eventsAfter(roomId, SortKey.minKey, amount); - } - - /** Looks up `amount` entries after `sortKey` in the timeline for `roomId`. - * The entry for `sortKey` is not included. - * @param {string} roomId - * @param {SortKey} sortKey - * @param {number} amount - * @return {Promise} a promise resolving to an array with 0 or more entries, in ascending order. - */ - eventsAfter(roomId, sortKey, amount) { - const events = this.lowerBoundRange(sortKey, true).select(roomId, amount); - return Promise.resolve(events); - } - - /** Looks up `amount` entries before `sortKey` in the timeline for `roomId`. - * The entry for `sortKey` is not included. - * @param {string} roomId - * @param {SortKey} sortKey - * @param {number} amount - * @return {Promise} a promise resolving to an array with 0 or more entries, in ascending order. - */ - eventsBefore(roomId, sortKey, amount) { - const events = this.upperBoundRange(sortKey, true).select(roomId, amount); - return Promise.resolve(events); - } - - /** Looks up the first, if any, event entry (so excluding gap entries) after `sortKey`. - * @param {string} roomId - * @param {SortKey} sortKey - * @return {Promise<(?Entry)>} a promise resolving to entry, if any. - */ - nextEvent(roomId, sortKey) { - const searchSpace = this.lowerBoundRange(sortKey, true).select(roomId); - const event = searchSpace.find(entry => !!entry.event); - return Promise.resolve(event); - } - - /** Looks up the first, if any, event entry (so excluding gap entries) before `sortKey`. - * @param {string} roomId - * @param {SortKey} sortKey - * @return {Promise<(?Entry)>} a promise resolving to entry, if any. - */ - previousEvent(roomId, sortKey) { - const searchSpace = this.upperBoundRange(sortKey, true).select(roomId); - const event = searchSpace.reverse().find(entry => !!entry.event); - return Promise.resolve(event); - } - - /** Inserts a new entry into the store. The combination of roomId and sortKey should not exist yet, or an error is thrown. - * @param {Entry} entry the entry to insert - * @return {Promise<>} a promise resolving to undefined if the operation was successful, or a StorageError if not. - * @throws {StorageError} ... - */ - insert(entry) { - this.assertWritable(); - const insertIndex = sortedIndex(this._timeline, entry, compareKeys); - if (insertIndex < this._timeline.length) { - const existingEntry = this._timeline[insertIndex]; - if (compareKeys(entry, existingEntry) === 0) { - return Promise.reject(new Error("entry already exists")); - } - } - this._timeline.splice(insertIndex, 0, entry); - return Promise.resolve(); - } - - /** Updates the entry into the store with the given [roomId, sortKey] combination. - * If not yet present, will insert. Might be slower than add. - * @param {Entry} entry the entry to update. - * @return {Promise<>} a promise resolving to undefined if the operation was successful, or a StorageError if not. - */ - update(entry) { - this.assertWritable(); - let update = false; - const updateIndex = sortedIndex(this._timeline, entry, compareKeys); - if (updateIndex < this._timeline.length) { - const existingEntry = this._timeline[updateIndex]; - if (compareKeys(entry, existingEntry) === 0) { - update = true; - } - } - this._timeline.splice(updateIndex, update ? 1 : 0, entry); - return Promise.resolve(); - } - - get(roomId, sortKey) { - const range = this.onlyRange(sortKey); - const {startIndex, count} = range.project(roomId); - const event = count ? this._timeline[startIndex] : undefined; - return Promise.resolve(event); - } -} diff --git a/src/matrix/storage/memory/stores/Store.js b/src/matrix/storage/memory/stores/Store.js deleted file mode 100644 index c7218ab8..00000000 --- a/src/matrix/storage/memory/stores/Store.js +++ /dev/null @@ -1,43 +0,0 @@ -/* -Copyright 2020 Bruno Windels - -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. -*/ - -export class Store { - constructor(storeValue, writable) { - this._storeValue = storeValue; - this._writable = writable; - } - - // makes a copy deep enough that any modifications in the store - // won't affect the original - // used for transactions - cloneStoreValue() { - // assumes 1 level deep is enough, and that values will be replaced - // rather than updated. - if (Array.isArray(this._storeValue)) { - return this._storeValue.slice(); - } else if (typeof this._storeValue === "object") { - return Object.assign({}, this._storeValue); - } else { - return this._storeValue; - } - } - - assertWritable() { - if (!this._writable) { - throw new Error("Tried to write in read-only transaction"); - } - } -}