IndexedDB के साथ काम करना

IndexedDB की बुनियादी बातों के लिए एक गाइड.

इस गाइड में IndexedDB API की बुनियादी बातों के बारे में बताया गया है. हम जेक आर्किबाल्ड की IndexedDB Promised लाइब्रेरी का इस्तेमाल कर रहे हैं, जो IndexedDB API से काफ़ी मिलती-जुलती है. हालांकि, इसमें प्रॉमिस का इस्तेमाल किया जाता है. इसे ज़्यादा सटीक सिंटैक्स के लिए await किया जा सकता है. यह एपीआई के स्ट्रक्चर को बनाए रखते हुए, उसे आसान बनाता है.

IndexedDB क्या है?

IndexedDB एक बड़े पैमाने का, NoSQL स्टोरेज सिस्टम है. यह उपयोगकर्ता के ब्राउज़र की किसी भी चीज़ को स्टोर करने की अनुमति देता है. सामान्य खोज, पाएं, और पुट ऐक्शन के अलावा, IndexedDB लेन-देन के साथ भी काम करता है. यहां एमडीएन पर IndexedDB की परिभाषा दी गई है:

IndexedDB, क्लाइंट-साइड स्टोरेज के लिए एक कम लेवल वाला एपीआई है. इसमें, फ़ाइलें/ब्लॉब के साथ-साथ, बहुत सारा स्ट्रक्चर्ड डेटा भी शामिल होता है. यह एपीआई, इंडेक्स का इस्तेमाल करता है, ताकि इस डेटा की बेहतर परफ़ॉर्मेंस के लिए खोज की जा सके. DOM स्टोरेज की मदद से कम डेटा सेव किया जा सकता है. हालांकि, ज़्यादा स्ट्रक्चर्ड डेटा को स्टोर करने का यह तरीका कम कारगर है. IndexedDB एक समाधान देता है.

हर IndexedDB डेटाबेस एक ऑरिजिन के लिए यूनीक होता है. आम तौर पर, यह साइट डोमेन या सबडोमेन होता है. इसका मतलब है कि न तो इसे किसी दूसरे ऑरिजिन से ऐक्सेस किया जा सकता है और न ही इसे ऐक्सेस किया जा सकता है. डेटा स्टोरेज की सीमाएं आम तौर पर काफ़ी बड़ी होती हैं, अगर वे मौजूद हों, लेकिन अलग-अलग ब्राउज़र, सीमाओं और डेटा को हटाने का काम अलग-अलग तरीके से करते हैं. ज़्यादा जानकारी के लिए, आगे पढ़ना सेक्शन देखें.

IndexedDB के शब्द

डेटाबेस
IndexedDB का सबसे ऊंचा लेवल. इसमें ऑब्जेक्ट स्टोर होता है, जिसमें वह डेटा होता है जिसे आपको बनाए रखना है. आपके पास, अपनी पसंद के किसी भी नाम के साथ कई डेटाबेस बनाने का विकल्प होता है.
ऑब्जेक्ट स्टोर
डेटा सेव करने के लिए अलग बकेट. आप ऑब्जेक्ट स्टोर को पारंपरिक रिलेशनल डेटाबेस की टेबल की तरह मान सकते हैं. आम तौर पर, स्टोर किए जाने वाले हर टाइप (JavaScript डेटा टाइप नहीं) के लिए एक ऑब्जेक्ट स्टोर होता है. उदाहरण के लिए, अगर कोई ऐसा ऐप्लिकेशन है जो ब्लॉग पोस्ट और उपयोगकर्ताओं की प्रोफ़ाइल में बना रहता है, तो आपके पास दो ऑब्जेक्ट स्टोर होने का विकल्प है. पारंपरिक डेटाबेस की टेबल के उलट, स्टोर में मौजूद JavaScript के डेटा के असल टाइप का डेटा एक जैसा होना ज़रूरी नहीं है. उदाहरण के लिए, अगर people ऑब्जेक्ट स्टोर में तीन लोग हैं, तो उनकी उम्र की प्रॉपर्टी 53, 'twenty-five', और unknown हो सकती हैं.
इंडेक्स
यह एक तरह का ऑब्जेक्ट स्टोर है, जो डेटा की अलग-अलग प्रॉपर्टी के ज़रिए किसी दूसरे ऑब्जेक्ट स्टोर (जिसे रेफ़रंस ऑब्जेक्ट स्टोर कहा जाता है) में डेटा को व्यवस्थित करता है. इंडेक्स का इस्तेमाल, इस प्रॉपर्टी के ज़रिए ऑब्जेक्ट स्टोर में रिकॉर्ड पाने के लिए किया जाता है. उदाहरण के लिए, अगर लोगों को स्टोर किया जा रहा है, तो हो सकता है कि आप उन्हें बाद में उनके नाम, उम्र या पसंदीदा जानवर के हिसाब से फ़ेच करना चाहें.
कार्रवाई
डेटाबेस के साथ इंटरैक्शन.
लेन-देन
किसी कार्रवाई या ऑपरेशन के ग्रुप के आस-पास एक रैपर, जो डेटाबेस को पूरी सुरक्षा देने की सुविधा देता है. अगर किसी ट्रांज़ैक्शन में कोई भी कार्रवाई पूरी नहीं होती, तो कोई भी कार्रवाई पूरी नहीं हो पाती. इसके बाद, डेटाबेस उसी स्थिति में आ जाता है जैसा वह लेन-देन शुरू होने से पहले था. IndexedDB में सभी पढ़ने या लिखने की कार्रवाइयां किसी लेन-देन का हिस्सा होनी चाहिए. यह एक ही समय पर डेटाबेस पर काम कर रहे दूसरे थ्रेड के बारे में चिंता किए बिना, ऐटॉमिक रीड-बदलाव-राइट ऑपरेशन की अनुमति देता है.
कर्सर
किसी डेटाबेस में एक से ज़्यादा रिकॉर्ड बनाने का तरीका.

IndexedDB के बारे में पता कैसे लगाएं

