npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

asljs-dali

v0.1.4

Published

IndexedDB data layer with a typed Table abstraction.

Readme

dali

Part of Alexandrite Software Library - a set of high-quality, performant JavaScript libraries for everyday use.

Overview

asljs-dali is a data layer for apps that store data in IndexedDB. It is for developers who want a typed, event-aware table abstraction instead of hand-writing low-level request and transaction plumbing. Use it to model stores as Table<T>, keep CRUD operations consistent, and optionally enforce optimistic concurrency with version strategies.

Installation

npm install asljs-dali

NPM Package: asljs-dali

Package Concept Map

  • dbOpen(...), dbDelete(...), and dbRequestAsync(...) manage database setup and low-level request handling.
  • Table<T> is the main high-level abstraction for typed IndexedDB work.
  • notify(...) and observe(...) handle committed change notifications.
  • record(...) and recordset(...) provide live-first containers.
  • Transaction helpers support lower-level control when Table<T> is not the right layer.
  • Version and delete strategies customize concurrency and deletion behavior.

Choose This API When

  • If you need a one-time single-row read, then use getOne(key).
  • If you need a one-time filtered scan, then use scan(predicate).
  • If you need live single-row tracking, then use record(key).
  • If you need live filtered tracking, then use recordset(predicate).
  • If you need local-only mutation notifications, then use notify(...).
  • If you need local-plus-remote committed notifications, then use observe(...).

Public Contracts

  • notify(...) is local-only.
  • observe(...) includes local and remote committed changes.
  • Broadcasts happen only after a successful commit.
  • Remote messages are not re-published.
  • record(key) is key-based only.
  • recordset(predicate) is client-side predicate filtering only.

What This Package Does Not Provide

  • No joins.
  • No server-style query planners.
  • No DB-level query composition through recordset(...).
  • No automatic ordering semantics for live sets.
  • No re-publishing of remote messages.

Public Surface Summary

DB helpers:

  • dbOpen
  • dbDelete
  • dbRequestAsync

Tables and live views:

  • Table
  • LiveRecord
  • LiveRecordSet

Version and delete strategies:

  • IncrementTableVersionStrategy
  • UuidTableVersionStrategy
  • TableVersionStrategy
  • TableVersionConflictError
  • TableDeleteStrategy
  • UuidSoftDeleteTableDeleteStrategy

Transaction helpers:

  • txRead
  • txWrite
  • txDone
  • txEnsure
  • txReuseOrCreate
  • TxMode

Broadcast and observe types:

  • TableBroadcastService
  • TableBroadcastMessage
  • TableObservedEvent
  • TableObservedReceiver

Event-source and saga helpers:

  • EventSourceManager
  • IndexedDbEventSourceAdapter
  • EventSourceProjectionManager
  • SagaManager
  • setup and store helper exports from event-source and saga modules

Usage

import {
    dbOpen,
    Table,
  } from 'asljs-dali';

type Note =
  { id: string;
    title: string; };

const db =
  await dbOpen(
    'notes-db',
    [ targetDb => {
        targetDb.createObjectStore(
          'notes',
          { keyPath: 'id' });
      } ]);

const notes =
  new Table<Note>(
    'notes',
    db,
    { /* options */ });

await notes.add(
  { id: '1',
    title: 'Hello' });

const row =
  await notes.getOne('1');

Cross-tab notifications with observe()

Table supports two notification paths:

  • If you want callbacks only for writes committed by this Table instance, then use notify(receiver).
  • If you want callbacks for local writes and remote writes from other tabs, then use observe(receiver).

Pass a broadcastService to the Table constructor to enable cross-tab delivery. The service is an abstraction — you can implement it with BroadcastChannel or any equivalent transport.

import {
    type TableBroadcastMessage,
    type TableBroadcastService,
  } from 'asljs-dali';

// BroadcastChannel-backed implementation
function makeBroadcastService(
    channelName: string
  ): TableBroadcastService
{
  const channel = new BroadcastChannel(channelName);

  return {
    publish(message: TableBroadcastMessage) {
      channel.postMessage(message);
    },
    subscribe(handler) {
      const listener = (ev: MessageEvent) => handler(ev.data);
      channel.addEventListener('message', listener);
      return () => channel.removeEventListener('message', listener);
    },
  };
}

const notes =
  new Table<Note>(
    'notes',
    db,
    { broadcastService: makeBroadcastService('notes-sync') });

// Local-only — fires only for writes made by this Table instance.
notes.notify(
  { add(record) { console.log('local add', record); } });

// Observed — fires for both local and remote writes.
// The `source` field tells you where the change came from.
const unobserve =
  notes.observe(event => {
    console.log(event.source, event.eventType);
    if (event.eventType === 'add')
      console.log(event.record);
  });

// When the Table is no longer needed, dispose it to stop listening.
notes.dispose();

Design rules:

  • Broadcast messages are published only after a successful IndexedDB transaction; rolled-back or provisional changes are never broadcast.
  • A Table instance discards its own echoed messages using a per-instance originId included in every broadcast message.
  • Remote messages are routed only to observe() subscribers; local-only notify() subscribers are never called for remote events.
  • A Table receiving a remote message does not re-publish it, preventing broadcast loops.

