Skip to content

Commit f92c662

Browse files
[IndexedDB] Update getAllRecords explainer to add direction to getAll and getAllKeys (#1040)
* [IndexedDB] Update getAllRecords explainer to add direction to getAll and getAllKeys * Address code review feedback. Update strings to use single quotes. Fix dicionary syntax error. Clarify extra count argument. * Add Evan to acknowledgements
1 parent eb6d169 commit f92c662

File tree

1 file changed

+72
-12
lines changed

1 file changed

+72
-12
lines changed

IndexedDbGetAllEntries/explainer.md

Lines changed: 72 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,23 +7,38 @@
77
## Participate
88

99
- https://github.com/w3c/IndexedDB/issues/206
10+
- https://github.com/w3c/IndexedDB/issues/130
1011

1112
## Introduction
1213

1314
[`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.
1415

1516
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.
1617

18+
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.
19+
1720
## Goals
1821

1922
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.
2023

2124
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.
2225

26+
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.
27+
2328
## `IDBObject::getAllRecords()` and `IDBIndex::getAllRecords()`
2429

2530
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.
2631

32+
## Adding direction to `getAll()` and `getAllKeys()`
33+
34+
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`. If developers provide both the `IDBGetAllOptions` argument and the `count` argument, the extra `count` argument is ignored like any other extra argument. The `count` property in `IDBGetAllOptions` is used instead.
35+
36+
## Compatibility risk
37+
38+
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.
39+
40+
Since using a dictionary with `getAll()` and `getAllKeys()` is a programming error, we believe compat risk is low.
41+
2742
## Key scenarios
2843

2944
### Read multiple database records through a single request
@@ -43,7 +58,7 @@ async function get_all_records_with_promise(
4358
// Create a read-only transaction.
4459
const read_transaction = database.transaction(
4560
object_store_name,
46-
"readonly"
61+
'readonly'
4762
);
4863

4964
// Get the object store or index to query.
@@ -78,7 +93,7 @@ const records = await get_all_records_with_promise(
7893
/*query_options=*/ { count: 5 }
7994
);
8095
console.log(
81-
"The second record in the database contains: " +
96+
'The second record in the database contains: ' +
8297
`primaryKey: ${records[1].primaryKey}, key: ${records[1].key}, value: ${records[1].value}`
8398
);
8499
```
@@ -150,7 +165,7 @@ async function* idb_batch_record_iterator(
150165

151166
// Store the lower or upper bound for the next iteration.
152167
const last_record = records[records.length - 1];
153-
if (direction === "next" || direction === "nextunique") {
168+
if (direction === 'next' || direction === 'nextunique') {
154169
query = IDBKeyRange.lowerBound(last_record.key, /*exclusive=*/ true);
155170
} else { // direction === 'prev' || direction === 'prevunique'
156171
query = IDBKeyRange.upperBound(last_record.key, /*exclusive=*/ true);
@@ -162,17 +177,17 @@ async function* idb_batch_record_iterator(
162177
// Create a reverse iterator that reads 5 records from an index at a time.
163178
const reverse_iterator = idb_batch_record_iterator(
164179
database,
165-
"my_object_store",
166-
/*direction=*/ "prev",
180+
'my_object_store',
181+
/*direction=*/ 'prev',
167182
/*batch_size=*/ 5,
168-
"my_index"
183+
'my_index'
169184
);
170185

171186
// Get the last 5 records.
172187
let results = await reverse_iterator.next();
173188
let records = results.value;
174189
console.log(
175-
"The first record contains: " +
190+
'The first record contains: ' +
176191
`primaryKey: ${records[0].primaryKey}, key: ${records[0].key}, value: ${records[0].value}`
177192
);
178193

@@ -182,6 +197,27 @@ if (!results.done) {
182197
}
183198
```
184199

200+
### Use direction with `getAllKeys()` after feature detection
201+
202+
`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`.
203+
204+
```js
205+
const read_transaction = database.transaction('my_object_store', 'readonly');
206+
const object_store = read_transaction.objectStore('my_object_store');
207+
208+
// Use feature detection to determine if this browser supports `getAllRecords()`.
209+
if ('getAllRecords' in object_store) {
210+
// Request the last 5 primary keys in `object_store`.
211+
const get_all_options = {
212+
direction: 'prev',
213+
count: 5
214+
};
215+
const request = object_store.getAllKeys(get_all_options);
216+
} else {
217+
// Fallback to a cursor with direction: 'prev' for this query.
218+
}
219+
```
220+
185221
## Considered alternatives
186222

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

195231
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.
196232

197-
### Adding direction to `getAll()` and `getAllKeys()`
198-
199-
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.
200-
201233
## WebIDL
202234

203235
```js
204-
dictionary IDBGetAllRecordsOptions {
236+
dictionary IDBGetAllOptions {
205237
// A key or an `IDBKeyRange` identifying the records to retrieve.
206238
any query = null;
207239

@@ -228,6 +260,20 @@ partial interface IDBObjectStore {
228260
// `[[primaryKey1, value1], [primaryKey2, value2], ... ]`
229261
[NewObject, RaisesException]
230262
IDBRequest getAllRecords(optional IDBGetAllRecordsOptions options = {});
263+
264+
// For `getAll()` and `getAllKeys()`, add support for the direction option
265+
// through a new overload, which accepts a `IDBGetAllOptions` dictionary as
266+
// the first and only argument.
267+
//
268+
// IDBRequest getAll(optional IDBGetAllOptions options);
269+
// IDBRequest getAllKeys(optional IDBGetAllOptions options);
270+
//
271+
[NewObject, RaisesException]
272+
IDBRequest getAll(optional any query_or_options = null,
273+
optional [EnforceRange] unsigned long count);
274+
[NewObject, RaisesException]
275+
IDBRequest getAllKeys(optional any query_or_options = null,
276+
optional [EnforceRange] unsigned long count);
231277
}
232278

233279
[Exposed=(Window,Worker)]
@@ -237,6 +283,19 @@ partial interface IDBIndex {
237283
// `[[primaryKey1, value1, indexKey1], [primaryKey2, value2, indexKey2], ... ]`
238284
[NewObject, RaisesException]
239285
IDBRequest getAllRecords(optional IDBGetAllRecordsOptions options = {});
286+
287+
// Like `IDBObjectStore` above, IDBIndex overloads `getAll()` and `getAllKeys()`
288+
// to support direction:
289+
//
290+
// IDBRequest getAll(optional IDBGetAllOptions options);
291+
// IDBRequest getAllKeys(optional IDBGetAllOptions options);
292+
//
293+
[NewObject, RaisesException]
294+
IDBRequest getAll(optional any query_or_options = null,
295+
optional [EnforceRange] unsigned long count);
296+
[NewObject, RaisesException]
297+
IDBRequest getAllKeys(optional any query_or_options = null,
298+
optional [EnforceRange] unsigned long count);
240299
}
241300
```
242301

@@ -258,3 +317,4 @@ Many thanks for valuable feedback and advice from:
258317

259318
- [Rahul Singh](https://github.com/rahulsingh-msft)
260319
- [Foromo Daniel Soromou](https://github.com/fosoromo_microsoft)
320+
- [Evan Stade](https://github.com/evanstade)

0 commit comments

Comments
 (0)