Skip to content

Instantly share code, notes, and snippets.

@MurylloEx
Created April 16, 2023 02:50
Show Gist options
  • Save MurylloEx/59a5c627f0d5f65b4dbfdc43e47305ae to your computer and use it in GitHub Desktop.
Save MurylloEx/59a5c627f0d5f65b4dbfdc43e47305ae to your computer and use it in GitHub Desktop.
A sample of Repository design pattern in DynamoDB using ElectroDB as library to access the AWS DynamoDB.
import { v4 as uuid } from 'uuid';
import { generate } from 'randomstring';
import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
import { DynamoDBDocumentClient } from '@aws-sdk/lib-dynamodb';
import {
Schema,
Entity,
Item,
AllTableIndexCompositeAttributes,
PutItem
} from 'electrodb';
const DynamoClient = new DynamoDBClient({
endpoint: 'http://localhost:8000',
region: 'us-east-1',
});
const DynamoDocumentClient = DynamoDBDocumentClient.from(DynamoClient);
const Options = {
client: DynamoDocumentClient,
table: 'ShortUrl'
}
export const ShortUrl = new Entity({
model: {
entity: 'short_url',
service: 'shortly',
version: '1'
},
attributes: {
shortId: {
type: 'string',
default: () => uuid()
},
shortCode: {
type: 'string',
default: () => generate(6)
},
realUrl: {
type: 'string',
required: true,
},
accessCount: {
type: 'number',
default: 0,
},
createdAt: {
type: 'number',
readOnly: true,
set: () => Date.now()
},
updatedAt: {
type: 'number',
readOnly: true,
watch: '*',
set: () => Date.now()
},
deletedAt: {
type: 'number',
default: undefined
},
},
indexes: {
primaryKey: {
pk: {
field: 'pk',
composite: ['shortId']
},
sk: {
field: 'sk',
composite: ['shortCode']
}
}
}
}, Options);
export type ElectroSchema = Schema<string, string, string>;
export type ElectroEntity<S extends ElectroSchema> = Entity<string, string, string, S>;
export type ElectroItem<S extends ElectroSchema> = Item<string, string, string, S, S['attributes']>;
export type ElectroPrimaryKey<S extends ElectroSchema> = AllTableIndexCompositeAttributes<string, string, string, S>;
export type ElectroCreateItem<S extends ElectroSchema> = PutItem<string, string, string, S>;
export type InferElectroEntitySchema<E> = E extends ElectroEntity<infer EValue> ? EValue : never;
export abstract class AbstractRepository<E extends ElectroEntity<S>, S extends ElectroSchema = InferElectroEntitySchema<E>> {
constructor(protected readonly entity: E) {}
abstract getAll(): Promise<ElectroItem<S>[]>;
abstract getAllByPage(cursor: string, size: number): Promise<ElectroItem<S>[]>;
abstract getOne(item: Partial<ElectroItem<S>>): Promise<ElectroItem<S>>;
abstract create(item: ElectroCreateItem<S>): Promise<ElectroItem<S>>;
abstract update(item: Partial<ElectroItem<S>> & ElectroPrimaryKey<S>, newItem: Partial<ElectroItem<S>>): Promise<ElectroItem<S>>;
abstract remove(item: Partial<ElectroItem<S>>): Promise<ElectroItem<S>>;
abstract exists(item: Partial<ElectroItem<S>>): Promise<boolean>;
}
export class Repository<E extends ElectroEntity<S>, S extends ElectroSchema = InferElectroEntitySchema<E>> extends AbstractRepository<E, S> {
private constructor(protected readonly entity: E) {
super(entity);
}
public static from<E extends ElectroEntity<S>, S extends ElectroSchema = InferElectroEntitySchema<E>>(entity: E): Repository<E, S> {
return new Repository(entity);
}
async getAll(): Promise<ElectroItem<S>[]> {
const { data } = await this.entity.scan.go();
return data as ElectroItem<S>[];
}
async getAllByPage(cursor: string, size: number): Promise<ElectroItem<S>[]> {
const { data } = await this.entity.scan.go({
cursor,
limit: size
});
return data as ElectroItem<S>[];
}
async getOne(item: Partial<ElectroItem<S>>): Promise<ElectroItem<S>> {
const { data } = await this.entity.match(item).go();
if (!data) {
throw new ReferenceError('The item specified was not found in database');
}
return (Array.isArray(data) ? data[0] : data) as ElectroItem<S>;
}
async create(item: ElectroCreateItem<S>): Promise<ElectroItem<S>> {
const { data } = await this.entity.create(item).go();
return data as ElectroItem<S>;
}
async update(item: Partial<ElectroItem<S>> & ElectroPrimaryKey<S>, newItem: Partial<ElectroItem<S>>): Promise<ElectroItem<S>> {
const { data } = await this.entity.patch(item).set(newItem).go();
return data as ElectroItem<S>;
}
async remove(item: Partial<ElectroItem<S>>): Promise<ElectroItem<S>> {
const oldItem = await this.getOne(item);
await this.entity.delete(oldItem as ElectroPrimaryKey<S>).go();
return oldItem;
}
async exists(item: Partial<ElectroItem<S>>): Promise<boolean> {
const fetchedItem = await this.getOne(item);
return !!fetchedItem;
}
}
(async () => {
const repository = Repository.from(ShortUrl);
const created = await repository.create({
realUrl: 'https://google.com/',
});
console.log('Created item:', created);
const exists = await repository.exists({
realUrl: 'https://google.com/'
});
console.log('The item exists?', exists);
const deleted = await repository.remove({
realUrl: created.realUrl
});
console.log('Deleted item:', deleted);
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment