or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

cursor-navigation.mddatabase-operations.mdenhanced-database.mdindex-operations.mdindex.mdobject-store-operations.mdpromise-wrapping.mdtransaction-management.md
tile.json

cursor-navigation.mddocs/

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

```