profpowell/project-template · Archived

data-storage

Implement client-side data storage with localStorage, IndexedDB, or SQLite WASM. Use when storing user preferences, caching data, or building offline-first applications.

First seen May 5, 2026

Installation

$ npx skills add profpowell/project-template --skill data-storage

Stronger alternatives

This repository is archived — consider an actively maintained alternative.

Similar popular skills

Related neighbors and high-traction skills in the same topics — useful to compare before installing.

Also in this package

Other skills from profpowell/project-template.

npx skills add profpowell/project-template

Browse all from profpowell/project-template

More details

Agent compatibility

Declared targets from SKILL.md / docs. Unmarked agents are not listed — the skill may still install via the CLI.

Claude Code Not declared
Cursor Not declared
Codex Not declared
GitHub Copilot Not declared
Windsurf Not declared
Gemini CLI Not declared
Cline Not declared
OpenCode Not declared

Repository health

Stars 1
Default branch main
Open issues 0
Status Archived

Skill metadata

Parsed from SKILL.md frontmatter.

Allowed toolsRead, Write, Edit, Glob, Grep

Package contents

Files included with this skill beyond the listing page.

  • skill md SKILL.md 13,549 B
  • docs SUMMARY.md 189 B

History

  1. First seen on skills.sh
  2. First recorded snapshot · 1 installs

SKILL.md

Frontend Data Storage Skill

Implement client-side data storage using localStorage, IndexedDB, or SQLite WASM for offline-capable web applications.


When to Use

  • Storing user preferences and settings
  • Caching API responses
  • Implementing offline-first applications
  • Managing large client-side datasets
  • Syncing data between sessions

Storage Options

Storage Best For Size Limit Persistence
localStorage Small key-value data, settings ~5MB Persistent
sessionStorage Temporary session data ~5MB Tab only
IndexedDB Larger datasets, offline apps ~50MB+ Persistent
SQLite WASM Complex queries, relational data ~50MB+ Persistent

LocalStorage Wrapper

Type-Safe Storage

/**
 * Type-safe localStorage wrapper with JSON serialization
 */
export const storage = {
  /**
   * Get value from storage
   * @template T
   * @param {string} key - Storage key
   * @param {T} defaultValue - Default if not found
   * @returns {T} - Stored value or default
   */
  get(key, defaultValue = null) {
    try {
      const item = localStorage.getItem(key);
      return item ? JSON.parse(item) : defaultValue;
    } catch {
      return defaultValue;
    }
  },

  /**
   * Set value in storage
   * @param {string} key - Storage key
   * @param {*} value - Value to store
   */
  set(key, value) {
    try {
      localStorage.setItem(key, JSON.stringify(value));
    } catch (error) {
      // Handle quota exceeded
      if (error.name === 'QuotaExceededError') {
        console.warn('Storage quota exceeded');
        this.cleanup();
        // Retry once
        localStorage.setItem(key, JSON.stringify(value));
      }
    }
  },

  /**
   * Remove value from storage
   * @param {string} key - Storage key
   */
  remove(key) {
    localStorage.removeItem(key);
  },

  /**
   * Clear all storage
   */
  clear() {
    localStorage.clear();
  },

  /**
   * Get all keys matching prefix
   * @param {string} prefix - Key prefix
   * @returns {string[]} - Matching keys
   */
  keys(prefix = '') {
    const keys = [];
    for (let i = 0; i < localStorage.length; i++) {
      const key = localStorage.key(i);
      if (key.startsWith(prefix)) {
        keys.push(key);
      }
    }
    return keys;
  },

  /**
   * Cleanup old/expired items
   */
  cleanup() {
    const now = Date.now();
    for (const key of this.keys()) {
      const item = this.get(key);
      if (item?.expiresAt && item.expiresAt < now) {
        this.remove(key);
      }
    }
  }
};

Settings Pattern

/**
 * User settings with defaults and persistence
 */
const DEFAULTS = {
  theme: 'system',
  language: 'en',
  fontSize: 'medium',
  notifications: true
};

export const settings = {
  _cache: null,

  /**
   * Load all settings
   * @returns {object} - Settings object
   */
  getAll() {
    if (!this._cache) {
      this._cache = {
        ...DEFAULTS,
        ...storage.get('user-settings', {})
      };
    }
    return this._cache;
  },

  /**
   * Get single setting
   * @param {string} key - Setting key
   * @returns {*} - Setting value
   */
  get(key) {
    return this.getAll()[key];
  },

  /**
   * Update settings
   * @param {object} updates - Settings to update
   */
  update(updates) {
    this._cache = { ...this.getAll(), ...updates };
    storage.set('user-settings', this._cache);

    // Emit change event
    window.dispatchEvent(new CustomEvent('settings-change', {
      detail: updates
    }));
  },

  /**
   * Reset to defaults
   */
  reset() {
    this._cache = { ...DEFAULTS };
    storage.set('user-settings', this._cache);
  }
};

