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

index-operations.mddocs/

0

# Index Operations

1

2

Enhanced index interface for querying data by secondary keys with full async iteration support.

3

4

## Capabilities

5

6

### Index Properties

7

8

Access index metadata and associated object store.

9

10

```typescript { .api }

11

/**

12

* The IDBObjectStore the index belongs to

13

*/

14

readonly objectStore: IDBPObjectStore<DBTypes, TxStores, StoreName, Mode>;

15

```

16

17

**Usage Examples:**

18

19

```typescript

20

const tx = db.transaction("users", "readonly");

21

const userStore = tx.store;

22

const emailIndex = userStore.index("email");

23

24

// Access parent object store

25

console.log("Parent store:", emailIndex.objectStore === userStore); // true

26

27

// Use parent store for operations not available on index

28

const user = await emailIndex.get("alice@example.com");

29

if (user) {

30

// Update user via parent store

31

const updateTx = db.transaction("users", "readwrite");

32

await updateTx.store.put({ ...user, lastLogin: new Date() });

33

await updateTx.done;

34

}

35

```

36

37

### Data Retrieval Operations

38

39

Query records using index keys instead of primary keys.

40

41

#### Get Operations

42

43

```typescript { .api }

44

/**

45

* Retrieves the value of the first record matching the query

46

* Resolves with undefined if no match is found

47

* @param query - Index key or key range to match

48

* @returns Promise resolving to the store value or undefined

49

*/

50

get(

51

query: IndexKey<DBTypes, StoreName, IndexName> | IDBKeyRange

52

): Promise<StoreValue<DBTypes, StoreName> | undefined>;

53

54

/**

55

* Retrieves all values that match the query

56

* @param query - Index key or key range to match (optional)

57

* @param count - Maximum number of values to return (optional)

58

* @returns Promise resolving to array of matching store values

59

*/

60

getAll(

61

query?: IndexKey<DBTypes, StoreName, IndexName> | IDBKeyRange | null,

62

count?: number

63

): Promise<StoreValue<DBTypes, StoreName>[]>;

64

```

65

66

#### Key Operations

67

68

```typescript { .api }

69

/**

70

* Retrieves the key of the first record that matches the query

71

* Returns the primary key of the matching record

72

* Resolves with undefined if no match is found

73

* @param query - Index key or key range to match

74

* @returns Promise resolving to the primary store key or undefined

75

*/

76

getKey(

77

query: IndexKey<DBTypes, StoreName, IndexName> | IDBKeyRange

78

): Promise<StoreKey<DBTypes, StoreName> | undefined>;

79

80

/**

81

* Retrieves the keys of records matching the query

82

* Returns primary keys of matching records

83

* @param query - Index key or key range to match (optional)

84

* @param count - Maximum number of keys to return (optional)

85

* @returns Promise resolving to array of primary store keys

86

*/

87

getAllKeys(

88

query?: IndexKey<DBTypes, StoreName, IndexName> | IDBKeyRange | null,

89

count?: number

90

): Promise<StoreKey<DBTypes, StoreName>[]>;

91

```

92

93

#### Count Operation

94

95

```typescript { .api }

96

/**

97

* Retrieves the number of records matching the given query

98

* @param key - Index key or key range to count (optional)

99

* @returns Promise resolving to the count

100

*/

101

count(

102

key?: IndexKey<DBTypes, StoreName, IndexName> | IDBKeyRange | null

103

): Promise<number>;

104

```

105

106

**Data Retrieval Examples:**

107

108

```typescript

109

interface UserDB {

110

users: {

111

key: number;

112

value: {

113

id: number;

114

name: string;

115

email: string;

116

age: number;

117

department: string;

118

tags: string[];

119

};

120

indexes: {

121

email: string;

122

name: string;

123

age: number;

124

department: string;

125

tags: string; // multiEntry index

126

};

127

};

128

}

129

130

const db = await openDB<UserDB>("company", 1, {

131

upgrade(db) {

132

const userStore = db.createObjectStore("users", { keyPath: "id", autoIncrement: true });

133

userStore.createIndex("email", "email", { unique: true });

134

userStore.createIndex("name", "name");

135

userStore.createIndex("age", "age");

136

userStore.createIndex("department", "department");

137

userStore.createIndex("tags", "tags", { multiEntry: true }); // Each tag creates separate index entry

138

}

139

});

140

141

const tx = db.transaction("users", "readonly");

142

const userStore = tx.store;

143

144

// Query by email (unique index)

145

const emailIndex = userStore.index("email");

146

const userByEmail = await emailIndex.get("alice@company.com");

147

if (userByEmail) {

148

console.log("Found user by email:", userByEmail.name);

149

}

150

151

// Query by age range

152

const ageIndex = userStore.index("age");

153

const youngEmployees = await ageIndex.getAll(IDBKeyRange.upperBound(30));

154

const seniorEmployees = await ageIndex.getAll(IDBKeyRange.lowerBound(50));

155

156

// Query by department

157

const deptIndex = userStore.index("department");

158

const engineeringTeam = await deptIndex.getAll("Engineering");

159

const firstFiveInSales = await deptIndex.getAll("Sales", 5);

160

161

// Query by tags (multiEntry index)

162

const tagIndex = userStore.index("tags");

163

const jsExperts = await tagIndex.getAll("javascript");

164

const fullStackDevs = await tagIndex.getAll("full-stack");

165

166

// Get primary keys only (more efficient)

167

const engineeringIds = await deptIndex.getAllKeys("Engineering");

168

const seniorIds = await ageIndex.getAllKeys(IDBKeyRange.lowerBound(50));

169

170

// Count operations

171

const totalUsers = await userStore.count();

172

const engineeringCount = await deptIndex.count("Engineering");

173

const adultCount = await ageIndex.count(IDBKeyRange.lowerBound(18));

174

175

await tx.done;

176

```

