0
# Cursor Navigation
1
2
Enhanced cursor interfaces with promise-based navigation and async iteration support for efficient data traversal.
3
4
## Capabilities
5
6
### Cursor Properties
7
8
Access cursor state and metadata during navigation.
9
10
```typescript { .api }
11
/**
12
* The key of the current index or object store item
13
*/
14
readonly key: IndexName extends IndexNames<DBTypes, StoreName>
15
? IndexKey<DBTypes, StoreName, IndexName>
16
: StoreKey<DBTypes, StoreName>;
17
18
/**
19
* The key of the current object store item (primary key)
20
*/
21
readonly primaryKey: StoreKey<DBTypes, StoreName>;
22
23
/**
24
* Returns the IDBObjectStore or IDBIndex the cursor was opened from
25
*/
26
readonly source: IndexName extends IndexNames<DBTypes, StoreName>
27
? IDBPIndex<DBTypes, TxStores, StoreName, IndexName, Mode>
28
: IDBPObjectStore<DBTypes, TxStores, StoreName, Mode>;
29
```
30
31
**Usage Examples:**
32
33
```typescript
34
const tx = db.transaction("users", "readonly");
35
const userStore = tx.store;
36
const nameIndex = userStore.index("name");
37
38
// Object store cursor
39
let storeCursor = await userStore.openCursor();
40
if (storeCursor) {
41
console.log("Current key:", storeCursor.key); // Primary key
42
console.log("Primary key:", storeCursor.primaryKey); // Same as key for store cursors
43
console.log("Source:", storeCursor.source === userStore); // true
44
}
45
46
// Index cursor
47
let indexCursor = await nameIndex.openCursor();
48
if (indexCursor) {
49
console.log("Current key:", indexCursor.key); // Index key (name value)
50
console.log("Primary key:", indexCursor.primaryKey); // Primary key of the record
51
console.log("Source:", indexCursor.source === nameIndex); // true
52
}
53
```
54
55
### Cursor Navigation Methods
56
57
Navigate to different positions within the cursor's iteration space.
58
59
#### Advance Method
60
61
```typescript { .api }
62
/**
63
* Advances the cursor a given number of records
64
* Resolves to null if no matching records remain
65
* @param count - Number of records to advance
66
* @returns Promise resolving to cursor at new position or null
67
*/
68
advance<T>(this: T, count: number): Promise<T | null>;
69
```
70
71
#### Continue Method
72
73
```typescript { .api }
74
/**
75
* Advance the cursor by one record (unless 'key' is provided)
76
* Resolves to null if no matching records remain
77
* @param key - Advance to the index or object store with a key equal to or greater than this value
78
* @returns Promise resolving to cursor at new position or null
79
*/
80
continue<T>(
81
this: T,
82
key?: IndexName extends IndexNames<DBTypes, StoreName>
83
? IndexKey<DBTypes, StoreName, IndexName>
84
: StoreKey<DBTypes, StoreName>
85
): Promise<T | null>;
86
```
87
88
#### Continue Primary Key Method
89
90
```typescript { .api }
91
/**
92
* Advance the cursor by given keys
93
* The operation is 'and' – both keys must be satisfied
94
* Resolves to null if no matching records remain
95
* @param key - Advance to the index or object store with a key equal to or greater than this value
96
* @param primaryKey - and where the object store has a key equal to or greater than this value
97
* @returns Promise resolving to cursor at new position or null
98
*/
99
continuePrimaryKey<T>(
100
this: T,
101
key: IndexName extends IndexNames<DBTypes, StoreName>
102
? IndexKey<DBTypes, StoreName, IndexName>
103
: StoreKey<DBTypes, StoreName>,
104
primaryKey: StoreKey<DBTypes, StoreName>
105
): Promise<T | null>;
106
```
107
108
**Navigation Examples:**
109
110
```typescript
111
const tx = db.transaction("users", "readonly");
112
const userStore = tx.store;
113
114
// Basic cursor navigation
115
let cursor = await userStore.openCursor();
116
while (cursor) {
117
console.log("User:", cursor.key, cursor.value);
118
cursor = await cursor.continue(); // Move to next record
119
}
120
121
// Skip records with advance
122
cursor = await userStore.openCursor();
123
if (cursor) {
124
console.log("First user:", cursor.value);
125
cursor = await cursor.advance(5); // Skip next 5 records
126
if (cursor) {
127
console.log("Sixth user:", cursor.value);
128
}
129
}
130
131
// Continue to specific key
132
cursor = await userStore.openCursor();
133
if (cursor) {
134
console.log("Starting from:", cursor.key);
135
cursor = await cursor.continue("user100"); // Jump to user100 or next key
136
if (cursor) {
137
console.log("Jumped to:", cursor.key);
138
}
139
}
140
141
// Index cursor with primary key continuation
142
const nameIndex = userStore.index("name");
143
let indexCursor = await nameIndex.openCursor();
144
if (indexCursor) {
145
console.log("First Alice:", indexCursor.key, indexCursor.primaryKey);
146
// Continue to next "Alice" with primary key >= 100
147
indexCursor = await indexCursor.continuePrimaryKey("Alice", 100);
148
if (indexCursor) {
149
console.log("Next Alice >= 100:", indexCursor.primaryKey);
150
}
151
}
152
```
153
154
### Cursor Modification Operations
155
156
Modify or delete records at the current cursor position.
157
158
#### Update Method
159
160
```typescript { .api }
161
/**
162
* Update the current record
163
* Only available in readwrite and versionchange transactions
164
* @param value - New value for the current record
165
* @returns Promise resolving to the key of the updated record
166
*/
167
update: Mode extends 'readonly'
168
? undefined
169
: (
170
value: StoreValue<DBTypes, StoreName>
171
) => Promise<StoreKey<DBTypes, StoreName>>;
172
```
173
174
#### Delete Method
175
176
```typescript { .api }
177
/**
178
* Delete the current record
179
* Only available in readwrite and versionchange transactions
180
* @returns Promise that resolves when deletion is complete
181
*/
182
delete: Mode extends 'readonly' ? undefined : () => Promise<void>;
183
```
184
185
**Modification Examples:**
186
187
```typescript
188
// Update records using cursor
189
const updateTx = db.transaction("users", "readwrite");
190
const updateStore = updateTx.store;
191
192
let updateCursor = await updateStore.openCursor();
193
while (updateCursor) {
194
const user = updateCursor.value;
195
196
// Update inactive users
197
if (user.lastLogin < oneMonthAgo) {
198
await updateCursor.update({
199
...user,
200
status: "inactive"
201
});
202
}
203
204
updateCursor = await updateCursor.continue();
205
}
206
207
await updateTx.done;
208
209
// Delete records using cursor
210
const deleteTx = db.transaction("users", "readwrite");
211
const deleteStore = deleteTx.store;
212
213
let deleteCursor = await deleteStore.openCursor();
214
while (deleteCursor) {
215
const user = deleteCursor.value;
216
217
// Delete old inactive users
218
if (user.status === "inactive" && user.lastLogin < sixMonthsAgo) {
219
await deleteCursor.delete();
220
}
221
222
deleteCursor = await deleteCursor.continue();
223
}
224
225
await deleteTx.done;
226
```
227
228
### Cursor with Value Interface
229
230
Enhanced cursor that includes the record value.
231
232
```typescript { .api }
233
interface IDBPCursorWithValue<...> extends IDBPCursor<...> {
234
/**
235
* The value of the current item
236
*/
237
readonly value: StoreValue<DBTypes, StoreName>;
238
}
239
```
240
241
**Usage Examples:**
242
243
```typescript
244
const tx = db.transaction("users", "readonly");
245
const userStore = tx.store;
246
247
// Cursor with value (most common)
248
let cursorWithValue = await userStore.openCursor();
249
while (cursorWithValue) {
250
console.log("Key:", cursorWithValue.key);
251
console.log("Value:", cursorWithValue.value); // Full record available
252
console.log("Name:", cursorWithValue.value.name);
253
254
cursorWithValue = await cursorWithValue.continue();
255
}
256
257
// Key-only cursor (more efficient when you don't need values)
258
let keyCursor = await userStore.openKeyCursor();
259
while (keyCursor) {
260
console.log("Key:", keyCursor.key);
261
// cursorKey.value; // Not available - undefined
262
263
keyCursor = await keyCursor.continue();
264
}
265
```
266
267
### Async Iterator Interface
268
269
Modern async iteration support for cursors.
270
271
```typescript { .api }
272
/**
273
* Iterate over the cursor using async iteration
274
* @returns Async iterable iterator over cursor values
275
*/
276
[Symbol.asyncIterator](): AsyncIterableIterator<
277
IDBPCursorIteratorValue<DBTypes, TxStores, StoreName, IndexName, Mode>
278
>;
279
```
280
281
**Async Iteration Examples:**
282
283
```typescript
284
const tx = db.transaction("users", "readonly");
285
const userStore = tx.store;
286
287
// Direct async iteration over store
288
for await (const cursor of userStore) {
289
console.log("User:", cursor.key, cursor.value);
290
291
// Control iteration flow
292
if (cursor.value.department === "Engineering") {
293
cursor.continue(); // Skip to next
294
}
295
296
// Early exit
297
if (cursor.value.id > 1000) {
298
break;
299
}
300
}
301
302
// Async iteration over index
303
const nameIndex = userStore.index("name");
304
for await (const cursor of nameIndex.iterate("Alice")) {
305
console.log("Alice variant:", cursor.value.name, cursor.primaryKey);
306
307
// Modify during iteration (in readwrite transaction)
308
// cursor.update({ ...cursor.value, processed: true });
309
}
310
311
// Manual cursor async iteration
312
let cursor = await userStore.openCursor();
313
if (cursor) {
314
for await (const iteratorCursor of cursor) {
315
console.log("Iterator cursor:", iteratorCursor.value);
316
// iteratorCursor.continue(); // Called automatically by iterator
317
}
318
}
319
```
320
321
### Cursor Direction
322
323
Control the order of cursor iteration.
324
325
```typescript { .api }
326
type IDBCursorDirection = "next" | "nextunique" | "prev" | "prevunique";
327
```
328
329
**Direction Examples:**
330
331
```typescript
332
const tx = db.transaction("users", "readonly");
333
const userStore = tx.store;
334
const ageIndex = userStore.index("age");
335
336
// Forward iteration (default)
337
let forwardCursor = await userStore.openCursor(null, "next");
338
while (forwardCursor) {
339
console.log("Forward:", forwardCursor.key);
340
forwardCursor = await forwardCursor.continue();
341
}
342
343
// Reverse iteration
344
let reverseCursor = await userStore.openCursor(null, "prev");
345
while (reverseCursor) {
346
console.log("Reverse:", reverseCursor.key);
347
reverseCursor = await reverseCursor.continue();
348
}
349
350
// Unique values only (useful for indexes with duplicates)
351
let uniqueCursor = await ageIndex.openCursor(null, "nextunique");
352
while (uniqueCursor) {
353
console.log("Unique age:", uniqueCursor.key);
354
uniqueCursor = await uniqueCursor.continue();
355
}
356
357
// Reverse unique
358
let reverseUniqueCursor = await ageIndex.openCursor(null, "prevunique");
359
while (reverseUniqueCursor) {
360
console.log("Reverse unique age:", reverseUniqueCursor.key);
361
reverseUniqueCursor = await reverseUniqueCursor.continue();
362
}
363
```
364
365
### Cursor Performance Patterns
366
367
Efficient patterns for cursor-based operations.
368
369
**Batch Processing:**
370
371
```typescript
372
async function batchUpdateUsers(batchSize = 100) {
373
let processed = 0;
374
const tx = db.transaction("users", "readwrite");
375
const userStore = tx.store;
376
377
let cursor = await userStore.openCursor();
378
while (cursor) {
379
// Process record
380
await cursor.update({
381
...cursor.value,
382
lastProcessed: new Date()
383
});
384
385
processed++;
386
387
// Commit batch and start new transaction
388
if (processed % batchSize === 0) {
389
await tx.done;
390
391
// Start new transaction for next batch
392
const newTx = db.transaction("users", "readwrite");
393
cursor = await newTx.objectStore("users").openCursor(
394
IDBKeyRange.lowerBound(cursor.key, true) // Continue from current position
395
);
396
} else {
397
cursor = await cursor.continue();
398
}
399
}
400
401
await tx.done; // Commit final batch
402
}
403
```
404
405
**Memory-Efficient Filtering:**
406
407
```typescript
408
async function findActiveUsersEfficiently(condition: (user: User) => boolean) {
409
const results = [];
410
const tx = db.transaction("users", "readonly");
411
412
// Use cursor to avoid loading all records into memory
413
for await (const cursor of tx.store) {
414
if (condition(cursor.value)) {
415
results.push(cursor.value);
416
}
417
418
// Optional: limit memory usage
419
if (results.length >= 1000) {
420
break;
421
}
422
}
423
424
await tx.done;
425
return results;
426
}
427
```
428
429
**Skip Pattern with Advance:**
430
431
```typescript
432
async function paginateWithCursor(pageSize: number, pageNumber: number) {
433
const tx = db.transaction("users", "readonly");
434
const skipCount = pageSize * pageNumber;
435
436
let cursor = await tx.store.openCursor();
437
438
// Skip to page start
439
if (cursor && skipCount > 0) {
440
cursor = await cursor.advance(skipCount);
441
}
442
443
// Collect page results
444
const results = [];
445
let collected = 0;
446
447
while (cursor && collected < pageSize) {
448
results.push(cursor.value);
449
collected++;
450
cursor = await cursor.continue();
451
}
452
453
await tx.done;
454
return {
455
data: results,
456
hasMore: cursor !== null
457
};
458
}
459
```
460
461
**Complex Navigation:**
462
463
```typescript
464
async function findUsersBetweenIds(startId: number, endId: number, excludeId: number) {
465
const tx = db.transaction("users", "readonly");
466
const results = [];
467
468
let cursor = await tx.store.openCursor(IDBKeyRange.bound(startId, endId));
469
470
while (cursor) {
471
if (cursor.key !== excludeId) {
472
results.push(cursor.value);
473
}
474
475
// Skip the excluded ID efficiently
476
if (cursor.key < excludeId) {
477
cursor = await cursor.continue(excludeId + 1);
478
} else {
479
cursor = await cursor.continue();
480
}
481
}
482
483
await tx.done;
484
return results;
485
}
486
```