IndexedDB करीब-करीब दुनिया भर में काम करता है. हालांकि, अगर पुराने ब्राउज़र का इस्तेमाल किया जा रहा है, तो ज़रूरत पड़ने पर, सुविधा की पहचान करना सही रहेगा. सबसे आसान तरीका यह है कि window ऑब्जेक्ट की जांच की जाए:

function indexedDBStuff () {
  // Check for IndexedDB support:
  if (!('indexedDB' in window)) {
    // Can't use IndexedDB
    console.log("This browser doesn't support IndexedDB");
    return;
  } else {
    // Do IndexedDB stuff here:
    // ...
  }
}

// Run IndexedDB code:
indexedDBStuff();

डेटाबेस खोलने का तरीका

IndexedDB से, आपके पास चुने गए किसी भी नाम के साथ कई डेटाबेस बनाने का विकल्प होता है. अगर डेटाबेस को खोलने की कोशिश करते समय वह मौजूद नहीं है, तो वह अपने-आप बन जाएगा. डेटाबेस खोलने के लिए, idb लाइब्रेरी का इस्तेमाल करके, openDB() तरीके का इस्तेमाल किया जा सकता है:

import {openDB} from 'idb';

async function useDB () {
  // Returns a promise, which makes `idb` usable with async/await.
  const dbPromise = await openDB('example-database', version, events);
}

useDB();

इस तरीके से एक प्रॉमिस मिलता है जो डेटाबेस ऑब्जेक्ट का समाधान करता है. openDB() का इस्तेमाल करते समय, डेटाबेस सेट अप करने के लिए नाम, वर्शन नंबर, और इवेंट ऑब्जेक्ट देने वाला तरीका.

यहां कॉन्टेक्स्ट के हिसाब से, openDB() तरीके का एक उदाहरण दिया गया है:

import {openDB} from 'idb';

async function useDB () {
  // Opens the first version of the 'test-db1' database.
  // If the database does not exist, it will be created.
  const dbPromise = await openDB('test-db1', 1);
}

useDB();

IndexedDB सहायता की जांच करने के लिए, आपको उस फ़ंक्शन के ऊपरी हिस्से पर होना चाहिए. अगर ब्राउज़र IndexedDB के साथ काम नहीं करता है, तो यह फ़ंक्शन से बाहर निकल जाता है. इसके बाद, 'test-db1' नाम का डेटाबेस खोलने के लिए, openDB() तरीके को कॉल किया जाता है. इस उदाहरण में, वैकल्पिक इवेंट ऑब्जेक्ट को छोड़ दिया गया है, ताकि चीज़ों को आसान बनाया जा सके—लेकिन IndexedDB के साथ कोई भी सही काम करने के लिए, आपको आखिर में इसे तय करना होगा.

ऑब्जेक्ट स्टोर के साथ काम करने का तरीका

IndexedDB डेटाबेस में एक या उससे ज़्यादा ऑब्जेक्ट स्टोर होते हैं. ऑब्जेक्ट स्टोर का कॉन्सेप्ट, SQL डेटाबेस में मौजूद टेबल जैसी ही होता है. SQL टेबल की तरह, किसी ऑब्जेक्ट स्टोर में पंक्तियां और कॉलम होते हैं, लेकिन IndexedDB में, कॉलम की संख्या में कम विकल्प होता है. ऐसा तब होता है, जब IndexedDB ऑब्जेक्ट स्टोर में किसी कुंजी के लिए एक कॉलम होता है और उस कुंजी से जुड़े डेटा के लिए दूसरा कॉलम होता है.

ऑब्जेक्ट स्टोर बनाएं

उदाहरण के लिए, एक ऐसी साइट की कल्पना करें जो उपयोगकर्ता की प्रोफ़ाइल और नोट को लंबे समय तक बनाए रखती है. ऐसा होने पर, एक people ऑब्जेक्ट स्टोर और person ऑब्जेक्ट वाले एक notes ऑब्जेक्ट स्टोर की कल्पना की जा सकती है. अच्छी तरह से स्ट्रक्चर किए गए IndexedDB डेटाबेस में हर तरह के डेटा के लिए एक ऑब्जेक्ट स्टोर होना चाहिए, जिसे बनाए रखने की ज़रूरत होती है.

डेटाबेस को पूरी सुरक्षा देने के लिए, ऑब्जेक्ट स्टोर सिर्फ़ openDB() कॉल में इवेंट ऑब्जेक्ट में बनाए और हटाए जा सकते हैं. इवेंट ऑब्जेक्ट, upgrade() वाला तरीका दिखाता है. इससे ऑब्जेक्ट स्टोर बनाने का तरीका पता चलता है. ऑब्जेक्ट स्टोर बनाने के लिए, upgrade() तरीके में createObjectStore() तरीके को कॉल करें:

import {openDB} from 'idb';

async function createStoreInDB () {
  const dbPromise = await openDB('example-database', 1, {
    upgrade (db) {
      // Creates an object store:
      db.createObjectStore('storeName', options);
    }
  });
}

createStoreInDB();

यह तरीका ऑब्जेक्ट स्टोर के नाम के साथ-साथ एक वैकल्पिक कॉन्फ़िगरेशन ऑब्जेक्ट का नाम भी लेता है, जिसकी मदद से ऑब्जेक्ट स्टोर के लिए अलग-अलग प्रॉपर्टी तय की जा सकती हैं.

यहां एक उदाहरण दिया गया है कि createObjectStore() तरीके का इस्तेमाल कैसे किया जाता है:

import {openDB} from 'idb';

async function createStoreInDB () {
  const dbPromise = await openDB('test-db1', 1, {
    upgrade (db) {
      console.log('Creating a new object store...');

      // Checks if the object store exists:
      if (!db.objectStoreNames.contains('people')) {
        // If the object store does not exist, create it:
        db.createObjectStore('people');
      }
    }
  });
}

createStoreInDB();

