IndexedDB-এর সাথে কাজ করা

এই গাইডে IndexedDB API-এর প্রাথমিক বিষয়গুলি কভার করা হয়েছে। আমরা জ্যাক আর্চিবল্ডের IndexedDB Promised লাইব্রেরি ব্যবহার করছি, যা IndexedDB API-এর সাথে খুব মিল, কিন্তু প্রমিস ব্যবহার করে, যা আপনি আরও সংক্ষিপ্ত সিনট্যাক্সের জন্য await করতে পারেন। এটি API-এর স্ট্রাকচার বজায় রেখে সেটিকে সরল করে।

IndexedDB কী?

IndexedDB হল একটি বৃহৎ-স্কেল, NoSQL স্টোরেজ সিস্টেম যা ব্যবহারকারীর ব্রাউজারে প্রায় যেকোনো কিছু স্টোরেজ করার অনুমতি দেয়। সাধারণ সার্চ, গেট ও পুট অ্যাকশন ছাড়াও, IndexedDB ট্রানজ্যাকশনও সাপোর্ট করে এবং এটি প্রচুর পরিমাণে স্ট্রাকচার্ড ডেটা স্টোর করার জন্য উপযুক্ত।

প্রতিটি IndexedDB ডেটাবেস একটি অরিজিনের (সাধারণত সাইট ডোমেন বা সাবডোমেন) জন্য অনন্য, যার অর্থ হল এটি অন্য কোনও অরিজিন অ্যাক্সেস করতে পারে না বা অন্য কোনও অরিজিন দ্বারা অ্যাক্সেস করা যায় না। এর ডেটা স্টোরেজ সীমা সাধারণত অনেক বেশি হয়, যদি আদৌ থাকে, তবে বিভিন্ন ব্রাউজার সীমা এবং ডেটা উচ্ছেদ আলাদাভাবে ম্যানেজ করে। আরও তথ্যের জন্য আরও পড়ুন বিভাগ দেখুন।

IndexedDB-এর শর্তাবলী

ডেটাবেস
IndexedDB-এর সর্বোচ্চ লেভেল। এতে অবজেক্ট স্টোর থাকে, যার মধ্যে আপনার সেভ করে রাখতে চাওয়া ডেটা থাকে। আপনি নিজের পছন্দমতো নাম দিয়ে একাধিক ডেটাবেস তৈরি করতে পারবেন।
অবজেক্ট স্টোর
ডেটা স্টোর করার জন্য একটি স্বতন্ত্র বাকেট, যা রিলেশনাল ডেটাবেসের টেবিলের মতো। সাধারণত, আপনি যে ডেটা সেভ করছেন তার প্রতিটি ধরনের (জাভাস্ক্রিপ্ট ডেটার ধরন নয়) জন্য একটি অবজেক্ট স্টোর থাকে। ডেটাবেস টেবিলের মতো নয়, স্টোরে থাকা ডেটার জাভাস্ক্রিপ্ট ডেটা টাইপ একই রকম হওয়ার প্রয়োজন নেই। যেমন, কোনও অ্যাপের 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 ডেটাবেসে এক বা একাধিক অবজেক্ট স্টোর থাকে, যার প্রতিটিতে 'কী'-এর জন্য একটি কলাম এবং সেই 'কী'-এর সাথে সম্পর্কিত ডেটার জন্য আরেকটি কলাম থাকে।

অবজেক্ট স্টোর তৈরি করুন

একটি সুগঠিত IndexedDB ডেটাবেসে প্রতিটি ধরনের ডেটার জন্য একটি অবজেক্ট স্টোর থাকা উচিত যা স্থায়ী হতে হবে। যেমন, ব্যবহারকারীর প্রোফাইল এবং নোট সেভ করে রাখে এমন সাইটে person অবজেক্ট সহ people অবজেক্ট স্টোর এবং note অবজেক্ট সহ notes অবজেক্ট স্টোর থাকতে পারে।

ডেটাবেসের ইন্টিগ্রিটি নিশ্চিত করতে, আপনি শুধুমাত্র 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() পদ্ধতিতে করা হয়। তবে, আপনি আগে থেকেই আছে এমন অবজেক্ট স্টোর তৈরি করার চেষ্টা করলে, ব্রাউজার একটি সমস্যা দেখায়, তাই আমরা অবজেক্ট স্টোর আছে কিনা তা চেক করে এমন একটি if স্টেটমেন্টে createObjectStore() পদ্ধতিটি র‍্যাপ করার সাজেস্ট করি। 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 একটি কী তৈরি করে এবং ডেটা থেকে আলাদা করে এটি স্টোর করে।