177

178

### Cursor Operations

179

180

Open cursors for efficient iteration over index results.

181

182

```typescript { .api }

183

/**

184

* Opens a cursor over the records matching the query

185

* Resolves with null if no matches are found

186

* @param query - Index key or key range to match (optional)

187

* @param direction - Cursor direction (next, nextunique, prev, prevunique)

188

* @returns Promise resolving to cursor with value or null

189

*/

190

openCursor(

191

query?: IndexKey<DBTypes, StoreName, IndexName> | IDBKeyRange | null,

192

direction?: IDBCursorDirection

193

): Promise<IDBPCursorWithValue<DBTypes, TxStores, StoreName, IndexName, Mode> | null>;

194

195

/**

196

* Opens a cursor over the keys matching the query

197

* Resolves with null if no matches are found

198

* @param query - Index key or key range to match (optional)

199

* @param direction - Cursor direction (next, nextunique, prev, prevunique)

200

* @returns Promise resolving to cursor or null

201

*/

202

openKeyCursor(

203

query?: IndexKey<DBTypes, StoreName, IndexName> | IDBKeyRange | null,

204

direction?: IDBCursorDirection

205

): Promise<IDBPCursor<DBTypes, TxStores, StoreName, IndexName, Mode> | null>;

206

```

207

208

**Cursor Usage Examples:**

209

210

```typescript

211

const tx = db.transaction("users", "readonly");

212

const userStore = tx.store;

213

const ageIndex = userStore.index("age");

214

215

// Manual cursor iteration

216

let cursor = await ageIndex.openCursor(IDBKeyRange.bound(25, 35));

217

while (cursor) {

218

console.log(`User ${cursor.value.name} is ${cursor.key} years old`);

219

cursor = await cursor.continue();

220

}

221

222

// Cursor with unique values only

223

let uniqueCursor = await ageIndex.openCursor(null, "nextunique");

224

while (uniqueCursor) {

225

console.log("Unique age:", cursor.key);

226

uniqueCursor = await uniqueCursor.continue();

227

}

228

229

// Key cursor (more efficient when you don't need full records)

230

let keyCursor = await ageIndex.openKeyCursor();

231

while (keyCursor) {

232

console.log("Age:", keyCursor.key, "Primary key:", keyCursor.primaryKey);

233

keyCursor = await keyCursor.continue();

234

}

235

```

236

237

### Async Iteration

238

239

Use modern async iteration syntax for convenient index traversal.

240

241

```typescript { .api }

242

/**

243

* Iterate over the index using async iteration

244

* @returns Async iterable iterator over cursor values

245

*/

246

[Symbol.asyncIterator](): AsyncIterableIterator<

247

IDBPCursorWithValueIteratorValue<DBTypes, TxStores, StoreName, IndexName, Mode>

248

>;

249

250

/**

251

* Iterate over the records matching the query

252

* @param query - Index key or key range to match (optional)

253

* @param direction - Cursor direction (optional)

254

* @returns Async iterable iterator over cursor values

255

*/

256

iterate(

257

query?: IndexKey<DBTypes, StoreName, IndexName> | IDBKeyRange | null,

258

direction?: IDBCursorDirection

259

): AsyncIterableIterator<

260

IDBPCursorWithValueIteratorValue<DBTypes, TxStores, StoreName, IndexName, Mode>

261

>;

262

```

263

264

**Async Iteration Examples:**

265

266