इस उदाहरण में, ऑब्जेक्ट स्टोर बनाने के लिए इवेंट ऑब्जेक्ट को openDB() तरीके को पास किया जाता है. पहले की तरह ही, ऑब्जेक्ट स्टोर बनाने का काम, इवेंट ऑब्जेक्ट के upgrade() तरीके में ही किया जाता है. हालांकि, अगर पहले से मौजूद कोई ऑब्जेक्ट स्टोर बनाने की कोशिश की जाती है, तो ब्राउज़र गड़बड़ी दिखाएगा. इसलिए, createObjectStore() तरीके को ऐसे if स्टेटमेंट में रैप करें जो यह देखता है कि ऑब्जेक्ट स्टोर मौजूद है या नहीं. if ब्लॉक के अंदर, 'firstOS' नाम का ऑब्जेक्ट स्टोर बनाने के लिए, आपको createObjectStore() को कॉल करना होता है.

मुख्य कुंजियों के बारे में बताने का तरीका

ऑब्जेक्ट स्टोर को सेट अप करते समय, यह तय किया जा सकता है कि स्टोर में प्राइमरी कुंजी का इस्तेमाल करके, डेटा की अलग तरह से पहचान कैसे की जाएगी. आप कुंजी का पाथ तय करके या कुंजी जनरेटर का इस्तेमाल करके, प्राथमिक कुंजी तय कर सकते हैं.

मुख्य पाथ एक ऐसी प्रॉपर्टी है जो हमेशा मौजूद होती है और जिसमें एक यूनीक वैल्यू होती है. उदाहरण के लिए, people ऑब्जेक्ट स्टोर के मामले में, ईमेल पते को मुख्य पाथ के तौर पर चुना जा सकता है:

import {openDB} from 'idb';

async function createStoreInDB () {
  const dbPromise = await openDB('test-db2', 1, {
    upgrade (db) {
      if (!db.objectStoreNames.contains('people')) {
        db.createObjectStore('people', { keyPath: 'email' });
      }
    }
  });
}

createStoreInDB();

इस उदाहरण में, 'people' नाम का एक ऑब्जेक्ट स्टोर बनाया जाता है. साथ ही, keyPath विकल्प में email प्रॉपर्टी को प्राइमरी कुंजी के तौर पर असाइन किया जाता है.

कुंजी जनरेटर का भी इस्तेमाल किया जा सकता है, जैसे कि autoIncrement. कुंजी जनरेटर, ऑब्जेक्ट स्टोर में जोड़े गए हर ऑब्जेक्ट के लिए एक यूनीक वैल्यू बनाता है. डिफ़ॉल्ट रूप से, अगर कोई कुंजी तय नहीं की जाती है, तो IndexedDB एक कुंजी बनाता है और उसे डेटा से अलग से स्टोर करता है.

import {openDB} from 'idb';

async function createStoreInDB () {
  const dbPromise = await openDB('test-db2', 1, {
    upgrade (db) {
      if (!db.objectStoreNames.contains('notes')) {
        db.createObjectStore('notes', { autoIncrement: true });
      }
    }
  });
}

createStoreInDB();

यह उदाहरण 'notes' नाम का एक ऑब्जेक्ट स्टोर बनाता है. साथ ही, प्राइमरी कुंजी को अपने-आप बढ़ने वाली संख्या के तौर पर अपने-आप असाइन होने के लिए सेट करता है.

import {openDB} from 'idb';

async function createStoreInDB () {
  const dbPromise = await openDB('test-db2', 1, {
    upgrade (db) {
      if (!db.objectStoreNames.contains('logs')) {
        db.createObjectStore('logs', { keyPath: 'id', autoIncrement: true });
      }
    }
  });
}

createStoreInDB();

यह उदाहरण पिछले उदाहरण से मिलता-जुलता है. हालांकि, इस बार अपने-आप बढ़ने वाली वैल्यू को, साफ़ तौर पर 'id' नाम वाली प्रॉपर्टी के लिए असाइन किया गया है.

कुंजी तय करने का तरीका चुनना आपके डेटा पर निर्भर करता है. अगर आपके डेटा में कोई ऐसी प्रॉपर्टी है जो हमेशा यूनीक होती है, तो उसे इस यूनीक प्रॉपर्टी को लागू करने के लिए keyPath बनाया जा सकता है. ऐसा न होने पर, अपने-आप बढ़ने वाली वैल्यू का इस्तेमाल करना सही रहता है.

यह कोड तीन ऑब्जेक्ट स्टोर बनाता है, जो ऑब्जेक्ट स्टोर में मुख्य कुंजियों को तय करने के अलग-अलग तरीकों को दिखाते हैं:

import {openDB} from 'idb';

async function createStoresInDB () {
  const dbPromise = await openDB('test-db2', 1, {
    upgrade (db) {
      if (!db.objectStoreNames.contains('people')) {
        db.createObjectStore('people', { keyPath: 'email' });
      }

      if (!db.objectStoreNames.contains('notes')) {
        db.createObjectStore('notes', { autoIncrement: true });
      }

      if (!db.objectStoreNames.contains('logs')) {
        db.createObjectStore('logs', { keyPath: 'id', autoIncrement: true });
      }
    }
  });
}

createStoresInDB();

इंडेक्स तय करने का तरीका

इंडेक्स एक तरह के ऑब्जेक्ट स्टोर होते हैं, जिनका इस्तेमाल किसी खास प्रॉपर्टी के ज़रिए, रेफ़रंस ऑब्जेक्ट स्टोर से डेटा हासिल करने के लिए किया जाता है. इंडेक्स, रेफ़रंस ऑब्जेक्ट स्टोर के अंदर होता है और उसमें वही डेटा होता है. हालांकि, यह रेफ़रंस स्टोर की प्राइमरी कुंजी के बजाय, बताई गई प्रॉपर्टी को अपने मुख्य पाथ के तौर पर इस्तेमाल करता है. जब आप अपना ऑब्जेक्ट स्टोर बनाते हैं, तब इंडेक्स बनाए जाने चाहिए. साथ ही, इसका इस्तेमाल आपके डेटा पर एक यूनीक कंस्ट्रेंट तय करने के लिए भी किया जा सकता है.