নিম্নলিখিত উদাহরণে 'notes' নামে একটি অবজেক্ট স্টোর তৈরি করা হয়েছে এবং অটোমেটিক-ইনক্রিমেন্টিং নম্বর হিসেবে অটোমেটিক অ্যাসাইন করার জন্য প্রাথমিক কী সেট করা হয়েছে:

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();

নিচের উদাহরণটি আগের উদাহরণের মতোই, তবে এইবার অটোমেটিক-ইনক্রিমেন্ট ভ্যালু স্পষ্টভাবে 'id' নামের প্রপার্টিতে অ্যাসাইন করা হয়েছে।

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();

'কী' নির্ধারণ করার জন্য কোন পদ্ধতি ব্যবহার করবেন তা আপনার ডেটার উপর নির্ভর করে। আপনার ডেটায় যদি এমন কোনও প্রপার্টি থাকে যা সবসময় অনন্য, তাহলে আপনি এটিকে 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 অনুরোধ ব্যবহার করে সেখানে প্রমিস ব্যবহার করে। এটি 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() পদ্ধতি ব্যবহার করে পুরো ট্রানজ্যাকশন সম্পূর্ণ হয়েছে কিনা তা চেক করতে হবে। এটি একটি প্রমিস যা ট্রানজ্যাকশন সম্পূর্ণ হলে রিজলভ হয় এবং ট্রানজ্যাকশনে সমস্যা হলে রিজেক্ট হয়। আপনাকে সব "write" অপারেশনের জন্য এই চেক করতে হবে, কারণ ডেটাবেসে পরিবর্তন হয়েছে কিনা তা জানার জন্য এটিই একমাত্র উপায়।

নিচে দেওয়া কোড থেকে ট্রানজ্যাকশনের মধ্যে 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) বলে সিগন্যাল দেওয়া।

Because all of these actions are all promise-based, we need to wait for all of them to finish. এইসব প্রমিস 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() কলব্যাক ব্যবহার করতে পারেন।

নিম্নলিখিত উদাহরণে 'name' প্রাথমিক কী ব্যবহার করে একটি সারি পেতে 'test-db4' ডেটাবেসের 'foods' অবজেক্ট স্টোরে get() পদ্ধতি ব্যবহার করা হয়েছে:

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 inline 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() ব্যবহার করতে পারবেন। আগের '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);

এইসব ফাংশনের রেঞ্জ ডিফল্ট হিসেবে অন্তর্ভুক্ত থাকে, যার অর্থ হল রেঞ্জের সীমা হিসেবে নির্দিষ্ট করা ডেটা এতে অন্তর্ভুক্ত থাকে। এইসব মান বাদ দিতে, lowerBound() বা upperBound()-এর জন্য দ্বিতীয় আর্গুমেন্ট হিসেবে 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' অবজেক্ট স্টোর যোগ করে।

মূল পয়েন্ট: switch স্টেটমেন্টে, সাধারণত প্রতিটি case ব্লকের পরে একটি break থাকে, কিন্তু এটি এখানে ইচ্ছাকৃতভাবে ব্যবহার করা হয়নি। এইভাবে, যদি বর্তমান ডেটাবেস কয়েকটি ভার্সন পিছনে থাকে, বা যদি এটি না থাকে, তাহলে কোডটি বাকি case ব্লকের মাধ্যমে চলতে থাকে যতক্ষণ না এটি আপ-টু-ডেট হয়। সুতরাং, উদাহরণে, ব্রাউজার case 1-এর মাধ্যমে এক্সিকিউট করা চালিয়ে যায়, store অবজেক্ট স্টোরে name ইন্ডেক্স তৈরি করে।

'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 ইনডেক্স তৈরি করে। এর পরে, ব্রাউজারে ভার্সন ৩-এ একটি ডেটাবেস থাকে যাতে name এবং description ইনডেক্স সহ একটি store অবজেক্ট স্টোর থাকে।

আরও পড়ুন

IndexedDB ব্যবহার করার জন্য নিম্নলিখিত রিসোর্স আরও তথ্য ও প্রসঙ্গ প্রদান করে।

IndexedDB ডকুমেন্টেশন

ডেটা স্টোরেজের সীমা