SkillAgentSearch skills...

Neverchange

NeverChange is a database solution for web applications using SQLite WASM and OPFS.

Install / Use

/learn @shinshin86/Neverchange
About this skill

Quality Score

0/100

Supported Platforms

Universal

README

NeverChange

CI Ask DeepWiki

Project logo

NeverChange is a database solution for web applications using SQLite WASM and OPFS.

👩‍💻 Beta Version Warning 👨‍💻

This package is currently in beta stage. The interface and method names may change with each update. Please use with caution.

Table of Contents

Installation

npm install neverchange

Supported Browsers

All end-to-end tests are conducted with Playwright in Chrome, Edge, Chromium, and Firefox.

About Application Deployment

Our recommendation is to deploy with Netlify for better compatibility. See below for Netlify and GitHub Pages deployment instructions.

A full deployment guide is available in the Deployment Documentation, including specific configuration examples.

Netlify (Recommendation)

Netlify is recommended for deploying apps with persistent data on the web front end using NeverChange.

The following statement in the _headers file can be used for this purpose.

/*  
  Cross-Origin-Opener-Policy: same-origin
  Cross-Origin-Embedder-Policy: require-corp

Cloudflare Pages (Recommendation)

Cloudflare Pages is recommended for deploying apps with persistent data on the web front end using NeverChange.

The following statement in the _headers file can be used for this purpose.

/*
  Cross-Origin-Opener-Policy: same-origin
  Cross-Origin-Embedder-Policy: require-corp
  Cross-Origin-Resource-Policy: same-origin

GitHub Pages

If you want to host on GitHub Pages, you will need coi-serviceworker.js.

But, Safari does not function properly in this case. Refer to the following issue for more details.
https://github.com/shinshin86/neverchange/issues/6

example:

<!DOCTYPE html>
<html>
<head>
  <title>NeverChange Example</title>
  <!-- If you want to host on GitHub, you will need coi-serviceworker.js -->
  <script src="/coi-serviceworker.js"></script>
</head>
<body>
  <div id="root"></div>
  <script type="module" src="/src/main.tsx">
</body>
</html>

A full deployment guide is available in the Deployment Documentation, including specific configuration examples.

Requirements

  • Node.js (version 20 or higher recommended)
  • npm (usually comes with Node.js)

Usage

Usage image

Here’s how to use NeverChange with some practical examples.

If you’re interested in writing SQL efficiently with NeverChange, you may also want to check out sqlc-gen-typescript-for-neverchange, which generates TypeScript code using sqlc.

Basic

Here's a basic example of how to use NeverChangeDB to create a database, insert data, and query it:

import { NeverChangeDB } from 'neverchange';

async function main() {
  // Initialize the database
  const db = new NeverChangeDB('myDatabase');
  await db.init();

  // Create a table
  await db.execute(`
    CREATE TABLE IF NOT EXISTS users (
      id INTEGER PRIMARY KEY AUTOINCREMENT,
      name TEXT NOT NULL,
      email TEXT UNIQUE NOT NULL
    )
  `);

  // Insert data
  await db.execute(
    'INSERT INTO users (name, email) VALUES (?, ?)',
    ['John Doe', 'john@example.com']
  );

  // Query data
  const users = await db.query('SELECT * FROM users');
  console.log('Users:', users);

  // Close the database connection
  await db.close();
}

main().catch(console.error);

Constructor Options

NeverChangeDB constructor accepts an optional second parameter for configuration:

const db = new NeverChangeDB('myDatabase', {
  debug: false,           // Enable debug logging (default: false)
  isMigrationActive: true // Enable automatic migration system (default: true)
});

Options:

  • debug: When set to true, enables detailed logging of database operations. Useful for development and debugging.
  • isMigrationActive: When set to true (default), the migration system is automatically enabled. When false, migrations are disabled and you need to manage schema changes manually.

Example with disabled migrations:

// Database without automatic migration system
const db = new NeverChangeDB('myDatabase', { 
  isMigrationActive: false,
  debug: true 
});
await db.init();

// You'll need to create tables manually
await db.execute(`
  CREATE TABLE IF NOT EXISTS users (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    name TEXT NOT NULL,
    email TEXT UNIQUE NOT NULL
  )
`);

Migration

NeverChangeDB supports database migrations, allowing you to evolve your database schema over time. Here's an example of how to define and use migrations:

import { NeverChangeDB } from 'neverchange';

// Define migrations
const migrations = [
  {
    version: 1,
    up: async (db) => {
      await db.execute(`
        CREATE TABLE users (
          id INTEGER PRIMARY KEY AUTOINCREMENT,
          name TEXT NOT NULL
        )
      `);
    }
  },
  {
    version: 2,
    up: async (db) => {
      await db.execute(`
        ALTER TABLE users ADD COLUMN email TEXT
      `);
    }
  }
];

async function main() {
  // Initialize the database with migrations
  const db = new NeverChangeDB('myDatabase', { isMigrationActive: true });
  db.addMigrations(migrations);
  await db.init();

  // The database will now have the latest schema
  const tableInfo = await db.query('PRAGMA table_info(users)');
  console.log('Users table schema:', tableInfo);

  await db.close();
}

main().catch(console.error);

Transactions

NeverChangeDB supports transaction handling.
When you call db.transaction(async (tx) => { ... }), the statements within the callback are wrapped in a transaction.

  • If the callback completes successfully, the transaction is committed automatically.
  • If an error occurs or if you explicitly call tx.rollback(), the transaction is rolled back.

Basic Example

import { NeverChangeDB } from 'neverchange';

async function main() {
  const db = new NeverChangeDB('myDatabase');
  await db.init();

  await db.execute(`
    CREATE TABLE IF NOT EXISTS accounts (
      id INTEGER PRIMARY KEY AUTOINCREMENT,
      name TEXT NOT NULL,
      balance INTEGER NOT NULL
    )
  `);

  // Perform multiple operations in a single transaction
  await db.transaction(async (tx) => {
    await tx.execute("INSERT INTO accounts (name, balance) VALUES (?, ?)", ["Alice", 1000]);
    await tx.execute("INSERT INTO accounts (name, balance) VALUES (?, ?)", ["Bob", 500]);
    // The transaction is automatically committed when this callback finishes successfully
  });

  const result = await db.query("SELECT * FROM accounts");
  console.log(result);

  await db.close();
}

main().catch(console.error);

Explicit Rollback

You can call tx.rollback() at any point within the transaction callback to force an immediate rollback. rollback() throws an error internally, which interrupts the rest of the transaction logic.

await db.transaction(async (tx) => {
  const [account] = await tx.query<{ balance: number }>(
    "SELECT balance FROM accounts WHERE name = ?",
    ["Alice"]
  );

  if (account.balance < 100) {
    // If there's not enough balance, roll back the transaction here
    await tx.rollback();
  }

  await tx.execute("UPDATE accounts SET balance = balance - 100 WHERE name = ?", ["Alice"]);
  await tx.execute("UPDATE accounts SET balance = balance + 100 WHERE name = ?", ["Bob"]);
});

Nested Transactions (Savepoints)

NeverChangeDB also supports nested transactions. When db.transaction is called inside another transaction, it creates a new savepoint, so an inner failure rolls back only the nested portion.

await db.transaction(async (tx) => {
  // Insert within top-level transaction
  await tx.execute("INSERT INTO accounts (name, balance) VALUES (?, ?)", ["Charlie", 200]);

  try {
    // Begin a nested transaction (savepoint)
    await tx.transaction(async (tx2) => {
      await tx2.execute("INSERT INTO accounts (name, balance) VALUES (?, ?)", ["David", 300]);
      // Force an error in the nested transaction
      throw new Error("Error in nested transaction!");
    });
  } catch (err) {
    console.warn("Nested transaction error:", err);
    // Only the nested changes are rolled back
  }

  // Outer transaction can still proceed
  await tx.execute("INSERT INTO accounts (name, balance) VALUES (?, ?)", ["Eve", 400]);
});

In the above scenario, the insert for “David” will be rolled back due to the error in the nested transaction, but the outer transaction remains valid and will commit the other statements unless it encounters its own error.

Returning Values

If your callback returns a value, that value is also ret

View on GitHub
GitHub Stars13
CategoryData
Updated1mo ago
Forks0

Languages

TypeScript

Security Score

95/100

Audited on Feb 10, 2026

No findings