इंडेक्स बनाने के लिए, ऑब्जेक्ट स्टोर इंस्टेंस पर, createIndex() तरीके का इस्तेमाल करें:

import {openDB} from 'idb';

async function createIndexInStore() {
  const dbPromise = await openDB('storeName', 1, {
    upgrade (db) {
      const objectStore = db.createObjectStore('storeName');

      objectStore.createIndex('indexName', 'property', options);
    }
  });
}

createIndexInStore();

यह तरीका, इंडेक्स ऑब्जेक्ट बनाता है और उसे दिखाता है. ऑब्जेक्ट स्टोर के इंस्टेंस पर मौजूद createIndex() तरीका, पहले आर्ग्युमेंट के तौर पर नए इंडेक्स का नाम लेता है. वहीं, दूसरा आर्ग्युमेंट उस प्रॉपर्टी की प्रॉपर्टी के बारे में बताता है जिसे आपको इंडेक्स करना है. आखिरी तर्क की मदद से, दो विकल्प बनाए जा सकते हैं. इनसे इंडेक्स के काम करने का तरीका तय किया जा सकता है: unique और multiEntry. अगर unique को true पर सेट किया जाता है, तो इंडेक्स किसी एक कुंजी के लिए डुप्लीकेट वैल्यू की अनुमति नहीं देता. इसके बाद, multiEntry यह तय करता है कि इंडेक्स की गई प्रॉपर्टी के कलेक्शन में, createIndex() कैसे काम करता है. अगर इसे true पर सेट किया जाता है, तो createIndex() हर ऐरे एलिमेंट के लिए इंडेक्स में एक एंट्री जोड़ता है. ऐसा न होने पर, यह अरे वाली एक एंट्री जोड़ता है.

यहां एक उदाहरण दिया गया है:

import {openDB} from 'idb';

async function createIndexesInStores () {
  const dbPromise = await openDB('test-db3', 1, {
    upgrade (db) {
      if (!db.objectStoreNames.contains('people')) {
        const peopleObjectStore = db.createObjectStore('people', { keyPath: 'email' });

        peopleObjectStore.createIndex('gender', 'gender', { unique: false });
        peopleObjectStore.createIndex('ssn', 'ssn', { unique: true });
      }

      if (!db.objectStoreNames.contains('notes')) {
        const notesObjectStore = db.createObjectStore('notes', { autoIncrement: true });

        notesObjectStore.createIndex('title', 'title', { unique: false });
      }

      if (!db.objectStoreNames.contains('logs')) {
        const logsObjectStore = db.createObjectStore('logs', { keyPath: 'id', autoIncrement: true });
      }
    }
  });
}

createIndexesInStores();

इस उदाहरण में, 'people' और 'notes' ऑब्जेक्ट स्टोर में इंडेक्स मौजूद हैं. इंडेक्स बनाने के लिए, सबसे पहले createObjectStore() के नतीजे (जो एक ऑब्जेक्ट स्टोर ऑब्जेक्ट है) को किसी वैरिएबल को असाइन करें, ताकि उस पर createIndex() को कॉल किया जा सके.

डेटा का इस्तेमाल कैसे करें

इस सेक्शन में, डेटा बनाने, पढ़ने, अपडेट करने, और मिटाने का तरीका बताया गया है. जहां IndexedDB API, अनुरोधों का इस्तेमाल करता है वहां वादों का इस्तेमाल करते हुए, ये सभी कार्रवाइयां एसिंक्रोनस होती हैं. यह एपीआई को आसान बनाता है. अनुरोध से ट्रिगर हुए इवेंट के बारे में जानने के बजाय, डेटाबेस के साथ इंटरैक्शन शुरू करने या await बनाने के लिए, openDB() तरीके से मिले डेटाबेस ऑब्जेक्ट पर .then() को कॉल किया जा सकता है.

IndexedDB में सभी डेटा कार्रवाइयां एक ट्रांज़ैक्शन में की जाती हैं. हर एक ऑपरेशन में यह फ़ॉर्म होता है:

  1. डेटाबेस ऑब्जेक्ट पाएं.
  2. डेटाबेस पर ट्रांज़ैक्शन खोलें.
  3. लेन-देन होने पर ऑब्जेक्ट स्टोर खोलें.
  4. ऑब्जेक्ट स्टोर पर कार्रवाई करें.

किसी कार्रवाई या ग्रुप के लिए, लेन-देन को एक सुरक्षित रैपर की तरह माना जा सकता है. अगर किसी लेन-देन की कोई भी कार्रवाई काम नहीं करती, तो सभी कार्रवाइयां वापस ले ली जाती हैं. ट्रांज़ैक्शन, एक या उससे ज़्यादा ऑब्जेक्ट स्टोर के लिए खास होते हैं. ट्रांज़ैक्शन खोलने पर आपके पास इनकी जानकारी होती है. इन्हें सिर्फ़ पढ़ा जा सकता है या इन्हें पढ़ा और लिखा जा सकता है. इससे पता चलता है कि ट्रांज़ैक्शन में मौजूद कार्रवाइयां डेटा को पढ़ती हैं या डेटाबेस में कोई बदलाव करती हैं.

डेटा बनाएं

डेटा बनाने के लिए, डेटाबेस इंस्टेंस पर मौजूद add() तरीके को कॉल करें और वह डेटा पास करें जिसे आपको जोड़ना है. add() तरीके का पहला आर्ग्युमेंट वह ऑब्जेक्ट स्टोर होता है जिसमें आपको डेटा जोड़ना है. दूसरा तर्क वह ऑब्जेक्ट है जिसमें वे फ़ील्ड और उससे जुड़ा डेटा होता है जिसे आपको जोड़ना है. यहां सबसे आसान उदाहरण दिया गया है, जिसमें डेटा की एक लाइन जोड़ी जाती है:

import {openDB} from 'idb';

async function addItemToStore () {
  const db = await openDB('example-database', 1);

  await db.add('storeName', {
    field: 'data'
  });
}

