Skip to content

[IndexedDB] Update getAllRecords explainer to add direction to getAll and getAllKeys #1040

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

Merged
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 64 additions & 5 deletions IndexedDbGetAllEntries/explainer.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,38 @@
## Participate

- https://github.com/w3c/IndexedDB/issues/206
- https://github.com/w3c/IndexedDB/issues/130

## Introduction

[`IndexedDB`](https://developer.mozilla.org/en-US/docs/Web/API/IndexedDB_API) is a transactional database for client-side storage. Each record in the database contains a key-value pair. [`getAll()`](https://developer.mozilla.org/en-US/docs/Web/API/IDBObjectStore/getAll) enumerates database record values sorted by key in ascending order. [`getAllKeys()`](https://developer.mozilla.org/en-US/docs/Web/API/IDBObjectStore/getAllKeys) enumerates database record primary keys sorted by key in ascending order.

This explainer proposes a new operation, `getAllRecords()`, which combines [`getAllKeys()`](https://developer.mozilla.org/en-US/docs/Web/API/IDBObjectStore/getAllKeys) with [`getAll()`](https://developer.mozilla.org/en-US/docs/Web/API/IDBObjectStore/getAll) to enumerate both primary keys and values at the same time. For an [`IDBIndex`](https://developer.mozilla.org/en-US/docs/Web/API/IDBIndex), `getAllRecords()` also provides the record's index key in addition to the primary key and value. Lastly, `getAllRecords()` offers a new direction option to enumerate records sorted by key in descending order.

To add the direction option to the existing `getAll()` and `getAllKeys()` operations, this explainer proposes new function overloads that accept the same argument as `getAllRecords()`: the `IDBGetAllOptions` dictionary.

## Goals

Decrease the latency of database read operations. By retrieving the primary key, value and index key for database records through a single operation, `getAllRecords()` reduces the number of JavaScript events required to read records. Each JavaScript event runs as a task on the main JavaScript thread. These tasks can introduce overhead when reading records requires a sequence of tasks that go back and forth between the main JavaScript thread and the IndexedDB I/O thread.

For batched record iteration, for example, retrieving *N* records at a time, the primary and index keys provided by `getAllRecords()` can eliminate the need for an [`IDBCursor`](https://developer.mozilla.org/en-US/docs/Web/API/IDBCursor), which further reduces the number of JavaScript events required. To read the next *N* records, instead of advancing a cursor to determine the range of the next batch, getAllRecords() can use the primary key or the index key retrieved by the results from the previous batch.

Update the existing operations `getAll()` and `getAllKeys()` to support the same query options as `getAllRecords()`, which adds direction. For some scenarios, `getAll()` and `getAllKeys()` may suffice. For example, developers may use `getAllKeys()` to defer loading values until needed. For records with inline keys, `getAll()` already retrieves both key and value.

## `IDBObject::getAllRecords()` and `IDBIndex::getAllRecords()`

This explainer proposes adding `getAllRecords()` to both [`IDBObjectStore`](https://www.w3.org/TR/IndexedDB/#idbobjectstore) and [`IDBIndex`](https://www.w3.org/TR/IndexedDB/#idbindex). `getAllRecords()` creates a new `IDBRequest` that queries its `IDBObjectStore` or `IDBIndex` owner. The `IDBRequest` completes with an array of `IDBRecord` results. Each `IDBRecord` contains the `key`, `primaryKey` and `value` attributes. For `IDBIndex`, `key` is the record's index key. For `IDBObjectStore`, both `key` and `primaryKey` return the same value. The pre-existing [`IDBCursorWithValue`](https://www.w3.org/TR/IndexedDB/#idbcursorwithvalue) interface contains the same attributes and values for both `IDBObjectStore` and `IDBIndex`. However, unlike `getAllRecords()`, a cursor may only read one record at a time.

## Adding direction to `getAll()` and `getAllKeys()`

This explainer proposes using `getAllRecords()` as feature detection for direction support in `getAllKeys()` and `getAll()`. `getAllRecords()` introduces the `IDBGetAllOptions` dictionary, which developers may also use with `getAll()` and `getAllKeys()`. Before using `IDBGetAllOptions`, developers must check for the existence of `getAllRecords()` in `IDBObjectStore` or `IDBIndex`.

## Compatibility risk

Overloading `getAll()` and `getAllKeys()` to accept the `IDBGetAllOptions` dictionary introduces compatibility risk. Prior to this proposal, when passed a dictionary argument, both `getAll()` and `getAllKeys()` throw an exception after [failing to convert the dictionary to a key range](https://w3c.github.io/IndexedDB/#convert-a-value-to-a-key-range). After the overload, `getAllKeys()` and `getAll()` will no longer throw for dictionary input. When the `IDBGetAllOptions` dictionary initializes with its default values, it creates a query that retrieves all of the keys or values from the entire database.

Since using a dictionary with `getAll()` and `getAllKeys()` is a programming error, we believe compat risk is low.

## Key scenarios

### Read multiple database records through a single request
Expand Down Expand Up @@ -182,6 +197,27 @@ if (!results.done) {
}
```

### Use direction with `getAllKeys()` after feature detection

`getAllRecords()` introduces the `IDBGetAllOptions` dictionary, which developers may also use with `getAll()` and `getAllKeys()`. Before using `IDBGetAllOptions`, developers must check for the existence of `getAllRecords()` in `IDBObjectStore` or `IDBIndex`.

```js
const read_transaction = database.transaction('my_object_store', "readonly");
const object_store = read_transaction.objectStore('my_object_store');

// Use feature detection to determine if this browser supports `getAllRecords()`.
if ('getAllRecords' in object_store) {
// Request the last 5 primary keys in `object_store`.
const get_all_options = {
direction = 'prev',
count: 5
};
const request = object_store.getAllKeys(get_all_options);
} else {
// Fallback to a cursor with direction: 'prev' for this query.
}
```

## Considered alternatives

### `getAllEntries()`
Expand All @@ -194,14 +230,10 @@ Similar to `getAllRecords()` but [provides results as an array of entries](https

Developers may directly use the entry results to construct a `Map` or `Object` since the entry results are inspired by ECMAScript's [Map.prototype.entries()](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map/entries). However, `getAllEntries()` has unusual ergonomics, requiring indices like `0` and `1` to access the record properties like `key` and `value`. Also, IndexedDB database records do not map cleanly to ECMAScript entries. For `IDBIndex`, the results contain a third element for index key. For an alternate form, `[[ indexKey1, [ primaryKey1, value1]], [ indexKey2, [ primaryKey2, value2]], ... ]`, the index key cannot always serve as the entry's key since the index key may not be unique across all records.

### Adding direction to `getAll()` and `getAllKeys()`

This will be pursued separately. Join the discussion at https://github.com/w3c/IndexedDB/issues/130. Providing the direction option on `getAllKeys()` might be useful for reverse iteration scenarios that don't need to load every value enumerated.

## WebIDL

```js
dictionary IDBGetAllRecordsOptions {
dictionary IDBGetAllOptions {
// A key or an `IDBKeyRange` identifying the records to retrieve.
any query = null;

Expand All @@ -228,6 +260,20 @@ partial interface IDBObjectStore {
// `[[primaryKey1, value1], [primaryKey2, value2], ... ]`
[NewObject, RaisesException]
IDBRequest getAllRecords(optional IDBGetAllRecordsOptions options = {});

// For `getAll()` and `getAllKeys()`, add support for the direction option
// through a new overload, which accepts a `IDBGetAllOptions` dictionary as
// the first and only argument.
//
// IDBRequest getAll(optional IDBGetAllOptions options);
// IDBRequest getAllKeys(optional IDBGetAllOptions options);
//
[NewObject, RaisesException]
IDBRequest getAll(optional any query_or_options = null,
optional [EnforceRange] unsigned long count);
[NewObject, RaisesException]
IDBRequest getAllKeys(optional any query_or_options = null,
optional [EnforceRange] unsigned long count);
}

[Exposed=(Window,Worker)]
Expand All @@ -237,6 +283,19 @@ partial interface IDBIndex {
// `[[primaryKey1, value1, indexKey1], [primaryKey2, value2, indexKey2], ... ]`
[NewObject, RaisesException]
IDBRequest getAllRecords(optional IDBGetAllRecordsOptions options = {});

// Like `IDBObjectStore` above, IDBIndex overloads `getAll()` and `getAllKeys()`
// to support direction:
//
// IDBRequest getAll(optional IDBGetAllOptions options);
// IDBRequest getAllKeys(optional IDBGetAllOptions options);
//
[NewObject, RaisesException]
IDBRequest getAll(optional any query_or_options = null,
optional [EnforceRange] unsigned long count);
[NewObject, RaisesException]
IDBRequest getAllKeys(optional any query_or_options = null,
optional [EnforceRange] unsigned long count);
}
```

Expand Down