IndexedDB Wrapper

Simple Store

/**
 * Simple IndexedDB wrapper for object stores
 */
export class IDBStore {
  /**
   * @param {string} dbName - Database name
   * @param {string} storeName - Object store name
   * @param {number} version - Database version
   */
  constructor(dbName, storeName, version = 1) {
    this.dbName = dbName;
    this.storeName = storeName;
    this.version = version;
    this.db = null;
  }

  /**
   * Open database connection
   * @returns {Promise<IDBDatabase>}
   */
  async open() {
    if (this.db) return this.db;

    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.db);
      };

      request.onupgradeneeded = (event) => {
        const db = event.target.result;
        if (!db.objectStoreNames.contains(this.storeName)) {
          db.createObjectStore(this.storeName, { keyPath: 'id' });
        }
      };
    });
  }

  /**
   * Get item by ID
   * @param {string} id - Item ID
   * @returns {Promise<*>}
   */
  async get(id) {
    const db = await this.open();
    return new Promise((resolve, reject) => {
      const tx = db.transaction(this.storeName, 'readonly');
      const request = tx.objectStore(this.storeName).get(id);
      request.onerror = () => reject(request.error);
      request.onsuccess = () => resolve(request.result);
    });
  }

  /**
   * Get all items
   * @returns {Promise<Array>}
   */
  async getAll() {
    const db = await this.open();
    return new Promise((resolve, reject) => {
      const tx = db.transaction(this.storeName, 'readonly');
      const request = tx.objectStore(this.storeName).getAll();
      request.onerror = () => reject(request.error);
      request.onsuccess = () => resolve(request.result);
    });
  }

  /**
   * Put item (insert or update)
   * @param {object} item - Item with id property
   * @returns {Promise<void>}
   */
  async put(item) {
    const db = await this.open();
    return new Promise((resolve, reject) => {
      const tx = db.transaction(this.storeName, 'readwrite');
      const request = tx.objectStore(this.storeName).put(item);
      request.onerror = () => reject(request.error);
      request.onsuccess = () => resolve();
    });
  }

  /**
   * Delete item by ID
   * @param {string} id - Item ID
   * @returns {Promise<void>}
   */
  async delete(id) {
    const db = await this.open();
    return new Promise((resolve, reject) => {
      const tx = db.transaction(this.storeName, 'readwrite');
      const request = tx.objectStore(this.storeName).delete(id);
      request.onerror = () => reject(request.error);
      request.onsuccess = () => resolve();
    });
  }

  /**
   * Clear all items
   * @returns {Promise<void>}
   */
  async clear() {
    const db = await this.open();
    return new Promise((resolve, reject) => {
      const tx = db.transaction(this.storeName, 'readwrite');
      const request = tx.objectStore(this.storeName).clear();
      request.onerror = () => reject(request.error);
      request.onsuccess = () => resolve();
    });
  }
}

// Usage
const todosStore = new IDBStore('my-app', 'todos');

await todosStore.put({ id: '1', title: 'Learn IDB', done: false });
const todos = await todosStore.getAll();

SQLite WASM

Using sql.js

/**
 * SQLite WASM wrapper using sql.js
 * @see https://sql.js.org/
 */
import initSqlJs from 'sql.js';

let SQL = null;

/**
 * Initialize SQL.js
 * @returns {Promise<void>}
 */
async function initSQL() {
  if (SQL) return;
  SQL = await initSqlJs({
    locateFile: file => `https://sql.js.org/dist/${file}`
  });
}

export class SQLiteDB {
  constructor(name) {
    this.name = name;
    this.db = null;
  }

  /**
   * Open or create database
   * @returns {Promise<void>}
   */
  async open() {
    await initSQL();

    // Try to load existing database from IndexedDB
    const stored = await this.loadFromStorage();
    if (stored) {
      this.db = new SQL.Database(stored);
    } else {
      this.db = new SQL.Database();
    }
  }

  /**
   * Execute SQL query
   * @param {string} sql - SQL statement
   * @param {Array} params - Query parameters
   * @returns {Array} - Result rows
   */
  exec(sql, params = []) {
    const stmt = this.db.prepare(sql);
    stmt.bind(params);

    const rows = [];
    while (stmt.step()) {
      rows.push(stmt.getAsObject());
    }
    stmt.free();
    return rows;
  }

  /**
   * Run SQL statement (no results)
   * @param {string} sql - SQL statement
   * @param {Array} params - Query parameters
   */
  run(sql, params = []) {
    this.db.run(sql, params);
  }