addItemToStore();

हर add() कॉल, लेन-देन के दौरान होता है. इसलिए, अगर प्रॉमिस पूरी हो गई है, तो भी इसका मतलब यह नहीं है कि कार्रवाई पूरी हो गई. याद रखें, अगर लेन-देन में हुई कोई भी कार्रवाई पूरी नहीं हो पाती है, तो लेन-देन की सभी कार्रवाइयां रद्द कर दी जाती हैं.

यह पक्का करने के लिए कि जोड़ा गया है या नहीं, आपको transaction.done() तरीके का इस्तेमाल करके यह देखना होगा कि पूरा लेन-देन पूरा हुआ है या नहीं. यह प्रॉमिस होता है, जो लेन-देन पूरा होने के बाद खत्म हो जाता है. अगर लेन-देन में गड़बड़ी होती है, तो इसे अस्वीकार कर दिया जाता है. ध्यान दें कि इस तरीके से लेन-देन पूरा नहीं होता. लेन-देन अपने-आप पूरा हो जाता है. आपको सभी "लिखने" वाली कार्रवाइयों के लिए यह चेक करना होगा, क्योंकि यह जानने का आपका एकमात्र तरीका है कि डेटाबेस में किए गए बदलाव वास्तव में हो चुके हैं.

यहां दिया गया कोड, add() तरीके के इस्तेमाल को दिखाता है, लेकिन इस बार लेन-देन का इस्तेमाल कर रहा है:

import {openDB} from 'idb';

async function addItemsToStore () {
  const db = await openDB('test-db4', 1, {
    upgrade (db) {
      if (!db.objectStoreNames.contains('foods')) {
        db.createObjectStore('foods', { keyPath: 'name' });
      }
    }
  });
  
  // Create a transaction on the 'foods' store in read/write mode:
  const tx = db.transaction('foods', 'readwrite');

  // Add multiple items to the 'foods' store in a single transaction:
  await Promise.all([
    tx.store.add({
      name: 'Sandwich',
      price: 4.99,
      description: 'A very tasty sandwich!',
      created: new Date().getTime(),
    }),
    tx.store.add({
      name: 'Eggs',
      price: 2.99,
      description: 'Some nice eggs you can cook up!',
      created: new Date().getTime(),
    }),
    tx.done
  ]);
}

addItemsToStore();

डेटाबेस खोलने के बाद (और अगर ज़रूरी हो, तो ऑब्जेक्ट स्टोर बनाएं), तो आपको transaction() तरीके को कॉल करके ट्रांज़ैक्शन खोलना होगा. यह तरीका उस स्टोर के लिए तर्क लेता है जिस पर आपको लेन-देन करना है. साथ ही, मोड के लिए भी विवाद होता है. इस मामले में, हमारी दिलचस्पी स्टोर को लिखित में देना है, इसलिए पिछले उदाहरण में 'readwrite' के बारे में बताया गया है.

अगले चरण में, ट्रांज़ैक्शन के तहत स्टोर में आइटम जोड़ना शुरू किया जाता है. पिछले उदाहरण में, हम 'foods' स्टोर पर तीन ऐसी कार्रवाइयों पर काम कर रहे हैं जिनमें हर एक प्रॉमिस मिलता है:

  1. स्वादिष्ट सैंडविच का रिकॉर्ड जोड़ रही हूँ.
  2. कुछ अंडों के लिए रिकॉर्ड जोड़ा जा रहा है.
  3. यह सिग्नल देना कि लेन-देन पूरा हो गया है (tx.done).

ये सभी कार्रवाइयां वादों पर आधारित हैं, इसलिए हमें सभी के पूरा होने तक इंतज़ार करना होगा. इन वादों को Promise.all के साथ पास करना, ऐसा करने का अच्छा और आसान तरीका है. Promise.all, कई तरह के वादों को स्वीकार करता है. साथ ही, इसे पास किए गए सभी वादों के हल हो जाने के बाद यह पूरा हो जाएगा.

जोड़े जा रहे दो रिकॉर्ड में, ट्रांज़ैक्शन इंस्टेंस के store इंटरफ़ेस में add का तरीका मौजूद है. इसे कॉल किया जा सकता है और हर रिकॉर्ड को डेटा भेजा जाता है. Promise.all कॉल को await किया जा सकता है. यह लेन-देन पूरा होने के बाद पूरा होगा.

डेटा पढ़ने की अनुमति दें

डेटा पढ़ने के लिए, openDB() तरीके का इस्तेमाल करके डेटा फ़ेच करने के लिए, डेटाबेस इंस्टेंस पर मौजूद get() तरीके को कॉल करें. get() में स्टोर का नाम होता है. साथ ही, इसमें उस ऑब्जेक्ट की प्राइमरी कुंजी वैल्यू भी ली जाती है जिसे आपको स्टोर से वापस पाना है. यहां एक उदाहरण दिया गया है:

import {openDB} from 'idb';

async function getItemFromStore () {
  const db = await openDB('example-database', 1);

  // Get a value from the object store by its primary key value:
  const value = await db.get('storeName', 'unique-primary-key-value');
}

getItemFromStore();

add() की तरह, get() तरीका एक प्रॉमिस देता है, ताकि अगर आप चाहें, तो इसे await कर सकें. अगर ऐसा नहीं है, तो .then() कॉलबैक सभी प्रॉमिस ऑफ़र का इस्तेमाल किया जा सकता है.

इस उदाहरण में, 'test-db4' डेटाबेस के 'foods' ऑब्जेक्ट स्टोर पर get() तरीके का इस्तेमाल किया गया है, ताकि 'name' प्राइमरी कुंजी से एक लाइन मिल सके:

import {openDB} from 'idb';

async function getItemFromStore () {
  const db = await openDB('test-db4', 1);
  const value = await db.get('foods', 'Sandwich');

  console.dir(value);
}

getItemFromStore();

