Skip to content

Commit

Permalink
chore: update model return type
Browse files Browse the repository at this point in the history
chore: add getPrimaryKey and fresh method to Model class
  • Loading branch information
joshua-williams committed Mar 17, 2024
1 parent 7fad300 commit 27e11fd
Show file tree
Hide file tree
Showing 21 changed files with 178 additions and 50 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
.idea
/dist
/node_modules
/src/**/*.js
/storage/dynamodb
Expand Down
1 change: 0 additions & 1 deletion __tests__/model.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ describe('model', () => {
fillData.author = undefined;
model.fill(fillData)
const result = model.validate();
console.log(result);
expect(result.valid).toBe(false);
expect(result.errors).toHaveLength(1);
expect(result.errors[0]).toContain('author');
Expand Down
2 changes: 1 addition & 1 deletion dist/src/dynamorm.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export declare class DynamoRM {
* @param modelName
* @param attributes
*/
model(modelName: string, attributes?: Record<string, any>): Model;
model<T>(modelName: string, attributes?: Record<string, any>): Model & T;
}
export declare const create: (App: Function) => IDynamoRM;
declare const _default: {
Expand Down
2 changes: 1 addition & 1 deletion dist/src/dynamorm.d.ts.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion dist/src/dynamorm.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ class DynamoRM {
client;
constructor(DB) {
this.tables = Reflect.getMetadata('tables', DB);
this.models = Reflect.getMetadata('models', DB);
this.models = Reflect.getMetadata('models', DB) || [];
this.client = Reflect.getMetadata('client', DB);
if (!this.tables || !this.client) {
throw new Error('A dynamodb client and tables are required');
Expand Down
4 changes: 4 additions & 0 deletions dist/src/exceptions.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,8 @@ export declare class ServiceUnavailableException extends DynamormException {
export declare class PrimaryKeyException extends DynamormException {
constructor(message: any);
}
export declare class ValidationError extends DynamormException {
messages: string[];
constructor(messages: string[]);
}
//# sourceMappingURL=exceptions.d.ts.map
2 changes: 1 addition & 1 deletion dist/src/exceptions.d.ts.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

10 changes: 9 additions & 1 deletion dist/src/exceptions.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.PrimaryKeyException = exports.ServiceUnavailableException = exports.TableNotFoundException = exports.DynamormException = void 0;
exports.ValidationError = exports.PrimaryKeyException = exports.ServiceUnavailableException = exports.TableNotFoundException = exports.DynamormException = void 0;
class DynamormException extends Error {
constructor(message) {
super(message);
Expand All @@ -25,3 +25,11 @@ class PrimaryKeyException extends DynamormException {
}
}
exports.PrimaryKeyException = PrimaryKeyException;
class ValidationError extends DynamormException {
messages;
constructor(messages) {
super(messages);
this.messages = messages;
}
}
exports.ValidationError = ValidationError;
13 changes: 11 additions & 2 deletions dist/src/model.d.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import { AttributeDefinitions, Attributes } from "./types";
import { AttributeDefinitions, Attributes, PrimaryKey } from "./types";
import { DynamoDBClient, PutItemCommandOutput } from "@aws-sdk/client-dynamodb";
import Table from "./table";
import Entity from "./entity";
declare class Model {
private client;
name: string;
protected table: Table;
protected entity: Entity;
protected attributes: AttributeDefinitions;
readonly primaryKey: PrimaryKey;
constructor(client: DynamoDBClient);
fill(attribute: Attributes | string, value?: any): void;
/**
Expand All @@ -19,11 +22,16 @@ declare class Model {
* @param attributeName
*/
get(attributeName: string): any;
/**
* @description Gets PrimaryKey including values
*/
getPrimaryKey(): PrimaryKey;
getAttributes(): AttributeDefinitions;
getAttributeValues(): {};
getAttributeValues(omitUndefined?: boolean): {};
static getEntity(): any;
getEntity(instance?: boolean): unknown;
save(): Promise<PutItemCommandOutput>;
fresh(): Promise<boolean>;
find(pk?: string, sk?: string): Promise<Model>;
/**
* @description Delete an item by primary key
Expand All @@ -33,6 +41,7 @@ declare class Model {
*/
delete(pk?: string, sk?: string): Promise<any>;
clear(): void;
private validatePrimaryKey;
validate(): {
valid: boolean;
errors: string[];
Expand Down
2 changes: 1 addition & 1 deletion dist/src/model.d.ts.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

72 changes: 58 additions & 14 deletions dist/src/model.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,15 @@ class Model {
client;
name;
table;
entity;
attributes = {};
primaryKey;
constructor(client) {
this.client = client;
this.table = new (Reflect.getMetadata('table', this.constructor));
const entity = this.table.getEntity(true);
this.attributes = entity.getAttributeDefinitions();
this.entity = this.table.getEntity(true);
this.attributes = this.entity.getAttributeDefinitions();
this.primaryKey = this.table.getPrimaryKey();
for (let attribute in this.attributes) {
Object.defineProperty(this, attribute, {
get() {
Expand Down Expand Up @@ -60,10 +63,24 @@ class Model {
}
throw new Error(`Attribute not found: ${attributeName}`);
}
/**
* @description Gets PrimaryKey including values
*/
getPrimaryKey() {
const primaryKeyDefinition = this.table.getPrimaryKeyDefinition();
const primaryKey = {
pk: this.attributes[primaryKeyDefinition.pk.AttributeName].value,
sk: undefined
};
if (primaryKeyDefinition.sk) {
primaryKey.sk = this.attributes[primaryKeyDefinition.sk.AttributeName].value;
}
return primaryKey;
}
getAttributes() {
return this.attributes;
}
getAttributeValues() {
getAttributeValues(omitUndefined = true) {
return Object.keys(this.attributes).reduce((attributes, attribute) => {
attributes[attribute] = this.attributes[attribute].value;
return attributes;
Expand All @@ -76,6 +93,9 @@ class Model {
return this.table.getEntity(instance);
}
async save() {
const { valid, errors } = this.validate();
if (!valid)
throw new exceptions_1.ValidationError(errors);
const input = this.toPutCommandInput();
const command = new client_dynamodb_1.PutItemCommand(input);
let result;
Expand All @@ -99,19 +119,31 @@ class Model {
}
return result;
}
async fresh() {
const { valid, errors } = this.validatePrimaryKey();
if (!valid) {
throw new exceptions_1.PrimaryKeyException(errors[0]);
}
const model = await this.find();
if (model === undefined)
return false;
for (let attributeName in this.attributes) {
if (model.attributes.hasOwnProperty(attributeName)) {
this.attributes[attributeName].value = model.attributes[attributeName].value;
}
}
return true;
}
async find(pk, sk) {
const primaryKeyDefinition = this.table.getPrimaryKeyDefinition();
if (primaryKeyDefinition.sk && !sk) {
throw new exceptions_1.PrimaryKeyException(`Failed to fetch item. Primary key requires partition key and sort key on ${this.table.constructor.name}`);
}
const primaryKey = {
pk: pk || this.table.getPrimaryKey().pk,
sk: sk || this.table.getPrimaryKey().sk,
};
const primaryKeyValues = this.getPrimaryKey();
const input = {
TableName: this.table.getName(),
// @ts-ignore
Key: this.table.toInputKey(primaryKey)
Key: this.table.toInputKey(primaryKeyValues)
};
const command = new client_dynamodb_1.GetItemCommand(input);
let result;
Expand Down Expand Up @@ -169,25 +201,37 @@ class Model {
this.attributes[attributeName].value = undefined;
}
}
validate() {
const errors = [];
validatePrimaryKey() {
const primaryKey = this.table.getPrimaryKey();
const className = this.constructor.name === 'DynamicModel' ? this.entity.constructor.name : this.constructor.name;
const errors = [];
// Validate partition key defined on table is also defined as attribute in respective entity
if (!this.attributes.hasOwnProperty(primaryKey.pk)) {
errors.push(`Partition key "${primaryKey.pk}" is not defined in ${this.getEntity().constructor.name}`);
errors.push(`Partition key "${primaryKey.pk}" is not defined in ${className}`);
}
// Validate partition key defined on table is set in respective entity
if (this.attributes[primaryKey.pk].value === undefined) {
errors.push(`Partition key "${primaryKey.pk}" is not set in ${this.constructor.name}`);
errors.push(`Partition key "${primaryKey.pk}" is not set in ${className}`);
}
// Validate sort key defined on table is also defined as attribute in respective entity
if (primaryKey.sk && !this.attributes.hasOwnProperty(primaryKey.sk)) {
errors.push(`Sort key "${primaryKey.sk}" is not defined in ${this.getEntity().constructor.name}`);
errors.push(`Sort key "${primaryKey.sk}" is not defined in ${className}`);
}
// Validate sort key defined on table is set in respective entity
if (this.attributes[primaryKey.sk].value === undefined) {
if (primaryKey.sk && this.attributes[primaryKey.sk].value === undefined) {
errors.push(`Sort key "${primaryKey.sk}" is not set in ${this.constructor.name}`);
}
return {
valid: !errors.length,
errors
};
}
validate() {
const className = this.constructor.name === 'DynamicModel' ? this.entity.constructor.name : this.constructor.name;
const errors = [];
const result = this.validatePrimaryKey();
if (!result.valid)
errors.push(...result.errors);
for (let attributeName in this.attributes) {
const attribute = this.attributes[attributeName];
// Validate required attribute is set
Expand Down
2 changes: 1 addition & 1 deletion dist/src/table.d.ts.map

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 6 additions & 2 deletions dist/src/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,15 @@ import Entity from "./entity";
import Table from "./table";
import { CreateTableCommandOutput, DynamoDBClient } from "@aws-sdk/client-dynamodb";
import Model from "./model";
export type EntityConstructor = new () => Entity;
export type EntityConstructor = {
name: string;
new (): Entity;
};
export type Entities = Record<string, EntityConstructor>;
export interface EntityAttributes extends Record<string | symbol, any> {
}
export type Attributes = Record<string, any>;
export type DTO = Record<string, any>;
export type AttributeDefinition = {
type: string;
required?: boolean;
Expand Down Expand Up @@ -63,7 +67,7 @@ export type IDynamoRM = {
getTables: () => TableConstructor[];
getModel: (modelName: string) => ModelConstructor;
getModels: () => ModelConstructor[];
model: (modelName: string, attributes?: Record<string, any>) => Model;
model: <T>(modelName: string, attributes?: Record<string, any>) => Model & T;
};
export type DynamoRMOptions = {
tables: Array<any>;
Expand Down
Loading

0 comments on commit 27e11fd

Please sign in to comment.