```typescript

267

const tx = db.transaction("users", "readonly");

268

const userStore = tx.store;

269

const deptIndex = userStore.index("department");

270

const ageIndex = userStore.index("age");

271

272

// Iterate over all records in department

273

for await (const cursor of deptIndex.iterate("Engineering")) {

274

console.log("Engineer:", cursor.value.name, "Age:", cursor.value.age);

275

276

// Skip junior engineers

277

if (cursor.value.age < 25) {

278

cursor.continue();

279

}

280

}

281

282

// Iterate over age range with early termination

283

let found = false;

284

for await (const cursor of ageIndex.iterate(IDBKeyRange.bound(30, 40))) {

285

if (cursor.value.name === "Target Employee") {

286

console.log("Found target employee at age", cursor.key);

287

found = true;

288

break;

289

}

290

}

291

292

// Collect filtered results

293

const seniorEngineers = [];

294

for await (const cursor of deptIndex.iterate("Engineering")) {

295

if (cursor.value.age >= 40) {

296

seniorEngineers.push(cursor.value);

297

}

298

}

299

300

// Iterate in reverse order

301

const newestEmployees = [];

302

for await (const cursor of ageIndex.iterate(null, "prev")) {

303

newestEmployees.push(cursor.value);

304

if (newestEmployees.length >= 10) break; // Get 10 youngest

305

}

306

307

await tx.done;

308

```

309

310

### Index Query Patterns

311

312

Common patterns for querying data using indexes.

313

314

**Exact Match Queries:**

315

316

```typescript

317

const tx = db.transaction("users", "readonly");

318

const emailIndex = tx.store.index("email");

319

const nameIndex = tx.store.index("name");

320

321

// Single exact match

322

const user = await emailIndex.get("alice@company.com");

323

324

// Multiple exact matches

325

const alices = await nameIndex.getAll("Alice");

326

```

327

328

**Range Queries:**

329

330

```typescript

331

const ageIndex = tx.store.index("age");

332

333

// Age ranges

334

const youngAdults = await ageIndex.getAll(IDBKeyRange.bound(18, 25));

335

const seniors = await ageIndex.getAll(IDBKeyRange.lowerBound(65));

336

const minors = await ageIndex.getAll(IDBKeyRange.upperBound(17));

337

338

// Exclude boundaries

339

const middleAged = await ageIndex.getAll(IDBKeyRange.bound(30, 50, true, true));

340

```

341

342

**Pattern Matching:**

343

344

```typescript

345

const nameIndex = tx.store.index("name");

346

347

// Names starting with "A"

348

const aNames = await nameIndex.getAll(IDBKeyRange.bound("A", "B", false, true));

349

350

// Names starting with "Alice"

351

const aliceVariations = await nameIndex.getAll(IDBKeyRange.bound("Alice", "Alicf", false, true));

352

```

353

354

**Multi-Entry Index Queries:**

355

356

```typescript

357

const tagIndex = tx.store.index("tags"); // multiEntry: true

358

359

// Users with specific skill

360

const jsDevs = await tagIndex.getAll("javascript");

361

const reactDevs = await tagIndex.getAll("react");

362

363

// Count users with skill

364

const jsDevCount = await tagIndex.count("javascript");

365

```

366

367

**Compound Queries (using multiple indexes):**

368

369

```typescript

370

// Find young engineers

371

const deptIndex = tx.store.index("department");

372

const ageIndex = tx.store.index("age");

373

374

// Get all engineers

375

const allEngineers = await deptIndex.getAll("Engineering");

376

377

// Filter by age in memory (for complex conditions)

378

const youngEngineers = allEngineers.filter(user => user.age < 30);

379

380

// Alternative: Use cursor for memory efficiency

381

const youngEngineersAlt = [];

382

for await (const cursor of deptIndex.iterate("Engineering")) {

383

if (cursor.value.age < 30) {

384

youngEngineersAlt.push(cursor.value);

385

}

386

}

387

```

388

389

### Performance Optimization

390

391

Strategies for efficient index operations.

392

393

**Choose Appropriate Index:**

394

395

```typescript

396

// Use most selective index first

397

const emailIndex = tx.store.index("email"); // Most selective (unique)

398

const deptIndex = tx.store.index("department"); // Less selective

399

const ageIndex = tx.store.index("age"); // Least selective

400

401

// Best: Start with most selective

402

const user = await emailIndex.get("specific@email.com");

403

404

// Good: Use department for departmental queries

405

const team = await deptIndex.getAll("Engineering");

406

407

// Acceptable: Use age for age-based queries

408

const adults = await ageIndex.getAll(IDBKeyRange.lowerBound(18));

409

```

410

411

**Key-Only Queries:**

412

413

```typescript

414

// More efficient when you only need to know if records exist

415

const hasEngineers = (await deptIndex.count("Engineering")) > 0;

416

417

// Get primary keys only

418

const engineerIds = await deptIndex.getAllKeys("Engineering");

419

420

// Instead of getting full records

421

const engineers = await deptIndex.getAll("Engineering");

422

const ids = engineers.map(e => e.id); // Less efficient

423

```

424

425

**Limit Result Sets:**

426

427

```typescript

428

// Use count parameter to limit results

429

const firstTenEngineers = await deptIndex.getAll("Engineering", 10);

430

431

// Use cursors for large datasets

432

let processed = 0;

433

for await (const cursor of deptIndex.iterate("Engineering")) {

434

processUser(cursor.value);

435

processed++;

436

437

if (processed >= 1000) break; // Process in batches

438

}

439

```