डेटाबेस से एक लाइन को वापस पाने का तरीका काफ़ी आसान है: डेटाबेस खोलें और ऑब्जेक्ट स्टोर और उस पंक्ति की प्राइमरी कुंजी वैल्यू तय करें जिससे आपको डेटा चाहिए. get() तरीके से प्रॉमिस मिलता है, इसलिए इसे await किया जा सकता है.

डेटा अपडेट करना

डेटा अपडेट करने के लिए, ऑब्जेक्ट स्टोर पर put() तरीके को कॉल करें. put() तरीका, add() तरीके की तरह ही है. साथ ही, ऑब्जेक्ट स्टोर में डेटा बनाने के लिए, add() की जगह पर इसे इस्तेमाल किया जा सकता है. किसी ऑब्जेक्ट स्टोर में किसी लाइन को मुख्य कुंजी की वैल्यू के हिसाब से अपडेट करने के लिए, put() का इस्तेमाल करने का सबसे आसान उदाहरण यहां दिया गया है:

import {openDB} from 'idb';

async function updateItemInStore () {
  const db = await openDB('example-database', 1);

  // Update a value from in an object store with an in-line key:
  await db.put('storeName', { inlineKeyName: 'newValue' });

  // Update a value from in an object store with an out-of-line key.
  // In this case, the out-of-line key value is 1, which is the
  // auto-incremented value.
  await db.put('otherStoreName', { field: 'value' }, 1);
}

updateItemInStore();

दूसरे तरीकों की तरह, यह तरीका भी प्रॉमिस देता है. लेन-देन में put() का इस्तेमाल भी किया जा सकता है, जैसे कि add() वाले तरीके के लिए किया जाता है. यहां पहले से 'foods' स्टोर का इस्तेमाल करने का एक उदाहरण दिया गया है. हालांकि, हम सैंडविच और अंडों की कीमत को अपडेट करते हैं:

import {openDB} from 'idb';

async function updateItemsInStore () {
  const db = await openDB('test-db4', 1);
  
  // Create a transaction on the 'foods' store in read/write mode:
  const tx = db.transaction('foods', 'readwrite');

  // Update multiple items in the 'foods' store in a single transaction:
  await Promise.all([
    tx.store.put({
      name: 'Sandwich',
      price: 5.99,
      description: 'A MORE tasty sandwich!',
      updated: new Date().getTime() // This creates a new field
    }),
    tx.store.put({
      name: 'Eggs',
      price: 3.99,
      description: 'Some even NICER eggs you can cook up!',
      updated: new Date().getTime() // This creates a new field
    }),
    tx.done
  ]);
}

updateItemsInStore();

आइटम अपडेट होने का तरीका, इस बात पर निर्भर करता है कि आपने कुंजी सेट करने का तरीका कैसे तय किया है. अगर आप keyPath सेट करते हैं, तो ऑब्जेक्ट स्टोर की हर पंक्ति, इन-लाइन कुंजी से जुड़ी होती है. पिछले उदाहरण में, इस कुंजी के आधार पर पंक्तियां अपडेट की जाती हैं. इस स्थिति में पंक्तियों को अपडेट करने पर, आपको ऑब्जेक्ट स्टोर में सही आइटम को अपडेट करने के लिए, उस कुंजी की जानकारी देनी होगी. autoIncrement को प्राइमरी कुंजी के तौर पर सेट करके, आउट-ऑफ़-लाइन कुंजी बनाई जाती है.

डेटा मिटाएं

डेटा मिटाने के लिए, ऑब्जेक्ट स्टोर पर delete() तरीके को कॉल करें:

import {openDB} from 'idb';

async function deleteItemFromStore () {
  const db = await openDB('example-database', 1);

  // Delete a value 
  await db.delete('storeName', 'primary-key-value');
}

deleteItemFromStore();

add() और put() की तरह, इसका भी लेन-देन के हिस्से के तौर पर इस्तेमाल किया जा सकता है:

import {openDB} from 'idb';

async function deleteItemsFromStore () {
  const db = await openDB('test-db4', 1);
  
  // Create a transaction on the 'foods' store in read/write mode:
  const tx = db.transaction('foods', 'readwrite');

  // Delete multiple items from the 'foods' store in a single transaction:
  await Promise.all([
    tx.store.delete('Sandwich'),
    tx.store.delete('Eggs'),
    tx.done
  ]);
}

deleteItemsFromStore();

डेटाबेस इंटरैक्शन का स्ट्रक्चर, अन्य ऑपरेशन के जैसा ही होता है. ध्यान दें, फिर से यह जांच लें कि पूरा लेन-देन पूरा हुआ है या नहीं. इसके लिए, Promise.all को पास किए जाने वाले कलेक्शन में tx.done तरीके को शामिल करके, यह पक्का करें कि डेटा मिटा दिया गया है.

सारा डेटा फ़ेच किया जा रहा है

अभी तक आपने स्टोर से एक बार में सिर्फ़ एक ऑब्जेक्ट वापस लिया है. getAll() तरीके या कर्सर का इस्तेमाल करके, किसी ऑब्जेक्ट स्टोर या इंडेक्स से पूरा डेटा या सबसेट भी हासिल किया जा सकता है.

getAll() तरीके का इस्तेमाल करना

किसी ऑब्जेक्ट स्टोर का पूरा डेटा वापस पाने का सबसे आसान तरीका यह है कि ऑब्जेक्ट स्टोर या इंडेक्स पर getAll() तरीके को कॉल किया जाए. जैसे:

import {openDB} from 'idb';

async function getAllItemsFromStore () {
  const db = await openDB('test-db4', 1);

  // Get all values from the designated object store:
  const allValues = await db.getAll('storeName');

  console.dir(allValues);
}

getAllItemsFromStore();

यह तरीका ऑब्जेक्ट स्टोर के सभी ऑब्जेक्ट को बिना किसी सीमा के दिखाता है. यह किसी ऑब्जेक्ट स्टोर से सभी वैल्यू पाने का सबसे सीधा तरीका है, लेकिन यह सबसे कम सुविधाजनक भी है.

import {openDB} from 'idb';

