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

@mantlejs/dynamodb

v0.1.0-experimental

Published

Amazon DynamoDB adapter for Mantle JS — DynamoDbRepository with full QueryParams support

Readme

@mantlejs/dynamodb

Amazon DynamoDB adapter for Mantle JS. Provides a dynamodb() Mantle plugin and an abstract DynamoDbRepository<T> base class that implements the Repository<T> interface using the AWS SDK v3.


Installation

npm install @mantlejs/dynamodb @aws-sdk/client-dynamodb @aws-sdk/util-dynamodb

Concepts

Plugin

dynamodb() is a Mantle plugin. It creates an @aws-sdk/client-dynamodb DynamoDBClient instance and stores it on the application at app.get("dynamodb"). When the application tears down, the client is destroyed automatically.

Repository

DynamoDbRepository<T> implements the Mantle Repository<T> contract against DynamoDB. Extend it with a concrete class, declare tableName, and the full set of methods (findAll, findById, save, saveAll, updateById, patchById, deleteById, count) work out of the box.

The repository uses:

  • GetItem for findById
  • PutItem for save
  • BatchWriteItem for saveAll (auto-chunked at 25 items per DynamoDB limit)
  • UpdateItem with a condition expression for updateById and patchById
  • DeleteItem with a condition expression for deleteById
  • Scan for findAll (falls back to Query when a sort key is configured and the partition key is in the where clause)
  • Scan with SELECT COUNT for count

Query translation

dynamodbify() converts a Mantle QueryParams.where clause into a DynamoDB FilterExpression. Supported operators:

| Operator | DynamoDB expression | | ---------------------------------- | -------------------------- | | { field: value } | #n = :v | | { field: null } | (attribute_not_exists(#n) OR #n = :v) — matches a missing attribute or an explicit DynamoDB NULL-type value | | { field: { $gt } } | #n > :v | | { field: { $gte } } | #n >= :v | | { field: { $lt } } | #n < :v | | { field: { $lte } } | #n <= :v | | { field: { $ne: value } } | #n <> :v | | { field: { $ne: null } } | attribute_exists(#n) | | { field: { $in: [...] } } | #n IN (...) | | { field: [a, b] } | #n IN (...) (shorthand) | | { field: { $nin: [...] } } | NOT (#n IN (...)) | | { field: { $begins: "prefix" } } | begins_with(#n, :v) | | { field: { $contains: "str" } } | contains(#n, :v) | | { $or: [...] } | (expr OR expr) | | { $and: [...] } | (expr AND expr) |

$like is deliberately unsupported (DynamoDB has no wildcard matching) and throws BadRequest.

Note: $contains here maps to DynamoDB's native contains() — set/list membership of a single value, or substring match on strings. This differs from the jsonb @> semantics used by the memory/supabase/knex adapters (no array-operand "contains every element", no object superset).


Quick start

import { mantle } from "@mantlejs/mantle";
import { express } from "@mantlejs/express";
import { dynamodb, DynamoDbRepository } from "@mantlejs/dynamodb";

// ─── Domain entity ─────────────────────────────────────────────────────────

interface Post extends Record<string, unknown> {
  id: string;
  title: string;
  body: string;
  status: "draft" | "published";
}

// ─── Infrastructure ────────────────────────────────────────────────────────

class PostRepository extends DynamoDbRepository<Post> {
  readonly tableName = "posts";
  // partitionKey defaults to "id"

  /** Custom query: find all published posts */
  async findPublished(): Promise<Post[]> {
    return this.findAll({ where: { status: "published" } });
  }
}

// ─── Application ───────────────────────────────────────────────────────────

const app = mantle()
  .configure(express())
  .configure(dynamodb({ region: "us-east-1" }));

const posts = new PostRepository(app);

app.use("/posts", new PostService(posts), {
  methods: ["find", "get", "create", "update", "patch", "remove"],
});

app.listen(3030);

Local development (DynamoDB Local)

const app = mantle().configure(
  dynamodb({
    clientConfig: {
      region: "us-east-1",
      endpoint: "http://localhost:8000",
      credentials: { accessKeyId: "local", secretAccessKey: "local" },
    },
  }),
);

Composite key (partition + sort key)

interface OrderItem extends Record<string, unknown> {
  orderId: string;
  itemId: string;
  quantity: number;
}

class OrderItemRepository extends DynamoDbRepository<OrderItem> {
  readonly tableName = "order-items";
  override readonly partitionKey = "orderId";
  override readonly sortKey = "itemId";
}

// findById with composite key
const item = await repo.findById({ pk: "ORDER#123", sk: "ITEM#456" });

// findAll — Query is used automatically when partitionKey is in the where clause
const items = await repo.findAll({ where: { orderId: "ORDER#123" } });

API

dynamodb(config?)

Returns a MantlePlugin. Call via app.configure(dynamodb(config)).

app.configure(dynamodb({
  region: "us-east-1",           // optional — AWS region
  clientConfig: { ... },         // optional — full DynamoDBClientConfig (overrides region)
}));

Side effects:

  • Stores the DynamoDBClient at app.get("dynamodb")
  • Wraps app.teardown() to call client.destroy() on shutdown

DynamoDbConfig

| Field | Type | Default | Description | | -------------- | ---------------------- | ------- | ------------------------------------------------------------ | | region | string | — | AWS region. Falls back to AWS_REGION env var when omitted. | | clientConfig | DynamoDBClientConfig | — | Full AWS SDK client config. Takes precedence over region. |


