javascript
examples
examples.js⚡javascript
/**
* IndexedDB Examples
*
* Demonstrates client-side database storage with IndexedDB
*/
// =============================================================================
// 1. Basic Database Operations
// =============================================================================
/**
* Open or create a database
*/
function openDatabase(name, version = 1) {
return new Promise((resolve, reject) => {
const request = indexedDB.open(name, version);
request.onerror = (event) => {
console.error('Database error:', event.target.error);
reject(event.target.error);
};
request.onsuccess = (event) => {
const db = event.target.result;
console.log(`Database '${name}' opened successfully`);
resolve(db);
};
request.onupgradeneeded = (event) => {
const db = event.target.result;
console.log(
`Upgrading database from version ${event.oldVersion} to ${event.newVersion}`
);
// Create object stores
if (!db.objectStoreNames.contains('users')) {
const userStore = db.createObjectStore('users', {
keyPath: 'id',
autoIncrement: true,
});
// Create indexes
userStore.createIndex('email', 'email', { unique: true });
userStore.createIndex('name', 'name', { unique: false });
userStore.createIndex('age', 'age', { unique: false });
userStore.createIndex('createdAt', 'createdAt', { unique: false });
console.log('Created users object store with indexes');
}
if (!db.objectStoreNames.contains('posts')) {
const postStore = db.createObjectStore('posts', {
keyPath: 'id',
autoIncrement: true,
});
postStore.createIndex('authorId', 'authorId');
postStore.createIndex('category', 'category');
postStore.createIndex('publishedAt', 'publishedAt');
console.log('Created posts object store');
}
};
request.onblocked = () => {
console.warn('Database blocked - close other connections');
};
});
}
// =============================================================================
// 2. CRUD Operations
// =============================================================================
/**
* Add a record to an object store
*/
function addRecord(db, storeName, data) {
return new Promise((resolve, reject) => {
const transaction = db.transaction([storeName], 'readwrite');
const store = transaction.objectStore(storeName);
// Add timestamp if not present
if (!data.createdAt) {
data.createdAt = new Date().toISOString();
}
const request = store.add(data);
request.onsuccess = () => {
console.log(`Added record with id: ${request.result}`);
resolve(request.result);
};
request.onerror = () => {
console.error('Add failed:', request.error);
reject(request.error);
};
});
}
/**
* Get a record by key
*/
function getRecord(db, storeName, key) {
return new Promise((resolve, reject) => {
const transaction = db.transaction([storeName], 'readonly');
const store = transaction.objectStore(storeName);
const request = store.get(key);
request.onsuccess = () => {
resolve(request.result);
};
request.onerror = () => {
reject(request.error);
};
});
}
/**
* Get all records
*/
function getAllRecords(db, storeName) {
return new Promise((resolve, reject) => {
const transaction = db.transaction([storeName], 'readonly');
const store = transaction.objectStore(storeName);
const request = store.getAll();
request.onsuccess = () => {
resolve(request.result);
};
request.onerror = () => {
reject(request.error);
};
});
}
/**
* Update a record (put replaces or adds)
*/
function updateRecord(db, storeName, data) {
return new Promise((resolve, reject) => {
const transaction = db.transaction([storeName], 'readwrite');
const store = transaction.objectStore(storeName);
data.updatedAt = new Date().toISOString();
const request = store.put(data);
request.onsuccess = () => {
console.log(`Updated record with id: ${request.result}`);
resolve(request.result);
};
request.onerror = () => {
reject(request.error);
};
});
}
/**
* Delete a record
*/
function deleteRecord(db, storeName, key) {
return new Promise((resolve, reject) => {
const transaction = db.transaction([storeName], 'readwrite');
const store = transaction.objectStore(storeName);
const request = store.delete(key);
request.onsuccess = () => {
console.log(`Deleted record with id: ${key}`);
resolve();
};
request.onerror = () => {
reject(request.error);
};
});
}
// =============================================================================
// 3. Index Queries
// =============================================================================
/**
* Get record by index
*/
function getByIndex(db, storeName, indexName, value) {
return new Promise((resolve, reject) => {
const transaction = db.transaction([storeName], 'readonly');
const store = transaction.objectStore(storeName);
const index = store.index(indexName);
const request = index.get(value);
request.onsuccess = () => {
resolve(request.result);
};
request.onerror = () => {
reject(request.error);
};
});
}
/**
* Get all records by index value
*/
function getAllByIndex(db, storeName, indexName, value) {
return new Promise((resolve, reject) => {
const transaction = db.transaction([storeName], 'readonly');
const store = transaction.objectStore(storeName);
const index = store.index(indexName);
const request = index.getAll(value);
request.onsuccess = () => {
resolve(request.result);
};
request.onerror = () => {
reject(request.error);
};
});
}
// =============================================================================
// 4. Range Queries
// =============================================================================
/**
* Query with key range
*/
function getInRange(db, storeName, indexName, lower, upper, options = {}) {
return new Promise((resolve, reject) => {
const {
lowerOpen = false,
upperOpen = false,
direction = 'next', // 'next', 'prev', 'nextunique', 'prevunique'
limit = null,
} = options;
const transaction = db.transaction([storeName], 'readonly');
const store = transaction.objectStore(storeName);
const index = indexName ? store.index(indexName) : store;
let range;
if (lower !== undefined && upper !== undefined) {
range = IDBKeyRange.bound(lower, upper, lowerOpen, upperOpen);
} else if (lower !== undefined) {
range = IDBKeyRange.lowerBound(lower, lowerOpen);
} else if (upper !== undefined) {
range = IDBKeyRange.upperBound(upper, upperOpen);
}
const results = [];
const request = index.openCursor(range, direction);
request.onsuccess = (event) => {
const cursor = event.target.result;
if (cursor && (limit === null || results.length < limit)) {
results.push(cursor.value);
cursor.continue();
} else {
resolve(results);
}
};
request.onerror = () => {
reject(request.error);
};
});
}
// Example usage with ranges
async function rangeQueryExamples(db) {
// Get users aged 20-30
const youngAdults = await getInRange(db, 'users', 'age', 20, 30);
console.log('Users aged 20-30:', youngAdults);
// Get users created this week
const oneWeekAgo = new Date(
Date.now() - 7 * 24 * 60 * 60 * 1000
).toISOString();
const recentUsers = await getInRange(
db,
'users',
'createdAt',
oneWeekAgo,
undefined
);
console.log('Recent users:', recentUsers);
// Get first 5 users (sorted by key, descending)
const lastFive = await getInRange(db, 'users', null, undefined, undefined, {
direction: 'prev',
limit: 5,
});
console.log('Last 5 added users:', lastFive);
}
// =============================================================================
// 5. Cursor Operations
// =============================================================================
/**
* Iterate with cursor for complex operations
*/
function iterateWithCursor(db, storeName, callback, options = {}) {
return new Promise((resolve, reject) => {
const { indexName, range, direction = 'next' } = options;
const transaction = db.transaction([storeName], 'readonly');
const store = transaction.objectStore(storeName);
const target = indexName ? store.index(indexName) : store;
const request = target.openCursor(range, direction);
request.onsuccess = (event) => {
const cursor = event.target.result;
if (cursor) {
const shouldContinue = callback(cursor.value, cursor.key);
if (shouldContinue !== false) {
cursor.continue();
} else {
resolve();
}
} else {
resolve(); // No more records
}
};
request.onerror = () => {
reject(request.error);
};
});
}
/**
* Update records with cursor
*/
function updateWithCursor(db, storeName, filter, updater) {
return new Promise((resolve, reject) => {
const transaction = db.transaction([storeName], 'readwrite');
const store = transaction.objectStore(storeName);
let updateCount = 0;
const request = store.openCursor();
request.onsuccess = (event) => {
const cursor = event.target.result;
if (cursor) {
if (filter(cursor.value)) {
const updated = updater({ ...cursor.value });
updated.updatedAt = new Date().toISOString();
cursor.update(updated);
updateCount++;
}
cursor.continue();
}
};
transaction.oncomplete = () => {
console.log(`Updated ${updateCount} records`);
resolve(updateCount);
};
transaction.onerror = () => {
reject(transaction.error);
};
});
}
// =============================================================================
// 6. Batch Operations
// =============================================================================
/**
* Add multiple records in a single transaction
*/
function addBatch(db, storeName, records) {
return new Promise((resolve, reject) => {
const transaction = db.transaction([storeName], 'readwrite');
const store = transaction.objectStore(storeName);
const ids = [];
records.forEach((record) => {
if (!record.createdAt) {
record.createdAt = new Date().toISOString();
}
const request = store.add(record);
request.onsuccess = () => ids.push(request.result);
});
transaction.oncomplete = () => {
console.log(`Added ${ids.length} records`);
resolve(ids);
};
transaction.onerror = () => {
reject(transaction.error);
};
});
}
/**
* Delete multiple records
*/
function deleteBatch(db, storeName, keys) {
return new Promise((resolve, reject) => {
const transaction = db.transaction([storeName], 'readwrite');
const store = transaction.objectStore(storeName);
keys.forEach((key) => store.delete(key));
transaction.oncomplete = () => {
console.log(`Deleted ${keys.length} records`);
resolve();
};
transaction.onerror = () => {
reject(transaction.error);
};
});
}
// =============================================================================
// 7. IndexedDB Wrapper Class
// =============================================================================
class IndexedDBWrapper {
constructor(dbName, version = 1) {
this.dbName = dbName;
this.version = version;
this.db = null;
this.storeConfigs = new Map();
}
/**
* Configure an object store
*/
configureStore(name, keyPath = 'id', autoIncrement = true, indexes = []) {
this.storeConfigs.set(name, { keyPath, autoIncrement, indexes });
return this;
}
/**
* Open the database
*/
async open() {
return new Promise((resolve, reject) => {
const request = indexedDB.open(this.dbName, this.version);
request.onerror = () => reject(request.error);
request.onsuccess = () => {
this.db = request.result;
resolve(this);
};
request.onupgradeneeded = (event) => {
const db = event.target.result;
this.storeConfigs.forEach((config, name) => {
if (!db.objectStoreNames.contains(name)) {
const store = db.createObjectStore(name, {
keyPath: config.keyPath,
autoIncrement: config.autoIncrement,
});
config.indexes.forEach(({ name: indexName, keyPath, options }) => {
store.createIndex(indexName, keyPath || indexName, options || {});
});
}
});
};
});
}
/**
* Get a store accessor
*/
store(name) {
return new StoreAccessor(this.db, name);
}
/**
* Close the database
*/
close() {
if (this.db) {
this.db.close();
this.db = null;
}
}
/**
* Delete the database
*/
static async delete(dbName) {
return new Promise((resolve, reject) => {
const request = indexedDB.deleteDatabase(dbName);
request.onsuccess = () => resolve();
request.onerror = () => reject(request.error);
});
}
}
class StoreAccessor {
constructor(db, storeName) {
this.db = db;
this.storeName = storeName;
}
_transaction(mode = 'readonly') {
return this.db
.transaction([this.storeName], mode)
.objectStore(this.storeName);
}
_promisify(request) {
return new Promise((resolve, reject) => {
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
}
async add(data) {
data.createdAt = data.createdAt || new Date().toISOString();
return this._promisify(this._transaction('readwrite').add(data));
}
async put(data) {
data.updatedAt = new Date().toISOString();
return this._promisify(this._transaction('readwrite').put(data));
}
async get(key) {
return this._promisify(this._transaction().get(key));
}
async getAll(query, count) {
return this._promisify(this._transaction().getAll(query, count));
}
async delete(key) {
return this._promisify(this._transaction('readwrite').delete(key));
}
async clear() {
return this._promisify(this._transaction('readwrite').clear());
}
async count(query) {
return this._promisify(this._transaction().count(query));
}
index(indexName) {
return new IndexAccessor(this.db, this.storeName, indexName);
}
}
class IndexAccessor {
constructor(db, storeName, indexName) {
this.db = db;
this.storeName = storeName;
this.indexName = indexName;
}
_getIndex() {
return this.db
.transaction([this.storeName], 'readonly')
.objectStore(this.storeName)
.index(this.indexName);
}
_promisify(request) {
return new Promise((resolve, reject) => {
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
}
async get(key) {
return this._promisify(this._getIndex().get(key));
}
async getAll(query, count) {
return this._promisify(this._getIndex().getAll(query, count));
}
async count(query) {
return this._promisify(this._getIndex().count(query));
}
}
// =============================================================================
// 8. Offline Data Sync Example
// =============================================================================
class OfflineStore {
constructor(dbName) {
this.dbName = dbName;
this.db = null;
this.syncQueue = [];
}
async init() {
const wrapper = new IndexedDBWrapper(this.dbName, 1);
wrapper
.configureStore('data', 'localId', true, [
{ name: 'serverId', keyPath: 'serverId' },
{ name: 'syncStatus', keyPath: 'syncStatus' },
])
.configureStore('syncQueue', 'id', true, [
{ name: 'timestamp', keyPath: 'timestamp' },
]);
await wrapper.open();
this.db = wrapper.db;
return this;
}
async saveLocal(data) {
const record = {
...data,
syncStatus: 'pending',
localId: data.localId || crypto.randomUUID(),
modifiedAt: new Date().toISOString(),
};
const store = this.db.transaction(['data', 'syncQueue'], 'readwrite');
// Save data
const dataStore = store.objectStore('data');
await this._promisify(dataStore.put(record));
// Add to sync queue
const queueStore = store.objectStore('syncQueue');
await this._promisify(
queueStore.add({
localId: record.localId,
action: 'upsert',
timestamp: Date.now(),
})
);
return record.localId;
}
async getPendingSync() {
const store = this.db
.transaction(['data'], 'readonly')
.objectStore('data')
.index('syncStatus');
return this._promisify(store.getAll('pending'));
}
async markSynced(localId, serverId) {
const transaction = this.db.transaction(['data'], 'readwrite');
const store = transaction.objectStore('data');
const record = await this._promisify(store.get(localId));
if (record) {
record.serverId = serverId;
record.syncStatus = 'synced';
record.syncedAt = new Date().toISOString();
await this._promisify(store.put(record));
}
}
async sync(serverApi) {
const pending = await this.getPendingSync();
for (const record of pending) {
try {
const serverId = await serverApi.save(record);
await this.markSynced(record.localId, serverId);
console.log(`Synced: ${record.localId} -> ${serverId}`);
} catch (error) {
console.error(`Sync failed for ${record.localId}:`, error);
}
}
}
_promisify(request) {
return new Promise((resolve, reject) => {
request.onsuccess = () => resolve(request.result);
request.onerror = () => reject(request.error);
});
}
}
// =============================================================================
// 9. Full Example Usage
// =============================================================================
async function runExamples() {
console.log('=== IndexedDB Examples ===\n');
// Using wrapper class
const wrapper = new IndexedDBWrapper('exampleDB', 1);
wrapper
.configureStore('users', 'id', true, [
{ name: 'email', keyPath: 'email', options: { unique: true } },
{ name: 'name', keyPath: 'name' },
{ name: 'age', keyPath: 'age' },
])
.configureStore('posts', 'id', true, [
{ name: 'authorId', keyPath: 'authorId' },
{ name: 'category', keyPath: 'category' },
]);
await wrapper.open();
console.log('Database opened\n');
// Add users
const users = wrapper.store('users');
const user1Id = await users.add({
name: 'Alice',
email: 'alice@example.com',
age: 28,
});
const user2Id = await users.add({
name: 'Bob',
email: 'bob@example.com',
age: 35,
});
const user3Id = await users.add({
name: 'Charlie',
email: 'charlie@example.com',
age: 22,
});
console.log('Added users:', { user1Id, user2Id, user3Id });
// Get all users
const allUsers = await users.getAll();
console.log('All users:', allUsers);
// Query by index
const alice = await users.index('email').get('alice@example.com');
console.log('Found Alice:', alice);
// Update user
await users.put({ ...alice, age: 29 });
console.log('Updated Alice age to 29');
// Count
const count = await users.count();
console.log('User count:', count);
// Add posts
const posts = wrapper.store('posts');
await posts.add({
title: 'Hello World',
authorId: user1Id,
category: 'tech',
});
await posts.add({
title: 'JavaScript Tips',
authorId: user1Id,
category: 'tech',
});
await posts.add({
title: 'Travel Guide',
authorId: user2Id,
category: 'travel',
});
// Get posts by author
const alicePosts = await posts.index('authorId').getAll(user1Id);
console.log('Alice posts:', alicePosts);
// Cleanup
wrapper.close();
console.log('\nDatabase closed');
}
// Export for use in other files
if (typeof module !== 'undefined' && module.exports) {
module.exports = {
openDatabase,
addRecord,
getRecord,
getAllRecords,
updateRecord,
deleteRecord,
getByIndex,
getAllByIndex,
getInRange,
IndexedDBWrapper,
OfflineStore,
};
}
// Run examples (comment out in production)
// runExamples().catch(console.error);