source: frontend/node_modules/idb/README.md@ 9af201e

Last change on this file since 9af201e was 9af201e, checked in by MBK <marija.karapandzova@…>, 11 days ago

Fix frontend appearance

  • Property mode set to 100644
File size: 16.1 KB
Line 
1# IndexedDB with usability.
2
3This is a tiny (~1.06kB brotli'd) library that mostly mirrors the IndexedDB API, but with small improvements that make a big difference to usability.
4
51. [Installation](#installation)
61. [Changes](#changes)
71. [Browser support](#browser-support)
81. [API](#api)
9 1. [`openDB`](#opendb)
10 1. [`deleteDB`](#deletedb)
11 1. [`unwrap`](#unwrap)
12 1. [`wrap`](#wrap)
13 1. [General enhancements](#general-enhancements)
14 1. [`IDBDatabase` enhancements](#idbdatabase-enhancements)
15 1. [`IDBTransaction` enhancements](#idbtransaction-enhancements)
16 1. [`IDBCursor` enhancements](#idbcursor-enhancements)
17 1. [Async iterators](#async-iterators)
181. [Examples](#examples)
191. [TypeScript](#typescript)
20
21# Installation
22
23## Using npm
24
25```sh
26npm install idb
27```
28
29Then, assuming you're using a module-compatible system (like webpack, Rollup etc):
30
31```js
32import { openDB, deleteDB, wrap, unwrap } from 'idb';
33
34async function doDatabaseStuff() {
35 const db = await openDB(…);
36}
37```
38
39## Directly in a browser
40
41### Using the modules method directly via jsdelivr:
42
43```html
44<script type="module">
45 import { openDB, deleteDB, wrap, unwrap } from 'https://cdn.jsdelivr.net/npm/idb@7/+esm';
46
47 async function doDatabaseStuff() {
48 const db = await openDB(…);
49 }
50</script>
51```
52
53### Using external script reference
54
55```html
56<script src="https://cdn.jsdelivr.net/npm/idb@7/build/umd.js"></script>
57<script>
58 async function doDatabaseStuff() {
59 const db = await idb.openDB(…);
60 }
61</script>
62```
63
64A global, `idb`, will be created, containing all exports of the module version.
65
66# Changes
67
68[See details of (potentially) breaking changes](CHANGELOG.md).
69
70# Browser support
71
72This library targets modern browsers, as in Chrome, Firefox, Safari, and other browsers that use those engines, such as Edge. IE is not supported.
73
74If you want to target much older versions of those browsers, you can transpile the library using something like [Babel](https://babeljs.io/). You can't transpile the library for IE, as it relies on a proper implementation of [JavaScript proxies](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy).
75
76# API
77
78## `openDB`
79
80This method opens a database, and returns a promise for an enhanced [`IDBDatabase`](https://w3c.github.io/IndexedDB/#database-interface).
81
82```js
83const db = await openDB(name, version, {
84 upgrade(db, oldVersion, newVersion, transaction, event) {
85 // …
86 },
87 blocked(currentVersion, blockedVersion, event) {
88 // …
89 },
90 blocking(currentVersion, blockedVersion, event) {
91 // …
92 },
93 terminated() {
94 // …
95 },
96});
97```
98
99- `name`: Name of the database.
100- `version` (optional): Schema version, or `undefined` to open the current version.
101- `upgrade` (optional): Called if this version of the database has never been opened before. Use it to specify the schema for the database. This is similar to the [`upgradeneeded` event](https://developer.mozilla.org/en-US/docs/Web/API/IDBOpenDBRequest/upgradeneeded_event) in plain IndexedDB.
102 - `db`: An enhanced `IDBDatabase`.
103 - `oldVersion`: Last version of the database opened by the user.
104 - `newVersion`: Whatever new version you provided.
105 - `transaction`: An enhanced transaction for this upgrade. This is useful if you need to get data from other stores as part of a migration.
106 - `event`: The event object for the associated `upgradeneeded` event.
107- `blocked` (optional): Called if there are older versions of the database open on the origin, so this version cannot open. This is similar to the [`blocked` event](https://developer.mozilla.org/en-US/docs/Web/API/IDBOpenDBRequest/blocked_event) in plain IndexedDB.
108 - `currentVersion`: Version of the database that's blocking this one.
109 - `blockedVersion`: The version of the database being blocked (whatever version you provided to `openDB`).
110 - `event`: The event object for the associated `blocked` event.
111- `blocking` (optional): Called if this connection is blocking a future version of the database from opening. This is similar to the [`versionchange` event](https://developer.mozilla.org/en-US/docs/Web/API/IDBDatabase/versionchange_event) in plain IndexedDB.
112 - `currentVersion`: Version of the open database (whatever version you provided to `openDB`).
113 - `blockedVersion`: The version of the database that's being blocked.
114 - `event`: The event object for the associated `versionchange` event.
115- `terminated` (optional): Called if the browser abnormally terminates the connection, but not on regular closures like calling `db.close()`. This is similar to the [`close` event](https://developer.mozilla.org/en-US/docs/Web/API/IDBDatabase/close_event) in plain IndexedDB.
116
117## `deleteDB`
118
119Deletes a database.
120
121```js
122await deleteDB(name, {
123 blocked() {
124 // …
125 },
126});
127```
128
129- `name`: Name of the database.
130- `blocked` (optional): Called if the database already exists and there are open connections that don’t close in response to a versionchange event, the request will be blocked until they all close.
131 - `currentVersion`: Version of the database that's blocking the delete operation.
132 - `event`: The event object for the associated 'versionchange' event.
133
134## `unwrap`
135
136Takes an enhanced IndexedDB object and returns the plain unmodified one.
137
138```js
139const unwrapped = unwrap(wrapped);
140```
141
142This is useful if, for some reason, you want to drop back into plain IndexedDB. Promises will also be converted back into `IDBRequest` objects.
143
144## `wrap`
145
146Takes an IDB object and returns a version enhanced by this library.
147
148```js
149const wrapped = wrap(unwrapped);
150```
151
152This is useful if some third party code gives you an `IDBDatabase` object and you want it to have the features of this library.
153
154This doesn't work with `IDBCursor`, [due to missing primitives](https://github.com/w3c/IndexedDB/issues/255). Also, if you wrap an `IDBTransaction`, `tx.store` and `tx.objectStoreNames` won't work in Edge. To avoid these issues, wrap the `IDBDatabase` object, and use the wrapped object to create a new transaction.
155
156## General enhancements
157
158Once you've opened the database the API is the same as IndexedDB, except for a few changes to make things easier.
159
160Firstly, any method that usually returns an `IDBRequest` object will now return a promise for the result.
161
162```js
163const store = db.transaction(storeName).objectStore(storeName);
164const value = await store.get(key);
165```
166
167### Promises & throwing
168
169The library turns all `IDBRequest` objects into promises, but it doesn't know in advance which methods may return promises.
170
171As a result, methods such as `store.put` may throw instead of returning a promise.
172
173If you're using async functions, there's no observable difference.
174
175### Transaction lifetime
176
177TL;DR: **Do not `await` other things between the start and end of your transaction**, otherwise the transaction will close before you're done.
178
179An IDB transaction auto-closes if it doesn't have anything left do once microtasks have been processed. As a result, this works fine:
180
181```js
182const tx = db.transaction('keyval', 'readwrite');
183const store = tx.objectStore('keyval');
184const val = (await store.get('counter')) || 0;
185await store.put(val + 1, 'counter');
186await tx.done;
187```
188
189But this doesn't:
190
191```js
192const tx = db.transaction('keyval', 'readwrite');
193const store = tx.objectStore('keyval');
194const val = (await store.get('counter')) || 0;
195// This is where things go wrong:
196const newVal = await fetch('/increment?val=' + val);
197// And this throws an error:
198await store.put(newVal, 'counter');
199await tx.done;
200```
201
202In this case, the transaction closes while the browser is fetching, so `store.put` fails.
203
204## `IDBDatabase` enhancements
205
206### Shortcuts to get/set from an object store
207
208It's common to create a transaction for a single action, so helper methods are included for this:
209
210```js
211// Get a value from a store:
212const value = await db.get(storeName, key);
213// Set a value in a store:
214await db.put(storeName, value, key);
215```
216
217The shortcuts are: `get`, `getKey`, `getAll`, `getAllKeys`, `count`, `put`, `add`, `delete`, and `clear`. Each method takes a `storeName` argument, the name of the object store, and the rest of the arguments are the same as the equivalent `IDBObjectStore` method.
218
219### Shortcuts to get from an index
220
221The shortcuts are: `getFromIndex`, `getKeyFromIndex`, `getAllFromIndex`, `getAllKeysFromIndex`, and `countFromIndex`.
222
223```js
224// Get a value from an index:
225const value = await db.getFromIndex(storeName, indexName, key);
226```
227
228Each method takes `storeName` and `indexName` arguments, followed by the rest of the arguments from the equivalent `IDBIndex` method.
229
230## `IDBTransaction` enhancements
231
232### `tx.store`
233
234If a transaction involves a single store, the `store` property will reference that store.
235
236```js
237const tx = db.transaction('whatever');
238const store = tx.store;
239```
240
241If a transaction involves multiple stores, `tx.store` is undefined, you need to use `tx.objectStore(storeName)` to get the stores.
242
243### `tx.done`
244
245Transactions have a `.done` promise which resolves when the transaction completes successfully, and otherwise rejects with the [transaction error](https://developer.mozilla.org/en-US/docs/Web/API/IDBTransaction/error).
246
247```js
248const tx = db.transaction(storeName, 'readwrite');
249await Promise.all([
250 tx.store.put('bar', 'foo'),
251 tx.store.put('world', 'hello'),
252 tx.done,
253]);
254```
255
256If you're writing to the database, `tx.done` is the signal that everything was successfully committed to the database. However, it's still beneficial to await the individual operations, as you'll see the error that caused the transaction to fail.
257
258## `IDBCursor` enhancements
259
260Cursor advance methods (`advance`, `continue`, `continuePrimaryKey`) return a promise for the cursor, or null if there are no further values to provide.
261
262```js
263let cursor = await db.transaction(storeName).store.openCursor();
264
265while (cursor) {
266 console.log(cursor.key, cursor.value);
267 cursor = await cursor.continue();
268}
269```
270
271## Async iterators
272
273Async iterator support isn't included by default (Edge doesn't support them). To include them, import `idb/with-async-ittr` instead of `idb` (this increases the library size to ~1.29kB brotli'd):
274
275```js
276import { openDB } from 'idb/with-async-ittr';
277```
278
279Or `https://cdn.jsdelivr.net/npm/idb@7/build/umd-with-async-ittr.js` if you're using the non-module version.
280
281Now you can iterate over stores, indexes, and cursors:
282
283```js
284const tx = db.transaction(storeName);
285
286for await (const cursor of tx.store) {
287 // …
288}
289```
290
291Each yielded object is an `IDBCursor`. You can optionally use the advance methods to skip items (within an async iterator they return void):
292
293```js
294const tx = db.transaction(storeName);
295
296for await (const cursor of tx.store) {
297 console.log(cursor.value);
298 // Skip the next item
299 cursor.advance(2);
300}
301```
302
303If you don't manually advance the cursor, `cursor.continue()` is called for you.
304
305Stores and indexes also have an `iterate` method which has the same signature as `openCursor`, but returns an async iterator:
306
307```js
308const index = db.transaction('books').store.index('author');
309
310for await (const cursor of index.iterate('Douglas Adams')) {
311 console.log(cursor.value);
312}
313```
314
315# Examples
316
317## Keyval store
318
319This is very similar to `localStorage`, but async. If this is _all_ you need, you may be interested in [idb-keyval](https://www.npmjs.com/package/idb-keyval). You can always upgrade to this library later.
320
321```js
322import { openDB } from 'idb';
323
324const dbPromise = openDB('keyval-store', 1, {
325 upgrade(db) {
326 db.createObjectStore('keyval');
327 },
328});
329
330export async function get(key) {
331 return (await dbPromise).get('keyval', key);
332}
333export async function set(key, val) {
334 return (await dbPromise).put('keyval', val, key);
335}
336export async function del(key) {
337 return (await dbPromise).delete('keyval', key);
338}
339export async function clear() {
340 return (await dbPromise).clear('keyval');
341}
342export async function keys() {
343 return (await dbPromise).getAllKeys('keyval');
344}
345```
346
347## Article store
348
349```js
350import { openDB } from 'idb/with-async-ittr.js';
351
352async function demo() {
353 const db = await openDB('Articles', 1, {
354 upgrade(db) {
355 // Create a store of objects
356 const store = db.createObjectStore('articles', {
357 // The 'id' property of the object will be the key.
358 keyPath: 'id',
359 // If it isn't explicitly set, create a value by auto incrementing.
360 autoIncrement: true,
361 });
362 // Create an index on the 'date' property of the objects.
363 store.createIndex('date', 'date');
364 },
365 });
366
367 // Add an article:
368 await db.add('articles', {
369 title: 'Article 1',
370 date: new Date('2019-01-01'),
371 body: '…',
372 });
373
374 // Add multiple articles in one transaction:
375 {
376 const tx = db.transaction('articles', 'readwrite');
377 await Promise.all([
378 tx.store.add({
379 title: 'Article 2',
380 date: new Date('2019-01-01'),
381 body: '…',
382 }),
383 tx.store.add({
384 title: 'Article 3',
385 date: new Date('2019-01-02'),
386 body: '…',
387 }),
388 tx.done,
389 ]);
390 }
391
392 // Get all the articles in date order:
393 console.log(await db.getAllFromIndex('articles', 'date'));
394
395 // Add 'And, happy new year!' to all articles on 2019-01-01:
396 {
397 const tx = db.transaction('articles', 'readwrite');
398 const index = tx.store.index('date');
399
400 for await (const cursor of index.iterate(new Date('2019-01-01'))) {
401 const article = { ...cursor.value };
402 article.body += ' And, happy new year!';
403 cursor.update(article);
404 }
405
406 await tx.done;
407 }
408}
409```
410
411# TypeScript
412
413This library is fully typed, and you can improve things by providing types for your database:
414
415```ts
416import { openDB, DBSchema } from 'idb';
417
418interface MyDB extends DBSchema {
419 'favourite-number': {
420 key: string;
421 value: number;
422 };
423 products: {
424 value: {
425 name: string;
426 price: number;
427 productCode: string;
428 };
429 key: string;
430 indexes: { 'by-price': number };
431 };
432}
433
434async function demo() {
435 const db = await openDB<MyDB>('my-db', 1, {
436 upgrade(db) {
437 db.createObjectStore('favourite-number');
438
439 const productStore = db.createObjectStore('products', {
440 keyPath: 'productCode',
441 });
442 productStore.createIndex('by-price', 'price');
443 },
444 });
445
446 // This works
447 await db.put('favourite-number', 7, 'Jen');
448 // This fails at compile time, as the 'favourite-number' store expects a number.
449 await db.put('favourite-number', 'Twelve', 'Jake');
450}
451```
452
453To define types for your database, extend `DBSchema` with an interface where the keys are the names of your object stores.
454
455For each value, provide an object where `value` is the type of values within the store, and `key` is the type of keys within the store.
456
457Optionally, `indexes` can contain a map of index names, to the type of key within that index.
458
459Provide this interface when calling `openDB`, and from then on your database will be strongly typed. This also allows your IDE to autocomplete the names of stores and indexes.
460
461## Opting out of types
462
463If you call `openDB` without providing types, your database will use basic types. However, sometimes you'll need to interact with stores that aren't in your schema, perhaps during upgrades. In that case you can cast.
464
465Let's say we were renaming the 'favourite-number' store to 'fave-nums':
466
467```ts
468import { openDB, DBSchema, IDBPDatabase } from 'idb';
469
470interface MyDBV1 extends DBSchema {
471 'favourite-number': { key: string; value: number };
472}
473
474interface MyDBV2 extends DBSchema {
475 'fave-num': { key: string; value: number };
476}
477
478const db = await openDB<MyDBV2>('my-db', 2, {
479 async upgrade(db, oldVersion) {
480 // Cast a reference of the database to the old schema.
481 const v1Db = db as unknown as IDBPDatabase<MyDBV1>;
482
483 if (oldVersion < 1) {
484 v1Db.createObjectStore('favourite-number');
485 }
486 if (oldVersion < 2) {
487 const store = v1Db.createObjectStore('favourite-number');
488 store.name = 'fave-num';
489 }
490 },
491});
492```
493
494You can also cast to a typeless database by omitting the type, eg `db as IDBPDatabase`.
495
496Note: Types like `IDBPDatabase` are used by TypeScript only. The implementation uses proxies under the hood.
497
498# Developing
499
500```sh
501npm run dev
502```
503
504This will also perform type testing.
505
506To test, navigate to `build/test/` in a browser. You'll need to set up a [basic web server](https://www.npmjs.com/package/serve) for this.
Note: See TracBrowser for help on using the repository browser.