async function getAllItemsFromStore () {
  const db = await openDB('test-db4', 1);

  // Get all values from the designated object store:
  const allValues = await db.getAll('foods');

  console.dir(allValues);
}

getAllItemsFromStore();

यहां, 'foods' ऑब्जेक्ट स्टोर पर getAll() को कॉल किया गया है. यह 'foods' के वे सभी ऑब्जेक्ट दिखाता है जिन्हें मुख्य कुंजी के हिसाब से क्रम में लगाया गया है.

कर्सर इस्तेमाल करने का तरीका

पूरा डेटा वापस पाने का एक और तरीका है कर्सर का इस्तेमाल करना. कर्सर, ऑब्जेक्ट स्टोर में हर ऑब्जेक्ट को एक-एक करके चुनता है या इंडेक्स करता है. इससे, डेटा चुने जाने पर, उसके साथ कुछ किया जा सकता है. कर्सर, दूसरे डेटाबेस ऑपरेशन की तरह ही ट्रांज़ैक्शन में काम करते हैं.

ऑब्जेक्ट स्टोर पर, openCursor() तरीके को कॉल करके कर्सर बनाया जाता है. ऐसा लेन-देन के तहत किया जाता है. पिछले उदाहरणों में दिए गए 'foods' स्टोर का इस्तेमाल करके, इस तरह से किसी ऑब्जेक्ट स्टोर में डेटा की सभी पंक्तियों पर कर्सर को आगे बढ़ाया जा सकता है:

import {openDB} from 'idb';

async function getAllItemsFromStoreWithCursor () {
  const db = await openDB('test-db4', 1);
  const tx = await db.transaction('foods', 'readonly');

  // Open a cursor on the designated object store:
  let cursor = await tx.store.openCursor();

  // Iterate on the cursor, row by row:
  while (cursor) {
    // Show the data in the row at the current cursor position:
    console.log(cursor.key, cursor.value);

    // Advance the cursor to the next row:
    cursor = await cursor.continue();
  }
}

getAllItemsFromStoreWithCursor();

इस मामले में, लेन-देन 'readonly' मोड में खुलता है और इसके openCursor तरीके को कॉल किया जाता है. बाद के while लूप में, कर्सर की मौजूदा जगह पर मौजूद पंक्ति की key और value प्रॉपर्टी को पढ़ा जा सकता है. फिर, आपके पास उन वैल्यू पर काम करने का विकल्प है जो भी आपके ऐप्लिकेशन के लिए सबसे सही हो. जब आप तैयार हों, तब अगली लाइन पर जाने के लिए cursor ऑब्जेक्ट की continue() तरीके को कॉल करें. साथ ही, डेटासेट के खत्म होने पर while लूप खत्म हो जाता है.

रेंज और इंडेक्स के साथ कर्सर इस्तेमाल करने का तरीका

आपके पास कई तरीकों से पूरा डेटा पाने का विकल्प होता है, लेकिन अगर आपको किसी खास प्रॉपर्टी पर आधारित डेटा का सिर्फ़ एक सबसेट चाहिए, तो क्या होगा? ऐसे में इंडेक्स का इस्तेमाल शुरू किया जाता है. इंडेक्स की मदद से, किसी ऑब्जेक्ट स्टोर में मुख्य कुंजी के बजाय किसी दूसरी प्रॉपर्टी से डेटा फ़ेच किया जा सकता है. किसी भी प्रॉपर्टी पर इंडेक्स बनाया जा सकता है, जो इंडेक्स के लिए keyPath बन जाता है. साथ ही, उस प्रॉपर्टी के लिए कोई रेंज तय की जा सकती है. साथ ही, getAll() तरीके या कर्सर का इस्तेमाल करके, रेंज में शामिल डेटा पाया जा सकता है.

आपने IDBKeyRange ऑब्जेक्ट का इस्तेमाल करके रेंज तय की है. इस ऑब्जेक्ट में पांच तरीके हैं, जिनका इस्तेमाल रेंज की सीमाओं को तय करने के लिए किया जाता है:

उम्मीद के मुताबिक, upperBound() और lowerBound() तरीके, रेंज की ऊपरी और निचली सीमा के बारे में बताते हैं.

IDBKeyRange.lowerBound(indexKey);

या:

IDBKeyRange.upperBound(indexKey);

उनमें से हर एक तर्क लेता है, जो उस आइटम का इंडेक्स keyPath मान होता है जिसे आप ऊपरी या निचली सीमा के रूप में तय करना चाहते हैं.

bound() तरीके का इस्तेमाल ऊपरी और निचली सीमा, दोनों के बारे में बताने के लिए किया जाता है. यह पहले तर्क के रूप में निचली सीमा को लेता है:

IDBKeyRange.bound(lowerIndexKey, upperIndexKey);

इन फ़ंक्शन की रेंज में डिफ़ॉल्ट रूप से शामिल होता है. हालांकि, इसे true को दूसरे तर्क के तौर पर (या bound() के मामले में तीसरे और चौथे तर्क के साथ, निचली और ऊपरी सीमाओं के लिए पास करके खास के तौर पर तय किया जा सकता है). शामिल की गई रेंज में, सीमा का डेटा शामिल होता है. खास रेंज में ऐसा नहीं होता.

आइए एक उदाहरण देखें. इस डेमो के लिए, आपने 'foods' ऑब्जेक्ट स्टोर में 'price' प्रॉपर्टी पर एक इंडेक्स बनाया है. आपने रेंज की ऊपरी और निचली सीमाओं के लिए, दो इनपुट के साथ एक छोटा फ़ॉर्म भी जोड़ा है. मान लें कि आपने फ़ंक्शन में निचली और ऊपरी सीमाओं को, कीमतों को दिखाने वाले फ़्लोटिंग पॉइंट नंबर के तौर पर पास किया है:

import {openDB} from 'idb';

