@medyll/idbql
TypeScript icon, indicating that this package has built-in type declarations

3.6.0 • Public • Published

idbql - IndexedDB Query Language

idbql is a powerful TypeScript library that provides a MongoDB-like query interface for IndexedDB, simplifying client-side database operations with strong typing and reactive state management.

Table of Contents

  1. Features
  2. Installation
  3. Quick Start
  4. API Reference
  5. Data Modeling
  6. Query Operators
  7. Event Types
  8. Indexing
  9. Transactions
  10. Reactive State Management
  11. Integration with Svelte
  12. Versioning and Migrations
  13. Error Handling
  14. Performance Optimization
  15. Contributing
  16. License

Features

  • MongoDB-like API for IndexedDB queries
  • Strong typing with TypeScript for enhanced developer experience
  • Reactive state management for real-time UI updates
  • Support for complex CRUD operations and advanced querying
  • Flexible and extensible data modeling
  • Built-in indexing and optimization features
  • Easy integration with front-end frameworks, especially Svelte
  • Robust error handling and logging
  • Versioning and database migration support

Installation

Install idbql using npm:

npm install idbql

Quick Start

Here's a basic example to get you started:

import { createIdbqDb } from 'idbql';

// Define your data model
const exampleModel = {
  messages: {
    keyPath: "++id, chatId, created_at",
    ts: {} as ChatMessage,
  },
  chat: {
    keyPath: "&chatId, created_at, dateLastMessage",
    ts: {} as Chat,
    template: {},
  },
};

// Create a database instance
const idbqStore = createIdbqDb(exampleModel, 1);
const { idbql, idbqlState, idbDatabase, idbqModel } = idbqStore.create("myDatabase");

// Perform database operations
async function fetchMessages() {
  const messages = await idbql.messages.where({ chatId: "123" }).toArray();
  console.log(messages);
}

fetchMessages();

API Reference

createIdbqDb

createIdbqDb(model: IdbqModel, version: number)

Creates an IndexedDB database instance with the specified model and version.

  • model: An object describing your database schema
  • version: The version number of your database schema

Returns an object with a create method to initialize the database.

idbql

The main interface for database operations. It provides methods for each collection defined in your model.

Example operations:

// Add a new item
await idbql.messages.add({ chatId: "123", content: "Hello" });

// Update an item
await idbql.messages.put({ id: 1, content: "Updated message" });

// Delete an item
await idbql.messages.delete(1);

// Query items
const recentMessages = await idbql.messages
  .where({ created_at: { gt: new Date(Date.now() - 86400000) } })
  .toArray();

idbqlState

A reactive state object that reflects the current state of your database. It's particularly useful for integrating with reactive UI frameworks.

idbDatabase

Provides low-level access to the IndexedDB instance, allowing for more advanced operations when needed.

idbqModel

Contains the database model definition, useful for introspection and dynamic querying.

Data Modeling

Define your data model using the IdbqModel type:

type IdbqModel = {
  [collectionName: string]: {
    keyPath: string;
    ts: any; // TypeScript type for the collection
    template?: object; // Optional default values
  };
};

Example:

const myModel: IdbqModel = {
  users: {
    keyPath: "++id, email",
    ts: {} as User,
    template: { role: "user" },
  },
  posts: {
    keyPath: "++id, userId, createdAt",
    ts: {} as Post,
  },
};

Query Operators

idbql supports a wide range of query operators:

  • eq: Equal to
  • ne: Not equal to
  • gt: Greater than
  • gte: Greater than or equal to
  • lt: Less than
  • lte: Less than or equal to
  • in: In array
  • nin: Not in array
  • contains: String contains
  • startsWith: String starts with
  • endsWith: String ends with
  • btw: Between (inclusive)

Example usage:

const results = await idbql.users.where({
  age: { gte: 18, lte: 65 },
  name: { startsWith: "A" },
  role: { in: ["admin", "moderator"] },
}).toArray();

Event Types

idbql uses a set of event types to represent different database operations:

/**
 * Represents the types of database operations that can be performed.
 * 
 * @typedef {string} EventType
 * @property {string} add - Add a new record
 * @property {string} put - Put (insert or update) a record
 * @property {string} update - Update an existing record
 * @property {string} updateWhere - Update records that match a condition
 * @property {string} delete - Delete a specific record
 * @property {string} deleteWhere - Delete records that match a condition
 * @property {string} set - Set the value of a record
 */
export type EventType =
  | "add"
  | "put"
  | "update"
  | "updateWhere"
  | "delete"
  | "deleteWhere"
  | "set";

These event types are used internally and can be useful when working with database change events or custom middleware.

Indexing

Indexing is crucial for query performance. Define indexes in your model's keyPath:

const model = {
  users: {
    keyPath: "++id, email, &username, *tags",
    // ...
  },
};
  • ++id: Auto-incrementing primary key
  • email: Regular index
  • &username: Unique index
  • *tags: Multi-entry index for arrays

Transactions

idbql supports complex transactions across multiple object stores, ensuring data consistency and atomicity. Here's how you can use transactions:

Basic Transaction

const result = await idbql.transaction(
  ["users", "posts"],
  "readwrite",
  async (tx) => {
    const userStore = tx.objectStore("users");
    const postStore = tx.objectStore("posts");

    const userId = await userStore.add({ name: "Alice", email: "alice@example.com" });
    const postId = await postStore.add({ userId, title: "Alice's First Post", content: "Hello, World!" });

    return { userId, postId };
  }
);

console.log("New user ID:", result.userId);
console.log("New post ID:", result.postId);

Reactive State Management

idbqlState provides a reactive interface to your data:

import { derived } from 'svelte/store';

const activeUsers = $derived(idbqlState.users.where({ isActive: true }));

Integration with Svelte

Svelte 5 example:

<script>
import { derived } from 'svelte/store';
import { idbqlState } from './store';

const messages = $derived(idbqlState.messages.where({ chatId: "123" }));
</script>

{#each $messages as message}
  <p>{message.content}</p>
{/each}

Versioning and Migrations

Handle database schema changes with versioning:

const idbqStore = createIdbqDb(myModel, 2);
const { idbDatabase } = idbqStore.create("myDb", {
  upgrade(oldVersion, newVersion, transaction) {
    if (oldVersion < 2) {
      const userStore = transaction.objectStore("users");
      userStore.createIndex("emailIndex", "email", { unique: true });
    }
  },
});

Error Handling

idbql provides detailed error information:

try {
  await idbql.users.add({ username: "existing_user" });
} catch (error) {
  if (error instanceof UniqueConstraintError) {
    console.error("Username already exists");
  } else {
    console.error("An unexpected error occurred", error);
  }
}

Performance Optimization

  • Use appropriate indexes
  • Limit result sets with .limit(n)
  • Use .count() instead of .toArray().length
  • Optimize queries to use indexes effectively

Contributing

Contributions are welcome! Please read our Contributing Guide for details on our code of conduct and the process for submitting pull requests.

License

This project is licensed under the MIT License.

Dependencies (0)

    Dev Dependencies (23)

    Package Sidebar

    Install

    npm i @medyll/idbql

    Weekly Downloads

    1

    Version

    3.6.0

    License

    none

    Unpacked Size

    61.3 kB

    Total Files

    25

    Last publish

    Collaborators

    • medyll