  /**
   * Save database to IndexedDB
   * @returns {Promise<void>}
   */
  async save() {
    const data = this.db.export();
    const buffer = new Uint8Array(data);

    const store = new IDBStore('sqlite-storage', 'databases');
    await store.put({ id: this.name, data: buffer });
  }

  /**
   * Load database from IndexedDB
   * @returns {Promise<Uint8Array|null>}
   */
  async loadFromStorage() {
    const store = new IDBStore('sqlite-storage', 'databases');
    const record = await store.get(this.name);
    return record?.data || null;
  }

  /**
   * Close database
   */
  close() {
    this.db?.close();
    this.db = null;
  }
}

// Usage
const db = new SQLiteDB('my-app');
await db.open();

// Create table
db.run(`
  CREATE TABLE IF NOT EXISTS users (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    name TEXT NOT NULL,
    email TEXT UNIQUE,
    created_at TEXT DEFAULT CURRENT_TIMESTAMP
  )
`);

// Insert
db.run('INSERT INTO users (name, email) VALUES (?, ?)', ['John', '[email protected]']);

// Query
const users = db.exec('SELECT * FROM users WHERE name LIKE ?', ['%John%']);

// Save to IndexedDB
await db.save();

Storage Abstraction

Unified Storage Interface

/**
 * Unified storage interface
 * Allows switching backends without changing code
 */
export class StorageAdapter {
  constructor(backend) {
    this.backend = backend;
  }

  async get(key) {
    return this.backend.get(key);
  }

  async set(key, value) {
    return this.backend.set(key, value);
  }

  async remove(key) {
    return this.backend.remove(key);
  }

  async getAll() {
    return this.backend.getAll();
  }
}

// LocalStorage backend
const localBackend = {
  get: (key) => Promise.resolve(storage.get(key)),
  set: (key, value) => Promise.resolve(storage.set(key, value)),
  remove: (key) => Promise.resolve(storage.remove(key)),
  getAll: () => Promise.resolve(/* ... */)
};

// IndexedDB backend
const idbBackend = {
  store: new IDBStore('app', 'data'),
  get: (key) => this.store.get(key),
  set: (key, value) => this.store.put({ id: key, value }),
  remove: (key) => this.store.delete(key),
  getAll: () => this.store.getAll()
};

// Use same interface
const appStorage = new StorageAdapter(
  'indexedDB' in window ? idbBackend : localBackend
);

Quota Management

/**
 * Check storage quota
 * @returns {Promise<{usage: number, quota: number, percent: number}>}
 */
async function checkStorageQuota() {
  if ('storage' in navigator && 'estimate' in navigator.storage) {
    const { usage, quota } = await navigator.storage.estimate();
    return {
      usage,
      quota,
      percent: Math.round((usage / quota) * 100)
    };
  }
  return { usage: 0, quota: 0, percent: 0 };
}

/**
 * Request persistent storage
 * @returns {Promise<boolean>} - Whether granted
 */
async function requestPersistence() {
  if ('storage' in navigator && 'persist' in navigator.storage) {
    return navigator.storage.persist();
  }
  return false;
}

Sync Patterns

Last-Write-Wins Sync

/**
 * Simple sync with last-write-wins conflict resolution
 */
async function syncWithServer(localData, serverEndpoint) {
  // Get server data
  const serverData = await fetch(serverEndpoint).then(r => r.json());

  // Merge: prefer newer timestamps
  const merged = {};

  for (const item of [...localData, ...serverData]) {
    const existing = merged[item.id];
    if (!existing || item.updatedAt > existing.updatedAt) {
      merged[item.id] = item;
    }
  }

  // Push changes to server
  const localChanges = Object.values(merged).filter(item =>
    !serverData.find(s => s.id === item.id && s.updatedAt >= item.updatedAt)
  );

  if (localChanges.length > 0) {
    await fetch(serverEndpoint, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ items: localChanges })
    });
  }

  return Object.values(merged);
}

Checklist

When implementing data storage:

  • Choose appropriate storage for data size and complexity
  • Wrap storage APIs for testability
  • Handle quota exceeded errors
  • Implement data expiration for caches
  • Add schema versioning for IndexedDB
  • Consider encryption for sensitive data
  • Request persistent storage for important data
  • Implement sync strategy for offline-first
  • Test with storage cleared/disabled
  • Monitor storage usage
  • Provide clear all/reset functionality

Related Skills

  • state-management - Client-side state patterns for Web Components
  • service-worker - Service worker patterns for offline support, caching stra...
  • api-client - Fetch API patterns with error handling, retry logic, and ...
  • security - Write secure web pages and applications