Live views with record() and recordset()

Table provides live-first APIs that return reactive containers tracking committed table changes automatically. Both containers are built on ASLJS eventful (for domain events) and ASLJS observable (for property-path watching).

Table.record(key)LiveRecord<T>

Returns a live single-record view for a specific primary key.

const live = notes.record('1');

// Stable property — null until the initial load settles.
console.log(live.record); // { id: '1', title: 'Hello' } | null

// Domain events via ASLJS eventful.
live.on('changed', (record, previous) => {
  console.log('record changed to', record, 'was', previous);
});

live.on('deleted', previous => {
  console.log('record deleted, was', previous);
});

// Property-path watching via ASLJS observable.
live.watch('record.title', title => {
  console.log('title is now', title);
});

// Release the live view when no longer needed.
live.dispose();

Behaviour:

  • record is null until the initial database read settles.
  • On add / update for the tracked key — record is updated and changed fires.
  • On delete or clearrecord becomes null and deleted fires.
  • Unrelated changes on the same table do not affect this view.
  • watch(path, cb) is called immediately with the current value and again whenever the path changes. Watchers are anchored to the stable container.

Snapshot read: use table.getOne(key) instead.

Limitation: record(key) is limited to key-only semantics.

Table.recordset(predicate)LiveRecordSet<T>

Returns a live filtered set view for records matching a client-side predicate.

const live = notes.recordset(note => note.title.startsWith('A'));

// Stable property — a readonly array snapshot.
console.log(live.records); // readonly Note[]

// Domain events via ASLJS eventful.
live.on('added',   record          => console.log('added',   record));
live.on('removed', record          => console.log('removed', record));
live.on('updated', (record, prev)  => console.log('updated', record, prev));
live.on('cleared', ()              => console.log('cleared'));
live.on('changed', records         => console.log('set now has', records.length));

// Property-path watching via ASLJS observable.
live.watch('records', records => {
  console.log('count:', (records as readonly Note[]).length);
});

live.dispose();

Behaviour:

  • On initial creation the table is scanned and all matching records are loaded.
  • On add — the record is included if the predicate returns true; added fires.
  • On update — membership is re-evaluated; added, updated, or removed fires accordingly.
  • On delete — the record is removed if it was present; removed fires.
  • On clear — the set is emptied and cleared fires.
  • changed fires after every mutation.

Snapshot read: use table.scan(predicate) instead.

Limitation: recordset(predicate) is limited to client-side predicate semantics. Joins, ordering, and DB-level query composition are not supported.

API Reference

Core:

  • dbOpen(name, upgrades)
  • dbDelete(name)
  • dbRequestAsync(request)
  • Table<T>

Live views:

  • LiveRecord<T> — live single-record container returned by Table.record(key)
    • Events (ASLJS eventful): changed, deleted
    • Watch (ASLJS observable): record.someField
  • LiveRecordSet<T> — live filtered set container returned by Table.recordset(predicate)
    • Events (ASLJS eventful): added, removed, updated, cleared, changed
    • Watch (ASLJS observable): records, records.length
  • LiveRecordEvents<T> — event map type for LiveRecord
  • LiveRecordSetPayload<T>set:record / set event payload for LiveRecord
  • LiveRecordSetEvents<T> — event map type for LiveRecordSet
  • LiveRecordSetSetPayload<T>set:records / set event payload for LiveRecordSet

Versioning:

  • TableVersionStrategy<T>
  • TableVersionConflictError
  • IncrementTableVersionStrategy<T>
  • UuidTableVersionStrategy<T>

Delete strategies:

  • TableDeleteStrategy<T>
  • UuidSoftDeleteTableDeleteStrategy<T>

Transactions:

  • TxMode
  • txRead(db, storeName, tx?)
  • txWrite(db, storeName, tx?)
  • txDone(tx)
  • txEnsure(tx, storeName, mode)
  • txReuseOrCreate(tx, storeNames, mode, db)

Broadcast / cross-tab:

  • TableBroadcastService — interface for the publish/subscribe transport
  • TableBroadcastMessage — message shape published on every committed change
  • TableObservedEvent<T> — event delivered to observe() subscribers
  • TableObservedReceiver<T> — callback type for observe()

Common Wrong Assumptions

  • recordset(predicate) is a database query planner.
  • notify(...) includes remote tab changes.
  • observe(...) re-broadcasts remote changes.
  • live views imply joins or rich query composition.
  • broadcast delivery happens during tentative mutations instead of after commit.

Related Packages

  • For event primitives, see asljs-eventful.
  • For path watching and reactive property access, see asljs-observable.
  • For DOM binding on top of observable models, see asljs-data-binding.

Safe Usage Rules

  • Use Table<T> before dropping to raw transaction helpers.
  • Prefer snapshot reads unless reactivity is actually needed.
  • Use observe(...) only when remote-origin changes matter.
  • Dispose live views when they are no longer needed.
  • Do not describe recordset(predicate) as a full query engine.

License

MIT License. See LICENSE for details.