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

@x-sls/dynamodb-users

v1.2.0

Published

Helpers for managing users in DynamoDB: create, update, get by ID or email, and list users by lookup list (e.g. active/inactive). The library never creates a DynamoDB client; you pass in a DocumentClient and table name.

Readme

@x-sls/dynamodb-users

Helpers for managing users in DynamoDB: create, update, get by ID or email, and list users by lookup list (e.g. active/inactive). The library never creates a DynamoDB client; you pass in a DocumentClient and table name.

Table requirements

  • Base table: partition key pk (String), sort key sk (String).
  • GSI (default name gsi1): partition key pk1 (String), sort key sk1 (String).

If your table uses different key or index names, override them via options.schema (see Schema).

Data model

Each user is represented by:

  1. User item — Main record: pk = U#<uuid>, sk = A, plus pk1/sk1 for email lookup on the GSI (e.g. pk1 = email#<normalized>, sk1 = A). Get by ID via base table; get by email via GSI query.
  2. Email item — Sentinel for by-email lookup when the GSI is eventually consistent: pk = EMAIL#<normalized>, sk = A, userId. The library queries the GSI first, then falls back to this item + getUser.
  3. LOOKUP item (optional) — For list queries: pk = U#<id>, sk = LOOKUP, pk1 = <lookupList> (e.g. USER#ACTIVE), sk1 = <normalized name>. Projected attributes (e.g. email, name, picture, userId). Created/updated when createLookupItem / updateLookupItem are not set to false.

Installation

npm install @x-sls/dynamodb-users

Peer dependency: @aws-sdk/lib-dynamodb (DocumentClient). You must provide your own DynamoDB client and pass it via options.

API

All functions take an options object that must include documentClient and tableName. Optional schema overrides the default key/index names and prefixes.

getUser(userId, options)

