Last active
March 27, 2023 07:18
-
-
Save andrestone/278ef74b3f2775a4d13abc472f3ea0b2 to your computer and use it in GitHub Desktop.
Revisions
-
andrestone revised this gist
Mar 27, 2023 . 1 changed file with 184 additions and 162 deletions.There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -1,129 +1,184 @@ import * as AWS from "@aws-sdk/client-dynamodb"; import { Entity, Service } from "../../"; const table = "your_table_name"; export const configuration = { endpoint: "http://localhost:8000", region: "us-east-1" }; const client = new AWS.DynamoDB(configuration); const dynamodb = client; const definition = { "KeySchema":[ { "AttributeName":"pk", "KeyType":"HASH" }, { "AttributeName":"sk", "KeyType":"RANGE" } ], "AttributeDefinitions":[ { "AttributeName":"pk", "AttributeType":"S" }, { "AttributeName":"sk", "AttributeType":"S" }, { "AttributeName":"gsi1pk", "AttributeType":"S" }, { "AttributeName":"gsi1sk", "AttributeType":"S" }, { "AttributeName":"gsi2pk", "AttributeType":"S" }, { "AttributeName":"gsi2sk", "AttributeType":"S" }, { "AttributeName":"gsi3pk", "AttributeType":"S" }, { "AttributeName":"gsi3sk", "AttributeType":"S" }, { "AttributeName":"gsi4pk", "AttributeType":"S" }, { "AttributeName":"gsi4sk", "AttributeType":"S" }, { "AttributeName":"gsi5pk", "AttributeType":"S" }, { "AttributeName":"gsi5sk", "AttributeType":"S" } ], "GlobalSecondaryIndexes":[ { "IndexName":"gsi1pk-gsi1sk-index", "KeySchema":[ { "AttributeName":"gsi1pk", "KeyType":"HASH" }, { "AttributeName":"gsi1sk", "KeyType":"RANGE" } ], "Projection":{ "ProjectionType":"ALL" } }, { "IndexName":"gsi2pk-gsi2sk-index", "KeySchema":[ { "AttributeName":"gsi2pk", "KeyType":"HASH" }, { "AttributeName":"gsi2sk", "KeyType":"RANGE" } ], "Projection":{ "ProjectionType":"ALL" } }, { "IndexName":"gsi3pk-gsi3sk-index", "KeySchema":[ { "AttributeName":"gsi3pk", "KeyType":"HASH" }, { "AttributeName":"gsi3sk", "KeyType":"RANGE" } ], "Projection":{ "ProjectionType":"ALL" } }, { "IndexName":"gsi4pk-gsi4sk-index", "KeySchema":[ { "AttributeName":"gsi4pk", "KeyType":"HASH" }, { "AttributeName":"gsi4sk", "KeyType":"RANGE" } ], "Projection":{ "ProjectionType":"ALL" } }, { "IndexName":"gsi5pk-gsi5sk-index", "KeySchema":[ { "AttributeName":"gsi5pk", "KeyType":"HASH" }, { "AttributeName":"gsi5sk", "KeyType":"RANGE" } ], "Projection":{ "ProjectionType":"ALL" } } ], "BillingMode":"PAY_PER_REQUEST" } export function createTableManager() { return { async exists() { let tables = await dynamodb.listTables({}); return !!tables.TableNames?.includes(table); }, async drop() { return dynamodb.deleteTable({TableName: table}); }, async create() { return dynamodb.createTable({...definition, TableName: table}); } } } async function initializeTable() { const tableManager = createTableManager(); const exists = await tableManager.exists(); if (exists) { await tableManager.drop(); } await tableManager.create(); } /* Users Entity */ const users = new Entity( @@ -161,7 +216,8 @@ const users = new Entity( } }, manager: { type: "set", items: ["frank", "jane", "joe", "sally"] as const, }, firstName: { type: "string" @@ -244,65 +300,31 @@ const users = new Entity( { table } ); new Service({ users }, { table, client, }); /* Write queries to generate parameters on the right */ const run = async () => { await initializeTable(); // `create` is like `put` except it uses "attribute_not_exists" // to ensure you do not overwrite a record that already exists users.create({ team: "purple", user: "t.walch", role: "senior", lastName: "walch", firstName: "tyler", manager: ["jane", "frank"], // This fails if Entity is instantiated without a client profile: { bio: "makes things", photo: "selfie.jpg", location: "atlanta" }, // interact with DynamoDB sets like arrays following: ["d.purdy"] }).go(); }; run(); -
andrestone created this gist
Mar 27, 2023 .There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters. Learn more about bidirectional Unicode charactersOriginal file line number Diff line number Diff line change @@ -0,0 +1,308 @@ /* Model queries, see results, share with friends */ import { Entity, Service } from "electrodb"; const table = "your_table_name"; /* Tasks Entity */ const tasks = new Entity( { model: { entity: "tasks", version: "1", service: "taskapp" }, attributes: { team: { type: "string", required: true }, task: { type: "string", required: true }, project: { type: "string", required: true }, user: { type: "string", required: true }, title: { type: "string", required: true, }, description: { type: "string" }, status: { // use an array to type an enum type: ["open", "in-progress", "on-hold", "closed"] as const, default: "open" }, points: { type: "number", }, tags: { type: "set", items: ["green", "red", "blue", "black"] }, comments: { type: "list", items: { type: "map", properties: { user: { type: "string" }, body: { type: "string" } } } }, closed: { type: "string", validate: /[0-9]{4}-[0-9]{2}-[0-9]{2}/, }, createdAt: { type: "number", default: () => Date.now(), // cannot be modified after created readOnly: true }, updatedAt: { type: "number", // watch for changes to any attribute watch: "*", // set current timestamp when updated set: () => Date.now(), readOnly: true } }, indexes: { projects: { pk: { field: "pk", composite: ["team"] }, sk: { field: "sk", // create composite keys for partial sort key queries composite: ["project", "task"] } }, assigned: { // collections allow for queries across multiple entities collection: "assignments", index: "gsi1pk-gsi1sk-index", pk: { // map to your GSI Hash/Partition key field: "gsi1pk", composite: ["user"] }, sk: { // map to your GSI Range/Sort key field: "gsi1sk", composite: ["status"] } }, backlog: { // map to the GSI name on your DynamoDB table index: "gsi2pk-gsi2sk-index", pk: { field: "gsi2pk", composite: ["project"] }, sk: { field: "gsi2sk", composite: ["team", "closed"], } } } }, { table } ); /* Users Entity */ const users = new Entity( { model: { entity: "user", service: "taskapp", version: "1" }, attributes: { team: { type: "string" }, user: { type: "string" }, role: { type: ["dev", "senior", "staff", "principal"] as const, set: (title: string) => { // save as index for comparison return [ "dev", "senior", "staff", "principal" ].indexOf(title); }, get: (index: number) => { return [ "dev", "senior", "staff", "principal" ][index] || "other"; } }, manager: { type: "string", }, firstName: { type: "string" }, lastName: { type: "string" }, fullName: { type: "string", // never set value to the database set: () => undefined, // calculate full name on retrieval get: (_, {firstName, lastName}) => { return `${firstName ?? ""} ${lastName ?? ""}`.trim(); } }, profile: { type: "map", properties: { photo: { type: "string" }, bio: { type: "string" }, location: { type: "string" } } }, pinned: { type: "any" }, following: { type: "set", items: "string" }, followers: { type: "set", items: "string" }, createdAt: { type: "number", default: () => Date.now(), readOnly: true }, updatedAt: { type: "number", watch: "*", set: () => Date.now(), readOnly: true } }, indexes: { members: { collection: "organization", pk: { composite: ["team"], field: "pk" }, sk: { composite: ["user"], field: "sk" } }, user: { collection: "assignments", index: "gsi1pk-gsi1sk-index", pk: { composite: ["user"], field: "gsi1pk" }, sk: { field: "gsi1sk", composite: [] } } } }, { table } ); const app = new Service({ users, tasks }); /* Write queries to generate parameters on the right */ const team = "green"; const user = "d.huynh"; const project = "core"; const task = "45-6620"; // complex objects are supported and typed in ElectroDB const comment = { user: "janet", body: "This seems half-baked." }; // add a comment, tag, and update item's status with a condition tasks .patch({ task, project, team }) .set({ status: "on-hold" }) .add({ tags: ["half-baked"] }) .append({ comments: [comment] }) .where(( {status}, {eq} ) => eq(status, "in-progress")) .go(); const january = "2021-01"; const july = "2021-07"; // sort key query conditions are first class in ElectroDB tasks.query .backlog({ project }) .between( { team, closed: january }, { team, closed: july }, ) .where(({title}, {contains}) => contains(title, "database")) .go({order: 'desc'}); // use a collection to query more than one entity at a time app.collections .assignments({ user }) .where(({ points }, { notExists, between }) => ` ${notExists(points)} OR ${between(points, 1, 5)} `) .go({pages: 'all'}); // `create` is like `put` except it uses "attribute_not_exists" // to ensure you do not overwrite a record that already exists users.create({ team: "purple", user: "t.walch", role: "senior", lastName: "walch", firstName: "tyler", manager: "d.purdy", profile: { bio: "makes things", photo: "selfie.jpg", location: "atlanta" }, // interact with DynamoDB sets like arrays following: ["d.purdy"] }).go();