![Deno 2.2 Improves Dependency Management and Expands Node.js Compatibility](https://cdn.sanity.io/images/cgdhsj6q/production/97774ea8c88cc8f4bed2766c31994ebc38116948-1664x1366.png?w=400&fit=max&auto=format)
Security News
Deno 2.2 Improves Dependency Management and Expands Node.js Compatibility
Deno 2.2 enhances Node.js compatibility, improves dependency management, adds OpenTelemetry support, and expands linting and task automation for developers.
@ginger.io/beyonce
Advanced tools
Type-safe DynamoDB query builder for TypeScript. Designed with single-table architecture in mind.
A type-safe DynamoDB query builder for TypeScript.
Beyonce's features include:
Low boilerplate. Define your tables, partitions, indexes and models in YAML and Beyonce codegens TypeScript definitions for you.
Store heterogeneous models in the same table. Unlike most DynamoDB libraries, Beyonce doesn't force you into a 1 model per table paradigm. It supports storing related models in the same table partition, which allows you to "precompute joins" and retrieve those models with a single roundtrip query to the db.
Type-safe API. Beyonce's API is type-safe. It's aware of which models live under your partition and sort keys (even for global secondary indexes).
When you get
, batchGet
or query
, the result types are automatically inferred. And when you apply filters on your
query
the attribute names are automatically type-checked.
Application-level encryption. Beyonce loves Jay-Z and supports him out of the box. Combine them into the power couple they deserve to be, and every non-key, non-index attribute on your models will be automatically encrypted before you send it to Dynamo. This grants an additional layer of security beyond just enabling AWS's DynamoDB server-side-enryption option (which you should do too).
First install beyonce - npm install @ginger.io/beyonce
Define your tables
, partitions
and models
in YAML:
Tables:
Library:
Partitions: # A "partition" is a set of models with the same partition key
Authors: # e.g. this one contains Authors + Books
Author:
partitionKey: [Author, $id]
sortKey: [Author, $id]
id: string
name: string
Book:
partitionKey: [Author, $authorId]
sortKey: [Book, $id]
id: string
authorId: string
name: string
partitionKey
and sortKey
syntaxBeyonce expects you to specify your partition and sort keys as tuple-2s, e.g. [Author, $id]
. The first element is a "key prefix" and the 2nd must be a field on your model. For example we set the primary key of the Author
model above to: [Author, $id]
, would result in the key: Author-$id
, where $id
is the value of a specific Author's id.
Using the example above, if we wanted to place Books
under the same partition key, then we'd need to set the Book
model's partitionKey
to [Author, $authorId]
.
If your table(s) have GSI's you can specify them like this:
Tables:
Library:
Partitions:
...
GSIs:
byName: # must match your GSI's name
partitionKey: $name # name field must exist on at least one model
sortKey: $id # same here
Note: Beyonce currently assumes that your GSI indexes project all model attributes, which will be reflected in the return types of your queries.
You can specify external types you need to import like so:
Author:
...
address: Address from author/Address
Which transforms into import { Address } from "author/address"
npx beyonce --in src/models.yaml --out src/generated/models.ts
Now you can write partition-aware, type safe queries with abandon:
import { Beyonce } from "@ginger.io/beyonce"
import { DynamoDB } from "aws-sdk"
import { LibraryTable } from "generated/models"
const dynamo = new DynamoDB({ endpoint: "...", region: "..."})
const beyonce = new Beyonce(LibraryTable, dynamo)
import {
AuthorModel,
BookModel,
} from "generated/models"
const author = AuthorModel.create({
id: "1",
name: "Jane Austen"
})
await beyonce.put(author)
const author = await beyonce.get(AuthorModel.key({ id: "1" }))
Note: the key prefix
("Author" from our earlier example) will be automatically appeneded.
Beyoncé supports type-safe query
operations that either return a single model type or all model types that live under a given partition key.
You can query
for a single type of model like so:
import { BookModel } from "generated/models"
// Get all Books for an Author
const results = await beyonce
.query(BookModel.partitionKey({ authorId: "1" }))
.exec() // returns { Book: Book[] }
To reduce the amount of data retrieved by DynamoDB, Beyoncé automatically applies a KeyConditionExpression
that uses the sortKey
prefix provided in your model definitions. For example, if the YAML definition for the Book
model contains sortKey:[Book, $id]
-- then the generated KeyConditionExpression
will contain a clause like #partitionKey = :partitionKey AND begins_with(#sortKey, Book)
.
You can also query for all models that live in a partition, like so:
import { AuthorPartition } from "generated/models"
// Get an Author + their books
const results = await beyonce
.query(AuthorPartition.key({ id: "1" }))
.exec() // returns { Author: Author[], Book: Book[] }
Note that, in this case the generated KeyconditionExpression
will not include a clause for the sort key since DynamoDB does not support OR-ing key conditions.
You can filter results from a query like so:
// Get an Author + filter on their books
const authorWithFilteredBooks = await beyonce
.query(AuthorPartition.key({ id: "1" }))
.attributeNotExists("title") // type-safe fields
.or("title", "=", "Brave New World") // type safe fields + operators
.exec()
When you call .exec()
Beyoncé will automatically page through all the results and return them to you. If you would like to step through pages manually (e.g to throttle reads) -- use the .iterator()
method instead:
const iterator = beyonce
.query(AuthorPartition.key({ id: "1" }))
.iterator({ pageSize: 1 })
// Step through each page 1 by 1
for await (const { items } of iterator) {
// ...
}
Each time you call .next()
on the iterator, you'll also get a cursor
back, which you can use to create a new iterator
that picks up where you left off
const iterator1 = beyonce
.query(AuthorPartition.key({ id: "1" }))
.iterator({ pageSize: 1 })
const firstPage = await iterator1.next()
const { items, cursor } = firstPage.value // do something with these
// Later...
const iterator2 = beyonce
.query(AuthorPartition.key({ id: "1" }))
.iterator({ cursor, pageSize: 1 })
const secondPage = await iterator2.next()
import { byNameGSI } from "generated/models"
const prideAndPrejudice = await beyonce
.queryGSI(byNameGSI.name, byNameGSI.key("Jane Austen"))
.where("title", "=", "Pride and Prejudice")
.exec()
// Batch get several items
const batchResults = await beyonce.batchGet({
keys: [
// Get 2 authors
AuthorModel.key({ id: "1" }),
AuthorModel.key({ id: "2" }),
// And a specific book from each
Book.key({ authorId: "1", id: "1" })
Book.key({ authorId: "2" id: "2" })
]
})
// And the return type is:
// { author: Author[], book: Book[] }
// Batch put several items in a transaction
const author1 = AuthorModel.create({
id: "1",
name: "Jane Austen"
})
const author2 = AuthorModel.create({
id: "2",
name: "Charles Dickens"
})
await beyonce.batchPutWithTransaction({ items: [author1, author2] })
Beyonce integrates with Jay-Z to enable transparent application-layer encryption out of the box using KMS with just a few additional lines of code:
import { KMS } from "aws-sdk"
import { KMSDataKeyProvider, JayZ } from "@ginger.io/jay-z"
// Given a dynamo client
const dynamo = new DynamoDB({endpoint: "...", region: "..."})
// Get yourself a JayZ
const kmsKeyId = "..." // the KMS key id or arn you want to use
const keyProvider = new KMSDataKeyProvider(kmsKeyId, new KMS())
const jayZ = new JayZ({ keyProvider })
// And give him to Beyonce (because she runs this relationship)
const beyonce = new Beyonce(
LibraryTable,
dynamo,
{ jayz }
)
Because Jay-Z performs encryption at the application level, DynamoDB query operations
occur before decryption. Put plainly, this means you can't filter .query
calls using any attribute that isn't a partition or sort key.
pk
and sk
When using DynamoDB, you often want to "pre-compute" joins by sticking a set of heterogeneous models into the same table, under the same partition key. This allows for retrieving related records using a single query instead of N.
Unfortunately most existing DynamoDB libraries, like DynamoDBMapper, don't support this use case as they follow the SQL convention sticking each model into a separte table.
For example, we might want to fetch an Author
+ all their Book
s in a single query. And we'd accomplish that by sticking both models
under the same partition key - e.g. author-${id}
.
AWS's guidelines, take this to the extreme:
...most well-designed applications require only one table
Keep in mind that the primary reason they recommened this is to avoid forcing the application-layer to perform in-memory joins. Due to Amazon's scale, they are highly motivated to minimize the number of roundtrip db calls.
You are probably not Amazon scale. And thus probably don't need to shove everything into a single table.
But you might want to keep a few related models in the same table, under the same partition key and fetch those models in a type-safe way. Beyonce makes that easy.
FAQs
Type-safe DynamoDB query builder for TypeScript. Designed with single-table architecture in mind.
The npm package @ginger.io/beyonce receives a total of 3 weekly downloads. As such, @ginger.io/beyonce popularity was classified as not popular.
We found that @ginger.io/beyonce demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 78 open source maintainers collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
Deno 2.2 enhances Node.js compatibility, improves dependency management, adds OpenTelemetry support, and expands linting and task automation for developers.
Security News
React's CRA deprecation announcement sparked community criticism over framework recommendations, leading to quick updates acknowledging build tools like Vite as valid alternatives.
Security News
Ransomware payment rates hit an all-time low in 2024 as law enforcement crackdowns, stronger defenses, and shifting policies make attacks riskier and less profitable.