Created
April 16, 2023 02:50
-
-
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.
This file contains 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 characters
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