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 🙏

© 2024 – Pkg Stats / Ryan Hefner

omanyd

v0.8.1

Published

DynamoDB data mapper for nodejs

Downloads

48

Readme

Omanyd

A simple and experimental dynamodb data mapper.

Coverage Status

Features

Missing features

  • Parallel scans
  • Paging
  • Complex querying
  • Number and binary sets
  • Local indexes

Installation

npm: npm install omanyd joi
yarn: yarn add omanyd joi

Both packages come with the necessary types so no need to download anything additional for typescript.

Getting Started

Set the AWS environment variables before running the program

AWS_REGION="REGION" \
AWS_ACCESS_KEY_ID="ACCESS KEY ID" \
AWS_SECRET_ACCESS_KEY="SECRET ACCESS KEY" \
node app.js

These will already by defined for you if you are running in ec2 or lambda.

To host on services like vercel you need to specify your own AWS environment variables but cannot set them using the standard names above. To do this you can specify them using an OMANYD_ prefix so they become:

OMANYD_AWS_REGION="REGION"
OMANYD_AWS_ACCESS_KEY_ID="ACCESS KEY ID"
OMANYD_AWS_SECRET_ACCESS_KEY="SECRET ACCESS KEY"

For running locally we recommend using the official dynamodb docker container and then providing an additional environment variable to override the dynamodb url.

DYNAMODB_URL=http://localhost:8000

Define a Store

Stores are defined through define. You provide the table name, schema and hashKey definition. Stores are the accessors to underlying dymamodb table.

import Omanyd from "omanyd";
import Joi from "joi";

interface Tweet {
  id: string;
  content: string;
}
const TweetStore = Omanyd.define<Tweet>({
  name: "Tweet",
  hashKey: "id",
  schema: Joi.object({
    id: Omanyd.types.id(),
    content: Joi.string(),
  }),
});

Create tables (for testing)

You can create tables for use locally during tests but should be managing this with a proper tool IaC for production.

This expects all stores defined using the define method above before use. It will skip creating a table if it already exists so this cannot be used for modifying a table definition.

import { createTables } from "omanyd";

await createTables();

Delete Tables (for testing)

You can delete tables for use locally during tests but should be managing this with a proper tool IaC for production.

This expects all stores defined using the define method above before use. It then clears all saved definitions so they can be redefined.

import { deleteTables } from "omanyd";

await deleteTables();

Clear Tables (for testing)

You can clear all existing data from known tables by deleting and then redefining the tables. This is a quick function for doing that for you.

import { clearTables } from "omanyd";

await clearTables();

Creating

Once you have defined your store you can create models from it and unless you provide an id then one will be created for you automatically as the omanyd.types.id() was used in the definition above

const tweet = await TweetStore.create({ content: "My first tweet" });
console.log(tweet);
/*
 * { id: "958f2b51-774a-436a-951e-9834de3fe559", content: "My first tweet"  }
 */

Reading one - getting by hash key

Now that we have some data in the store we can now read it. The quickest way is reading directly by the hash key.

const readTweet = await TweetStore.getByHashKey(
  "958f2b51-774a-436a-951e-9834de3fe559"
);
console.log(readTweet);
/*
 * { id: "958f2b51-774a-436a-951e-9834de3fe559", content: "My first tweet"  }
 */

Reading many - items with hash and range key

When an item has a hash and range key then this means you can have multiple items for the one hash key as their range keys are different. To retrieve all of the items for a hash key:

interface EditableTweet {
  id: string;
  version: number;
  content: string;
}
const EditableTweetStore = Omanyd.define<EditableTweet>({
  name: "Tweet",
  hashKey: "id",
  rangeKey: "version",
  schema: Joi.object({
    id: Omanyd.types.id(),
    version: Joi.number(),
    content: Joi.string(),
  }),
});

await Promise.all([
  EditableTweetStore.create({
    id: "958f2b51-774a-436a-951e-9834de3fe559",
    version: 1,
    content: "My tweet",
  }),
  EditableTweetStore.create({
    id: "958f2b51-774a-436a-951e-9834de3fe559",
    version: 2,
    content: "My tweet edited",
  }),
]);

const tweets = await EditableTweetStore.getAllByHashKey("id");
console.log(tweets);
/* [
 *   { id: "958f2b51-774a-436a-951e-9834de3fe559", version: 1, content: "My tweet"  },
 *   { id: "aa6ea347-e3d3-4c73-8960-709fa47e3a4c", version: 2, content: "My tweet edited"  },
 * ]
 */

Reading many - scanning

If we want all of the items in the store we can use a scan. DynamoDB scans come with some interesting caveats.

await Promise.all([
  TweetStore.create({ content: "My second tweet" }),
  TweetStore.create({ content: "My third tweet" }),
  TweetStore.create({ content: "My fourth tweet" }),
]);

const tweets = await TweetStore.scan();