async function searchItems (lower, upper) {
  if (!lower === '' && upper === '') {
    return;
  }

  let range;

  if (lower !== '' && upper !== '') {
    range = IDBKeyRange.bound(lower, upper);
  } else if (lower === '') {
    range = IDBKeyRange.upperBound(upper);
  } else {
    range = IDBKeyRange.lowerBound(lower);
  }

  const db = await openDB('test-db4', 1);
  const tx = await db.transaction('foods', 'readonly');
  const index = tx.store.index('price');

  // Open a cursor on the designated object store:
  let cursor = await index.openCursor(range);

  if (!cursor) {
    return;
  }

  // Iterate on the cursor, row by row:
  while (cursor) {
    // Show the data in the row at the current cursor position:
    console.log(cursor.key, cursor.value);

    // Advance the cursor to the next row:
    cursor = await cursor.continue();
  }
}

// Get items priced between one and four dollars:
searchItems(1.00, 4.00);

कोड पहले, सीमाओं के लिए वैल्यू की जानकारी लेता है और देखता है कि सीमाएं मौजूद हैं या नहीं. कोड का अगला ब्लॉक यह तय करता है कि वैल्यू के आधार पर रेंज को सीमित करने के लिए, कौनसा तरीका इस्तेमाल करना है. डेटाबेस इंटरैक्शन में, हमेशा की तरह ट्रांज़ैक्शन पर ऑब्जेक्ट स्टोर खोलें. इसके बाद, ऑब्जेक्ट स्टोर पर 'price' इंडेक्स खोलें. 'price' इंडेक्स की मदद से, कीमत के हिसाब से आइटम खोजे जा सकते हैं.

फिर, इंडेक्स पर एक कर्सर खोलें और रेंज में पास करें. कर्सर अब रेंज में पहले ऑब्जेक्ट को दिखाने वाला प्रॉमिस देता है. अगर रेंज में कोई डेटा नहीं है, तो कर्सर undefined दिखाता है. cursor.continue() तरीके अगले ऑब्जेक्ट को दिखाने वाला कर्सर दिखाता है. यही क्रम लूप की मदद से तब तक दिखाया जाता है, जब तक आप रेंज के आखिर में नहीं पहुंच जाते.

डेटाबेस वर्शनिंग का इस्तेमाल करना

openDB() तरीके को कॉल करते समय, दूसरे पैरामीटर में डेटाबेस का वर्शन नंबर तय किया जा सकता है. इस गाइड में दिए गए सभी उदाहरणों में, वर्शन को 1 पर सेट किया गया है. हालांकि, अगर आपको डेटाबेस में किसी तरह से बदलाव करने की ज़रूरत पड़ती है, तो उसे नए वर्शन में अपग्रेड किया जा सकता है. अगर दिया गया वर्शन, मौजूदा डेटाबेस के वर्शन से बड़ा है, तो इवेंट ऑब्जेक्ट में upgrade कॉलबैक लागू होगा. इससे, डेटाबेस में नए ऑब्जेक्ट स्टोर और इंडेक्स जोड़े जा सकेंगे.

upgrade कॉलबैक के db ऑब्जेक्ट में एक खास oldVersion प्रॉपर्टी होती है, जो ब्राउज़र में मौजूद डेटाबेस का मौजूदा वर्शन नंबर दिखाती है. आपके पास इस वर्शन नंबर को switch स्टेटमेंट में पास करने का विकल्प है. इससे मौजूदा डेटाबेस के वर्शन नंबर के आधार पर, upgrade कॉलबैक में कोड के ब्लॉक चलाए जा सकते हैं. यहां एक उदाहरण दिया गया है:

import {openDB} from 'idb';

const db = await openDB('example-database', 2, {
  upgrade (db, oldVersion) {
    switch (oldVersion) {
      case 0:
        // Create first object store:
        db.createObjectStore('store', { keyPath: 'name' });

      case 1:
        // Get the original object store, and create an index on it:
        const tx = await db.transaction('store', 'readwrite');
        tx.store.createIndex('name', 'name');
    }
  }
});

यह उदाहरण, डेटाबेस के सबसे नए वर्शन को 2 पर सेट करता है. जब यह कोड पहली बार काम करता है और ब्राउज़र में डेटाबेस मौजूद नहीं है, तो oldVersion 0 होता है और switch स्टेटमेंट case 0 से शुरू होता है. उदाहरण में, इसकी वजह से डेटाबेस में एक 'store' ऑब्जेक्ट स्टोर जुड़ जाता है.

'store' ऑब्जेक्ट स्टोर पर 'description' इंडेक्स बनाने के लिए, वर्शन नंबर अपडेट करें और इस तरह एक नया case ब्लॉक जोड़ें:

import {openDB} from 'idb';

const db = await openDB('example-database', 3, {
  upgrade (db, oldVersion) {
    switch (oldVersion) {
      case 0:
        // Create first object store:
        db.createObjectStore('store', { keyPath: 'name' });

      case 1:
        // Get the original object store, and create an index on it:
        const tx = await db.transaction('store', 'readwrite');
        tx.store.createIndex('name', 'name');

      case 2:
        const tx = await db.transaction('store', 'readwrite');
        tx.store.createIndex('description', 'description');
    }
  }
});

यह मानते हुए कि पिछले उदाहरण में बनाया गया डेटाबेस अब भी ब्राउज़र में मौजूद है, जब इसे oldVersion को चलाया जाता है, तो वह 2 होती है. case 0 और case 1 को स्किप कर दिया गया है और ब्राउज़र, case 2 में कोड को एक्ज़ीक्यूट करता है, जो 'description' इंडेक्स बनाता है. यह सब करने के बाद, ब्राउज़र के वर्शन 3 पर एक डेटाबेस होता है. इसमें 'store' ऑब्जेक्ट स्टोर होता है, जिसमें 'name' और 'description' इंडेक्स होते हैं.

इसके बारे में और पढ़ें

IndexedDB के इस्तेमाल के मामले में, नीचे दिए गए संसाधन कुछ ज़्यादा जानकारी और संदर्भ दे सकते हैं.

IndexedDB दस्तावेज़

डेटा स्टोरेज की सीमाएं