Get a user by ID (with or without U# prefix).

  • Returns: User item or null.
  • Options: documentClient, tableName (required); schema (optional).
const user = await getUser('abc-123', { documentClient, tableName });
const user2 = await getUser('U#abc-123', { documentClient, tableName });

getUserByEmail(email, options)

Get a user by email. Queries the GSI first; if empty, falls back to the EMAIL sentinel and then getUser.

  • Returns: User item or null.
  • Options: documentClient, tableName (required); schema (optional; must have lookupIndex for by-email); normalizeEmailFn (optional, default: lowercased trim).
const user = await getUserByEmail('[email protected]', { documentClient, tableName });

createUser(input, options)

Create a user atomically: user item + email sentinel. By default also writes a LOOKUP item so the user can be returned by listUsers (opt-out with createLookupItem: false).

  • Input: email, name (required). Other keys (e.g. picture, status) are stored on the user item.
  • Returns: The created user item.
  • Throws: "User with this email already exists" on duplicate email (transaction conditional check).
  • Options: documentClient, tableName (required); createLookupItem (default true); lookupList (optional, default from schema.defaultLookupList e.g. USER#ACTIVE); lookupExtraAttributes (array of attribute names to add to LOOKUP); extraTransactItems, normalizeEmailFn, schema (optional).
const user = await createUser(
  { email: '[email protected]', name: 'Alice' },
  { documentClient, tableName }
);
// With custom list
await createUser(
  { email: '[email protected]', name: 'Bob' },
  { documentClient, tableName, lookupList: 'USER#INACTIVE' }
);

updateUser(userId, updates, options)

Update a user. Writes a version record (if createVersionRecord is true), updates the user item, and on email change updates/deletes/puts email sentinels. By default also (re)writes the LOOKUP item (opt-out with updateLookupItem: false).

  • Returns: Updated user item.
  • Throws: "User not found", or "User with this email already exists" if email is changed to an existing one.
  • Options: documentClient, tableName, modifiedBy, modifiedReason (required); createVersionRecord (default true); updateLookupItem (default true); lookupList (optional); lookupExtraAttributes; allowedUpdateFields (if set, only these keys are updated); extraTransactItems, normalizeEmailFn, schema (optional).
await updateUser(
  userId,
  { name: 'Alice Smith' },
  { documentClient, tableName, modifiedBy: 'admin', modifiedReason: 'name change' }
);
// Move to inactive list
await updateUser(
  userId,
  {},
  { documentClient, tableName, modifiedBy: 'admin', modifiedReason: 'deactivate', lookupList: 'USER#INACTIVE' }
);

listUsers(options)

List users by querying the LOOKUP index (GSI). Only returns users for whom LOOKUP items exist (created/updated by this library with LOOKUP enabled). If you never create LOOKUP items, listUsers will return empty results; implement your own list logic in that case.

  • Returns: { items: object[], lastEvaluatedKey: object | null }. Each item is a LOOKUP row (projected attributes + userId). Use lastEvaluatedKey for pagination.
  • Options: documentClient, tableName, lookupList (required, e.g. 'USER#ACTIVE'); beginsWith (optional, prefix match on normalized name); limit (default 50, max 100); exclusiveStartKey, scanIndexForward; schema (optional).
const { items, lastEvaluatedKey } = await listUsers({
  documentClient,
  tableName,
  lookupList: 'USER#ACTIVE'
});
// Pagination
const next = await listUsers({
  documentClient,
  tableName,
  lookupList: 'USER#ACTIVE',
  exclusiveStartKey: lastEvaluatedKey
});
// Search by name prefix (normalized)
const { items } = await listUsers({
  documentClient,
  tableName,
  lookupList: 'USER#ACTIVE',
  beginsWith: 'alice'
});

LOOKUP behavior (opt-out)

  • createUser: Creates a LOOKUP item by default (createLookupItem defaults to true), so new users appear in the default list (e.g. USER#ACTIVE) and can be listed. Set createLookupItem: false to skip LOOKUP.
  • updateUser: Updates the LOOKUP item by default (updateLookupItem defaults to true). Set updateLookupItem: false to skip LOOKUP.
  • listUsers: Only returns items that match the LOOKUP pattern (same GSI, pk1 = lookupList). If you opt out of LOOKUP on create/update, you must implement your own listing (e.g. Scan or custom GSI).

Schema

The library uses a default schema (exported as DEFAULT_SCHEMA). Override specific keys by passing options.schema; it is merged over the default.

| Key | Default | Description | |-----|---------|-------------| | userItemPK | 'pk' | Base table partition key attribute name | | userItemSK | 'sk' | Base table sort key attribute name | | userItemPrefix | 'U#' | Prefix for user item pk | | userItemLookupPrefix | 'email#' | Prefix for user item GSI pk1 (email lookup) | | userItemSKValue | 'A' | User item sk value | | userItemLookupSKValue | 'A' | User item GSI sk1 value for email lookup | | lookupIndex | 'gsi1' | GSI name | | lookupIndexPK | 'pk1' | GSI partition key attribute | | lookupIndexSK | 'sk1' | GSI sort key attribute | | lookupItemSK | 'LOOKUP' | LOOKUP item sk value | | defaultLookupList | 'USER#ACTIVE' | Default list for new users (LOOKUP pk1) | | emailItemPrefix | 'EMAIL#' | Email sentinel pk prefix | | emailItemUserAttribute | 'userId' | Attribute name for user ID on email/LOOKUP items |

Example: use U# for the email lookup key instead of email#:

const user = await getUserByEmail('[email protected]', {
  documentClient,
  tableName,
  schema: { userItemLookupPrefix: 'U#' }
});

Example: full flow

const { createUser, getUser, getUserByEmail, updateUser, listUsers } = require('@x-sls/dynamodb-users');
const { DynamoDBClient } = require('@aws-sdk/client-dynamodb');
const { DynamoDBDocument } = require('@aws-sdk/lib-dynamodb');

const client = new DynamoDBClient({ region: 'us-east-1' });
const documentClient = DynamoDBDocument.from(client);
const tableName = process.env.USERS_TABLE;

const opts = { documentClient, tableName };

// Create (includes LOOKUP on USER#ACTIVE by default)
const user = await createUser({ email: '[email protected]', name: 'Alice' }, opts);

// Get by ID or email
const byId = await getUser(user.pk, opts);
const byEmail = await getUserByEmail('[email protected]', opts);

// List active users
const { items } = await listUsers({ ...opts, lookupList: 'USER#ACTIVE' });

// Mark inactive
await updateUser(user.pk, {}, { ...opts, modifiedBy: 'system', modifiedReason: 'deactivate', lookupList: 'USER#INACTIVE' });

// List inactive users
const { items: inactive } = await listUsers({ ...opts, lookupList: 'USER#INACTIVE' });

Tests

npm test

Uses Node's built-in test runner (node --test).