console.log(tweets);
/* [
 *   { id: "958f2b51-774a-436a-951e-9834de3fe559", content: "My first tweet"  },
 *   { id: "aa6ea347-e3d3-4c73-8960-709fa47e3a4c", content: "My second tweet"  },
 *   { id: "9cd6b18a-eafd-49c2-8f0f-d3bf8e75c26e", content: "My third tweet"  },
 *   { id: "fc446fcd-d65a-4ae2-ba9f-6bd94aae8705", content: "My fourth tweet"  }
 * ]
 */

Updating an item - putting

Now that we have saved and read an item lets update it with a new value.

const updatedTweet = await TweetStore.put({
  ...tweet,
  content: "I hope you are having a good day",
});

console.log(updatedTweet);
/*
 * { id: "958f2b51-774a-436a-951e-9834de3fe559", content: "I hope you are having a good day"  }
 */

Deleting an item

Now lets get rid of what we have created.

await TweetStore.deleteByHashKey("958f2b51-774a-436a-951e-9834de3fe559");
const readTweet = await TweetStore.getByHashKey(
  "958f2b51-774a-436a-951e-9834de3fe559"
);
console.log(readTweet);
/*
 * null
 */

Advanced Features

Range keys

It is possible define a composite key for a model so you can have a repeating hash key. This is great for features like versioning. To do this you need to define this range key as part of the definition and then you have access to getByHashAndRangeKey.

import Omanyd from "omanyd";
import Joi from "joi";

interface Document {
  id: string;
  version: string;
  content: string;
}
const DocumentStore = Omanyd.define<User>({
  name: "Documents",
  hashKey: "id",
  schema: Joi.object({
    id: Omanyd.types.id(),
    version: Joi.string().required(),
    email: Joi.string().required(),
  }),
});

// Assuming table has been created separately
const original = await DocumentStore.create({
  id,
  version: "1.0",
  content: "hello",
});
await DocumentStore.create({
  id: original.id,
  version: "2.0",
  content: "hello world",
});

const document = await DocumentStore.getByHashAndRangeKey(id, "2.0");
console.log(document);
/*
 * { id: "e148f2ca-e86d-4c5b-8826-2dbb101a3553", content: "hello world", version: "2.0"  }
 */

Global indexes

It is possible to quickly access documents by keys other than their hash key. This is done through indexes.

Indexes should be created as a part of your table creation but need to be defined with Omanyd so they can be used at run time correctly.

Indexes must have a hash key but can also have a sort key.

import Omanyd from "omanyd";
import Joi from "joi";

interface User {
  id: string;
  email: string;
}
const UserStore = Omanyd.define<User>({
  name: "Users",
  hashKey: "id",
  schema: Joi.object({
    id: Omanyd.types.id(),
    email: Joi.string().required(),
  }),
  indexes: [
    {
      name: "EmailIndex",
      type: "global",
      hashKey: "email",
    },
  ],
});

// Assuming table and index have been created separately

await UserStore.create({ email: "[email protected]" });

const user = await UserStore.getByIndex("EmailIndex", "[email protected]");
console.log(user);
/*
 * { id: "958f2b51-774a-436a-951e-9834de3fe559", email: "[email protected]"  }
 */

Versioning

By default all objects saved with Omanyd get an additional key called _v. This holds the version number of the object so that we can automatically migrate it.

As a part of the options you can provide a field called versions which holds a list of schema and migration functions.

import Omanyd from "omanyd";
import Joi from "joi";

interface User {
  id: string;
  email: string;
}
const UserStore = Omanyd.define<UserV1>({
  name: "Users",
  hashKey: "id",
  schema: Joi.object({
    id: Omanyd.types.id(),
    email: Joi.string().required(),
  }),
});

const user = await UserStore.create({ email: "[email protected]" });
console.log(user);
/*
 * { id: "958f2b51-774a-436a-951e-9834de3fe559", email: "[email protected]" }
 */

Time passes and we need to another version storing more data. We can update it as so:

interface UserV1 {
  id: string;
  email: string;
}
interface UserV2 {
  id: string;
  email: string;
  age: number;
}
const UserStore = Omanyd.define<UserV2>({
  name: "Users",
  hashKey: "id",
  schema: Joi.object({
    id: Omanyd.types.id(),
    email: Joi.string().required(),
    age: Joi.string().required(),
  }),
  versions: [
    {
      schema: Joi.object({
        id: Omanyd.types.id(),
        email: Joi.string().required(),
      }),
      migrate: (userV1: UserV1): UserV2 => {
        return {
          ...userV1,
          age: 2,
        };
      },
    },
  ],
});
// At this point we run the migration defined above. We only run migrations when necessary.
const user = await UserStore.getByHashKey(
  "958f2b51-774a-436a-951e-9834de3fe559"
);
console.log(user);
/*
 * { id: "958f2b51-774a-436a-951e-9834de3fe559", email: "[email protected]", age: 2 }
 */

History

Omanyd was originally inspired by dynamodb and dynamoose

Support

Omanyd is provided as-is, free of charge. For support, you have a few choices: