Skip to content

Commit

Permalink
Release 4.0.0-beta.1. Added FluentDynameh.transactWriteItems. Made re…
Browse files Browse the repository at this point in the history
…questBuilder.addProjection, requestBuilder.addCondition, requestBuilder.addFilter mutate state.
  • Loading branch information
jeff committed Apr 1, 2019
1 parent c498446 commit 9b4881a
Show file tree
Hide file tree
Showing 4 changed files with 134 additions and 103 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "dynameh",
"version": "3.3.0-beta.5",
"version": "4.0.0-beta.1",
"description": "DynamoDB on Node more easier",
"main": "dist/index.js",
"types": "dist/index.d.ts",
Expand Down
68 changes: 55 additions & 13 deletions src/FluentDynameh.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,12 @@ export class FluentRequestBuilder<TRequest, TResponse, TResult> {

private response: TResponse & { getResult: () => TResult };

constructor(public tableSchema: TableSchema, public request: TRequest, public executor: (param: TRequest) => aws.Request<TResponse, aws.AWSError>, public resultGetter: (resp: TResponse) => TResult) {
constructor(
public readonly tableSchema: TableSchema,
public readonly request: TRequest,
private readonly executor: (param: TRequest) => aws.Request<TResponse, aws.AWSError>,
private readonly resultGetter: (resp: TResponse) => TResult
) {
}

async execute(): Promise<TResponse & { getResult: () => TResult }> {
Expand All @@ -28,26 +33,64 @@ export class FluentRequestBuilder<TRequest, TResponse, TResult> {
return this.resultGetter(await this.execute());
}

addProjection(attributes: string[]): FluentRequestBuilder<TRequest, TResponse, Partial<TResult>> {
const req = requestBuilder.addProjection(this.tableSchema, this.request, attributes);
return new FluentRequestBuilder(this.tableSchema, req, this.executor, this.resultGetter);
addProjection(attributes: string[]): this {
requestBuilder.addProjection(this.tableSchema, this.request, attributes);
return this;
}

addCondition(...conditions: Condition[]): FluentRequestBuilder<TRequest, TResponse, TResult> {
const req = requestBuilder.addCondition(this.tableSchema, this.request, ...conditions);
return new FluentRequestBuilder(this.tableSchema, req, this.executor, this.resultGetter);
addCondition(...conditions: Condition[]): this {
requestBuilder.addCondition(this.tableSchema, this.request, ...conditions);
return this;
}

addFilter(...filter: Condition[]): FluentRequestBuilder<TRequest, TResponse, TResult> {
const req = requestBuilder.addFilter(this.tableSchema, this.request, ...filter);
return new FluentRequestBuilder(this.tableSchema, req, this.executor, this.resultGetter);
addFilter(...filter: Condition[]): this {
requestBuilder.addFilter(this.tableSchema, this.request, ...filter);
return this;
}
}

export class FluentTransactWriteItemsBuilder<T extends object> {

public readonly request: aws.DynamoDB.TransactWriteItemsInput;
private response: aws.DynamoDB.TransactWriteItemsOutput;

constructor(public readonly tableSchema: TableSchema, public readonly client: aws.DynamoDB) {
}

async execute(): Promise<aws.DynamoDB.TransactWriteItemsOutput> {
if (!this.response) {
this.response = await this.client.transactWriteItems(this.request).promise();
}
return this.response;
}

putItem(item: T): FluentRequestBuilder<aws.DynamoDB.PutItemInput, void, {}> {
const req = requestBuilder.buildPutInput(this.tableSchema, item);
requestBuilder.addTransactWriteItemsInput(this.request, req);
return new FluentRequestBuilder(this.tableSchema, req, notExecutable, emptyResultGetter);
}

updateItemFromActions(itemToUpdate: object, ...updateActions: UpdateExpressionAction[]): FluentRequestBuilder<aws.DynamoDB.UpdateItemInput, void, {}> {
const req = requestBuilder.buildUpdateInputFromActions(this.tableSchema, itemToUpdate, ...updateActions);
requestBuilder.addTransactWriteItemsInput(this.request, req);
return new FluentRequestBuilder(this.tableSchema, req, notExecutable, emptyResultGetter);
}

deleteItem(itemToDelete: Partial<T>): FluentRequestBuilder<aws.DynamoDB.DeleteItemInput, void, {}> {
const req = requestBuilder.buildDeleteInput(this.tableSchema, itemToDelete);
requestBuilder.addTransactWriteItemsInput(this.request, req);
return new FluentRequestBuilder(this.tableSchema, req, notExecutable, emptyResultGetter);
}
}

function emptyResultGetter(): {} {
return {};
}

function notExecutable(): any {
throw new Error("This builder is not executable because it is part of a transaction.");
}

export class FluentDynameh<T extends object> {

constructor(public tableSchema: TableSchema, public client: aws.DynamoDB) {
Expand Down Expand Up @@ -86,9 +129,8 @@ export class FluentDynameh<T extends object> {
// TODO executor should get ALL scan results with paging
}

transactWriteItems(...input: FluentRequestBuilder<aws.DynamoDB.PutItemInput | aws.DynamoDB.DeleteItemInput | aws.DynamoDB.UpdateItemInput, any, {}>[]): FluentRequestBuilder<aws.DynamoDB.TransactWriteItemsInput, aws.DynamoDB.TransactWriteItemsOutput, {}> {
const req = requestBuilder.buildTransactWriteItemsInput(...input.map(i => i.request));
return new FluentRequestBuilder(this.tableSchema, req, this.client.transactWriteItems, emptyResultGetter);
transactWriteItems(): FluentTransactWriteItemsBuilder<T> {
return new FluentTransactWriteItemsBuilder(this.tableSchema, this.client);
}

createTable(additionalTableSchemas: TableSchema[] = [], readCapacity?: number, writeCapacity?: number): FluentRequestBuilder<aws.DynamoDB.CreateTableInput, aws.DynamoDB.CreateTableOutput, {}> {
Expand Down
83 changes: 35 additions & 48 deletions src/requestBuilder.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1079,10 +1079,9 @@ describe("requestBuilder", () => {

it("adds a projection to get input", () => {
const input = buildGetInput(defaultTableSchema, "prim", "so");
const projectedInput = addProjection(defaultTableSchema, input, ["key", "lock"]);
addProjection(defaultTableSchema, input, ["key", "lock"]);

chai.assert.notEqual(input, projectedInput);
chai.assert.deepEqual(projectedInput, {
chai.assert.deepEqual(input, {
TableName: "table",
Key: {
primary: {S: "prim"},
Expand All @@ -1098,12 +1097,10 @@ describe("requestBuilder", () => {

it("adds a projection to an already projected expression", () => {
const input = buildGetInput(defaultTableSchema, "prim", "so");
const projectedInput = addProjection(defaultTableSchema, input, ["key", "lock"]);
const projectedInput2 = addProjection(defaultTableSchema, projectedInput, ["key", "value"]);
addProjection(defaultTableSchema, input, ["key", "lock"]);
addProjection(defaultTableSchema, input, ["key", "value"]);

chai.assert.notEqual(input, projectedInput);
chai.assert.notEqual(projectedInput, projectedInput2);
chai.assert.deepEqual(projectedInput2, {
chai.assert.deepEqual(input, {
TableName: "table",
Key: {
primary: {S: "prim"},
Expand All @@ -1120,10 +1117,9 @@ describe("requestBuilder", () => {

it("adds a projection to query input without key values", () => {
const input = buildQueryInput(defaultTableSchema, "mah value", "BETWEEN", "alpha", "beta");
const projectedInput = addProjection(defaultTableSchema, input, ["key", "lock"]);
addProjection(defaultTableSchema, input, ["key", "lock"]);

chai.assert.notEqual(input, projectedInput);
chai.assert.deepEqual(projectedInput, {
chai.assert.deepEqual(input, {
TableName: "table",
ExpressionAttributeNames: {
"#P": "primary",
Expand All @@ -1149,10 +1145,9 @@ describe("requestBuilder", () => {

it("adds a projection to query input that includes key values", () => {
const input = buildQueryInput(defaultTableSchema, "mah value", "BETWEEN", "alpha", "beta");
const projectedInput = addProjection(defaultTableSchema, input, ["primary", "sort", "key", "lock"]);
addProjection(defaultTableSchema, input, ["primary", "sort", "key", "lock"]);

chai.assert.notEqual(input, projectedInput);
chai.assert.deepEqual(projectedInput, {
chai.assert.deepEqual(input, {
TableName: "table",
ExpressionAttributeNames: {
"#P": "primary",
Expand Down Expand Up @@ -1186,13 +1181,12 @@ describe("requestBuilder", () => {

it("adds a condition to put input", () => {
const input = buildPutInput(defaultTableSchema, {primary: "foo"});
const conditionalInput = addCondition(defaultTableSchema, input, {
addCondition(defaultTableSchema, input, {
attribute: "primary",
operator: "attribute_not_exists"
});

chai.assert.notEqual(input, conditionalInput);
chai.assert.deepEqual(conditionalInput, {
chai.assert.deepEqual(input, {
TableName: "table",
Item: {
primary: {
Expand All @@ -1205,16 +1199,15 @@ describe("requestBuilder", () => {

it("adds three conditions to put input", () => {
const input = buildPutInput(defaultTableSchema, {primary: "hick", eyes: 2, teeth: 12, ears: 2});
const conditionalInput = addCondition(
addCondition(
defaultTableSchema,
input,
{attribute: "eyes", operator: "<", values: [3]},
{attribute: "teeth", operator: ">", values: [4]},
{attribute: "ears", operator: "=", values: [2]}
);

chai.assert.notEqual(input, conditionalInput);
chai.assert.deepEqual(conditionalInput, {
chai.assert.deepEqual(input, {
TableName: "table",
Item: {
primary: {
Expand Down Expand Up @@ -1247,23 +1240,23 @@ describe("requestBuilder", () => {

it("adds three conditions to put input, one at a time", () => {
const input = buildPutInput(defaultTableSchema, {primary: "hick", eyes: 2, teeth: 12, ears: 2});
const conditionalInput1 = addCondition(
addCondition(
defaultTableSchema,
input,
{attribute: "eyes", operator: "<", values: [3]}
);
const conditionalInput2 = addCondition(
addCondition(
defaultTableSchema,
conditionalInput1,
input,
{attribute: "teeth", operator: ">", values: [4]}
);
const conditionalInput3 = addCondition(
addCondition(
defaultTableSchema,
conditionalInput2,
input,
{attribute: "ears", operator: "=", values: [2]}
);

chai.assert.deepEqual(conditionalInput3, {
chai.assert.deepEqual(input, {
TableName: "table",
Item: {
primary: {
Expand Down Expand Up @@ -1299,14 +1292,13 @@ describe("requestBuilder", () => {
primary: "foo",
alphabet: "αβγδε"
});
const conditionalInput = addCondition(defaultTableSchema, input, {
addCondition(defaultTableSchema, input, {
attribute: "alphabet",
operator: "begins_with",
values: ["abc"]
});

chai.assert.notEqual(input, conditionalInput);
chai.assert.deepEqual(conditionalInput, {
chai.assert.deepEqual(input, {
TableName: "table",
Item: {
primary: {
Expand All @@ -1330,16 +1322,15 @@ describe("requestBuilder", () => {

it("adds a condition to put input with reserved words", () => {
const input = buildPutInput(defaultTableSchema, {primary: "foo"});
const conditionalInput = addCondition(
addCondition(
defaultTableSchema,
input,
{attribute: "ASCII", operator: "begins_with", values: ["abc"]},
{attribute: "GOTO", operator: ">", values: [11]},
{attribute: "a.b\\.c", operator: "<", values: [0]},
);

chai.assert.notEqual(input, conditionalInput);
chai.assert.deepEqual(conditionalInput, {
chai.assert.deepEqual(input, {
TableName: "table",
Item: {
primary: {
Expand Down Expand Up @@ -1378,14 +1369,13 @@ describe("requestBuilder", () => {

it("adds a filter to query input", () => {
const input = buildQueryInput(defaultTableSchema, "foo");
const filteredInput = addFilter(defaultTableSchema, input, {
addFilter(defaultTableSchema, input, {
attribute: "nonprimary",
operator: "=",
values: [11]
});

chai.assert.notEqual(input, filteredInput);
chai.assert.deepEqual(filteredInput, {
chai.assert.deepEqual(input, {
TableName: "table",
ExpressionAttributeNames: {
"#P": "primary"
Expand All @@ -1405,16 +1395,15 @@ describe("requestBuilder", () => {

it("adds three filters to query input", () => {
const input = buildQueryInput(defaultTableSchema, "foo");
const filteredInput = addFilter(
addFilter(
defaultTableSchema,
input,
{attribute: "eyes", operator: "<", values: [3]},
{attribute: "teeth", operator: ">", values: [4]},
{attribute: "ears", operator: "=", values: [2]}
);

chai.assert.notEqual(input, filteredInput);
chai.assert.deepEqual(filteredInput, {
chai.assert.deepEqual(input, {
TableName: "table",
ExpressionAttributeNames: {
"#P": "primary"
Expand All @@ -1440,24 +1429,23 @@ describe("requestBuilder", () => {

it("adds three filters to query input, one at a time", () => {
const input = buildQueryInput(defaultTableSchema, "foo");
const filteredInput1 = addFilter(
addFilter(
defaultTableSchema,
input,
{attribute: "eyes", operator: "<", values: [3]}
);
const filteredInput2 = addFilter(
addFilter(
defaultTableSchema,
filteredInput1,
input,
{attribute: "teeth", operator: ">", values: [4]}
);
const filteredInput3 = addFilter(
addFilter(
defaultTableSchema,
filteredInput2,
input,
{attribute: "ears", operator: "=", values: [2]}
);

chai.assert.notEqual(input, filteredInput3);
chai.assert.deepEqual(filteredInput3, {
chai.assert.deepEqual(input, {
TableName: "table",
ExpressionAttributeNames: {
"#P": "primary"
Expand All @@ -1483,16 +1471,15 @@ describe("requestBuilder", () => {

it("adds a filter to query input with reserved words", () => {
const input = buildQueryInput(defaultTableSchema, "foo");
const filteredInput = addFilter(
addFilter(
defaultTableSchema,
input,
{attribute: "ASCII", operator: "begins_with", values: ["abc"]},
{attribute: "GOTO", operator: ">", values: [11]},
{attribute: "a.b\\.c", operator: "<", values: [0]},
);

chai.assert.notEqual(input, filteredInput);
chai.assert.deepEqual(filteredInput, {
chai.assert.deepEqual(input, {
TableName: "table",
ExpressionAttributeNames: {
"#P": "primary",
Expand Down
Loading

0 comments on commit 9b4881a

Please sign in to comment.