DynamoDbRepository<T, D>

Abstract base class. Extend it, declare tableName, and override optional properties.

class UserRepository extends DynamoDbRepository<User> {
  readonly tableName = "users";

  // Optional overrides:
  override readonly partitionKey = "userId";
  override readonly sortKey = "email";
  override readonly timestamps = false;
}

Properties

| Property | Type | Default | Description | | -------------- | ---------------------------------------- | ----------- | ------------------------------------------------------------------------ | | tableName | string | — | DynamoDB table name (required) | | partitionKey | string | "id" | Partition key attribute name | | sortKey | string \| undefined | undefined | Sort key attribute name (composite key tables) | | timestamps | boolean | true | Auto-write createdAt / updatedAt ISO-8601 strings | | lastKey | Record<string, AttributeValue> \| undefined | undefined | Deprecated — use findPage(). LastEvaluatedKey from the most recent paginated findAll() call |

Methods (from Repository<T>)

| Method | Description | | ---------------------- | -------------------------------------------------------------------- | | findAll(params?) | Scan (or Query when PK is in where). Supports skip/limit offsets. | | findPage(params?) | One page via native cursor pagination — see below | | findById(id) | GetItem by partition key (or composite key) | | save(data) | PutItem — auto-generates UUID when no partitionKey is set | | saveAll(data[]) | BatchWriteItem in chunks of 25 — auto-generates UUIDs as needed | | updateById(id, data) | UpdateItem (full replacement, throws NotFound if missing) | | patchById(id, data) | UpdateItem (partial, strips undefined, throws NotFound if missing) | | deleteById(id) | DeleteItem (throws NotFound if missing) | | count(params?) | Scan SELECT COUNT (paginates automatically) | | withTransaction(fn) | Execute writes atomically via TransactWriteItems (100-item limit) |

Cursor pagination

DynamoDB is natively cursor-paginated. Use findPage() — each page carries an opaque cursor (the LastEvaluatedKey, base64-JSON encoded); pass it back to fetch the next page:

const repo = new PostRepository(app);

// First page
const page1 = await repo.findPage({ limit: 20 });

// Next page — pass the cursor from the previous page
if (page1.cursor) {
  const page2 = await repo.findPage({ limit: 20, cursor: page1.cursor });
}

findPage() is stateless — the cursor lives entirely in the returned page, so concurrent calls on one repository instance never interfere. It uses Query when a sortKey is defined and the where clause pins the partition key, otherwise Scan. skip and sort are rejected with BadRequest (offsets and cross-page ordering don't compose with cursors — use findAll() for those). Note that with a filtering where, DynamoDB applies limit before filtering, so a page may hold fewer than limit items while cursor is still set.

Deprecated: the previous mechanism — reading repo.lastKey after findAll() and passing it back as _startKey — still works but shares mutable state on the repository instance. It will be removed in the next minor release.

skip on findAll() performs in-memory offset (scans all pages) — use findPage() for large tables.

Transactions

await repo.withTransaction(async (tx) => {
  await tx.save({ id: "order-1", status: "pending", total: 99.99 });
  await tx.save({ id: "item-1", orderId: "order-1", qty: 2 });
});

In transaction mode, mutation methods return the input data immediately (DynamoDB TransactWriteItems does not support ReturnValues). All writes commit atomically when the callback resolves.


dynamodbify(where)

Converts a Mantle where clause to a DynamoDB FilterExpression. Useful when writing raw SDK commands inside a custom repository method.

import { dynamodbify } from "@mantlejs/dynamodb";

const { expression, names, values } = dynamodbify({
  status: "active",
  age: { $gt: 18 },
});
// expression: "#n0 = :v0 AND #n1 > :v1"

buildKeyCondition(partitionKey, sortKey, where)

Splits a where clause into a KeyConditionExpression and an optional FilterExpression for use in QueryCommand.


Error mapping

AWS SDK errors are translated to typed MantleError subclasses by exception name:

| DynamoDB exception name | Error thrown | | --------------------------------------- | ------------- | | ResourceNotFoundException | NotFound | | ConditionalCheckFailedException | NotFound | | ProvisionedThroughputExceededException, RequestLimitExceeded, ServiceUnavailable | Unavailable | | AccessDeniedException, UnauthorizedException | Forbidden | | ValidationException | BadRequest | | TransactionConflictException, TransactionCanceledException | Conflict | | other | GeneralError |


Types

import type { DynamoDbConfig } from "@mantlejs/dynamodb";
import type { FilterExpression, WhereClause } from "@mantlejs/dynamodb";

| Type | Description | | ------------------ | ---------------------------------------------------------------- | | DynamoDbConfig | Options passed to dynamodb() | | WhereClause | Input type for dynamodbify() and buildKeyCondition() | | FilterExpression | Return type of dynamodbify(){ expression, names, values } |


Development

npx nx build dynamodb     # compile
npx nx test dynamodb      # run tests
npx nx lint dynamodb      # lint

Publishing

Build before publishing:

npx nx build dynamodb

First publish (scoped packages require --access public):

cd packages/dynamodb
npm publish --access public

Subsequent releases — bump version in packages/dynamodb/package.json, then:

cd packages/dynamodb
npm publish

Testing locally with Verdaccio

# Terminal 1 — start the local registry
npx nx run @mantle/source:local-registry

# Terminal 2 — publish to it
cd packages/dynamodb
npm publish --registry http://localhost:4873

# Install from it in another project
npm install @mantlejs/dynamodb --registry http://localhost:4873