Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: remove DynamodbWrapper client dependency #32

Draft
wants to merge 13 commits into
base: master
Choose a base branch
from
101 changes: 70 additions & 31 deletions index.d.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
import { DynamoDB } from 'aws-sdk';
import DynamoDBWrapper from 'dynamodb-wrapper';

declare module "@flybondi/flynamo" {
declare module '@flybondi/flynamo' {
module Flynamo {
export type KeyAttributes = { [key: string]: string | number };
export type Key = string | number | KeyAttributes | { Key: KeyAttributes };
export type Item = { [key: string]: any };

export interface BatchGetItemInput {
ConsistentRead?: DynamoDB.ConsistentRead;
ReturnConsumedCapacity?: DynamoDB.ReturnConsumedCapacity;
Expand All @@ -17,29 +15,50 @@ declare module "@flybondi/flynamo" {
export type BatchWriteItemInput = Omit<DynamoDB.BatchWriteItemInput, 'RequestItems'>;

export interface BatchWriteItemOperations {
insert?: Item | Item[],
remove?: Key | Key[]
insert?: Item | Item[];
remove?: Key | Key[];
}

export type GetItemInput = Omit<DynamoDB.GetItemInput, 'TableName' | 'Key' | 'AttributesToGet'>;

export type PutItemInput = Omit<DynamoDB.PutItemInput, 'TableName' | 'Item' | 'Expected' | 'ConditionalOperator'>;
export type PutItemInput = Omit<
DynamoDB.PutItemInput,
'TableName' | 'Item' | 'Expected' | 'ConditionalOperator'
>;

export type QueryInput = Omit<DynamoDB.QueryInput, 'TableName' | 'AttributesToGet' | 'KeyConditions' | 'QueryFilter' | 'ConditionalOperator'>;
export type QueryInput = Omit<
DynamoDB.QueryInput,
'TableName' | 'AttributesToGet' | 'KeyConditions' | 'QueryFilter' | 'ConditionalOperator'
>;

export type DeleteItemInput = Omit<DynamoDB.DeleteItemInput, 'TableName' | 'Key' | 'Expected' | 'ConditionalOperator'>;
export type DeleteItemInput = Omit<
DynamoDB.DeleteItemInput,
'TableName' | 'Key' | 'Expected' | 'ConditionalOperator'
>;

export type UpdateItemInput = Omit<DynamoDB.UpdateItemInput, 'TableName' | 'Key' | 'AttributeUpdates' | 'Expected' | 'ConditionalOperator'>;
export type UpdateItemInput = Omit<
DynamoDB.UpdateItemInput,
'TableName' | 'Key' | 'AttributeUpdates' | 'Expected' | 'ConditionalOperator'
>;

export interface QueryOptions extends DynamoDBWrapper.IQueryOptions {
export interface OperationOptions {
/**
* Wheter to return all the DynamoDB response pages or just one page.
*/
autopagination?: boolean;
/**
* Whether to return the full `AWS.DynamoDB` response object when `true` or just the `Items` property value.
*/
raw?: boolean;
}

type UpdateExpressionBuilderInput = Pick<DynamoDB.Update, 'UpdateExpression' | 'ExpressionAttributeNames' | 'ExpressionAttributeValues'>;
export type UpdateExpressionBuilder = (input: UpdateExpressionBuilderInput) => DynamoDB.ExpressionAttributeValueMap;
type UpdateExpressionBuilderInput = Pick<
DynamoDB.Update,
'UpdateExpression' | 'ExpressionAttributeNames' | 'ExpressionAttributeValues'
>;
export type UpdateExpressionBuilder = (
input: UpdateExpressionBuilderInput
) => DynamoDB.ExpressionAttributeValueMap;
}
export interface Flynamo {
/**
Expand All @@ -66,7 +85,10 @@ declare module "@flybondi/flynamo" {
* @param input - Optional settings supported by `AWS.DynamoDB` for this operation.
* @returns Resolves to the response from `AWS.DynamoDB` client for a `BatchGetItem` operation.
*/
batchGet: (keys: Flynamo.Key | Flynamo.Key[], input?: Flynamo.BatchGetItemInput) => Promise<DynamoDB.BatchGetItemOutput>,
batchGet: (
keys: Flynamo.Key | Flynamo.Key[],
input?: Flynamo.BatchGetItemInput
) => Promise<DynamoDB.BatchGetItemOutput>;

/**
* Puts (i.e.: inserts) items in a table in a single batch.
Expand All @@ -81,7 +103,10 @@ declare module "@flybondi/flynamo" {
* @param input - Optional settings supported by `AWS.DynamoDB` for this operation.
* @returns Resolves to the response from `AWS.DynamoDB` client for a `BatchWriteItem` operation.
*/
batchInsert: (items: Flynamo.Item | Flynamo.Item[], input?: Flynamo.BatchWriteItemInput) => Promise<DynamoDB.BatchWriteItemOutput>,
batchInsert: (
items: Flynamo.Item | Flynamo.Item[],
input?: Flynamo.BatchWriteItemInput
) => Promise<DynamoDB.BatchWriteItemOutput>;

/**
* Deletes multiple items from a table in a single batch.
Expand All @@ -102,8 +127,10 @@ declare module "@flybondi/flynamo" {
* @param input - Optional settings supported by `AWS.DynamoDB` for this operation.
* @returns Resolves to the response from `AWS.DynamoDB` client for a `BatchWriteItem` operation.
*/
batchRemove: (keys: Flynamo.Key | Flynamo.Key[], input?: Flynamo.BatchWriteItemInput) => Promise<DynamoDB.BatchWriteItemOutput>,

batchRemove: (
keys: Flynamo.Key | Flynamo.Key[],
input?: Flynamo.BatchWriteItemInput
) => Promise<DynamoDB.BatchWriteItemOutput>;

/**
* Puts (i.e.: inserts) or deletes multiple items in a table in a single operation. It expects an
Expand All @@ -124,7 +151,10 @@ declare module "@flybondi/flynamo" {
* @param operations - An object containing `insert` and/or `remove` properties.
* @returns - Resolves to the response from `AWS.DynamoDB` client for a `BatchWriteItem` operation.
*/
batchWrite: (operations: Flynamo.BatchWriteItemOperations, input?: Flynamo.BatchWriteItemInput) => Promise<DynamoDB.BatchWriteItemOutput>,
batchWrite: (
operations: Flynamo.BatchWriteItemOperations,
input?: Flynamo.BatchWriteItemInput
) => Promise<DynamoDB.BatchWriteItemOutput>;

/**
* Return the number of elements in a table or a secondary index. This function
Expand All @@ -138,7 +168,7 @@ declare module "@flybondi/flynamo" {
* @param request - Parameters as expected by DynamoDB `Scan` operation.
* @returns A `Promise` that resolves to the total number of elements
*/
count: () => Promise<DynamoDB.Integer>,
count: () => Promise<DynamoDB.Integer>;

/**
* Returns a set of attributes for the item with the given primary key.
Expand Down Expand Up @@ -167,7 +197,7 @@ declare module "@flybondi/flynamo" {
* @returns A `Promise` that resolves to the item returned by `AWS.DynamoDB` response or `undefined` if it
* does not exist.
*/
get: (key: Flynamo.Key, input?: Flynamo.GetItemInput) => Promise<Flynamo.Item[]>,
get: (key: Flynamo.Key, input?: Flynamo.GetItemInput) => Promise<Flynamo.Item[]>;

/**
* Returns all items in a table or a secondary index. This uses `Scan` internally.
Expand All @@ -178,9 +208,13 @@ declare module "@flybondi/flynamo" {
* await getAll();
*
* @param input - Parameters as expected by `AWS.DynamoDB` `Scan` operation.
* @param options - The configuration options parameters.
* @returns A `Promise` that resolves to an array of `Items` returned by the `AWS.DynamoDB` response.
*/
getAll: (input?: DynamoDB.ScanInput) => Promise<Flynamo.Item[]>,
getAll: (
input?: DynamoDB.ScanInput,
options?: Flynamo.OperationOptions
) => Promise<Flynamo.Item[]>;

/**
* Creates a new item, or replaces an old item with a new item.
Expand All @@ -197,7 +231,7 @@ declare module "@flybondi/flynamo" {
* @param input - Optional parameters as expected by `AWS.DynamoDB` `PutItem` operation.
* @returns Resolves to the response from DynamoDB client.
*/
insert: (item: Flynamo.Item, input?: Flynamo.PutItemInput) => Promise<DynamoDB.PutItemOutput>,
insert: (item: Flynamo.Item, input?: Flynamo.PutItemInput) => Promise<DynamoDB.PutItemOutput>;

/**
* Finds items based on primary key values.
Expand All @@ -218,7 +252,10 @@ declare module "@flybondi/flynamo" {
* @param options - Configuration options parameters.
* @returns A `Promise` that resolves to either an array of returned `Items` or the full, raw response from `AWS.DynamoDB`.
*/
query: (input: Flynamo.QueryInput, options?: Flynamo.QueryOptions) => Promise<DynamoDB.QueryOutput | Item[]>,
query: (
input: Flynamo.QueryInput,
options?: Flynamo.OperationOptions
) => Promise<DynamoDB.QueryOutput | Item[]>;

/**
* Deletes a single item in a table by primary key. Returns the recently removed item by default
Expand All @@ -235,7 +272,7 @@ declare module "@flybondi/flynamo" {
* @returns A `Promise` that resolves to the `Attributes` property of the DynamoDB response. Unless, `ReturnValues` has
* been explicitly set, this will match all attributes of the recently deleted element.
*/
remove: (key: Flynamo.Key, input?: Flynamo.DeleteItemInput) => Promise<Flynamo.Item>,
remove: (key: Flynamo.Key, input?: Flynamo.DeleteItemInput) => Promise<Flynamo.Item>;

/**
* Edits an existing item's attributes, or adds a new item to the table if it does not
Expand All @@ -257,7 +294,11 @@ declare module "@flybondi/flynamo" {
* will override any value automatically derived from `itemOrBuilder`.
* @returns A `Promise` that resolves to the `Attributes` property of the `AWS.DynamoDB` response.
*/
update: (key: Flynamo.Key, itemOrBuilder: Flynamo.Item | Flynamo.UpdateExpressionBuilder, input?: Flynamo.UpdateItemInput) => Promise<Flynamo.Item>
update: (
key: Flynamo.Key,
itemOrBuilder: Flynamo.Item | Flynamo.UpdateExpressionBuilder,
input?: Flynamo.UpdateItemInput
) => Promise<Flynamo.Item>;
}

export interface FlynamoClient {
Expand All @@ -274,19 +315,17 @@ declare module "@flybondi/flynamo" {
* @param tableName - The value of `TableName`.
* @returns The entire `Flynamo` scoped to a single table.
*/
forTable(tableName: string): Flynamo
forTable(tableName: string): Flynamo;
}

/**
* Wraps an `AWS.DynamoDB` instance and returns a new `Flynamo` client.
* Configurable `options` for `dynamodb-wrapper` may be provided.
* Grabs an AWS DynamoDB `client` and returns Flynamo's API to access
* its methods.
*
* @see https://github.com/Shadowblazen/dynamodb-wrapper#setup
* @see https://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/DynamoDB.html
* @param client - A `AWS.DynamoDB` client.
* @param options - `DynamoDBWrapper` configuration (optional).
*/
export declare function flynamo(dynamodb: DynamoDB, options?: DynamoDBWrapper.IDynamoDBWrapperOptions): FlynamoClient;
*/
export declare function flynamo(dynamodb: DynamoDB): FlynamoClient;

export = flynamo;
}
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,9 @@
]
},
"dependencies": {
"@flybondi/ramda-land": "^4.0.6",
"dynamodb-data-types": "^3.0.1",
"dynamodb-update-expression": "^0.1.21",
"dynamodb-wrapper": "^1.4.1",
"ramda": "^0.27.1"
},
"devDependencies": {
Expand Down
23 changes: 23 additions & 0 deletions src/and-then.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
'use strict';
const { compose, invoker, andThen: RAndThen, when, hasIn } = require('ramda');

/**
* Invokes `promise` on the given object passing
* in no arguments.
*
* @param {object} obj The obj to invoke `promise` on.
* @returns {Promise.<*>} A `Promise` as returned by a call to `promise`.
*/
const toPromise = when(hasIn('promise'), invoker(0, 'promise'));

/**
* Returns the result of applying an `fn` function to the value inside a fulfilled promise,
* after having invoked `promise()` on the target object.
*
* @private
* @see https://ramdajs.com/docs/#andThen
* @param {Function} fn The function to apply. Can return a value or a promise of a value.
*/
const andThen = fn => compose(RAndThen(fn), toPromise);

module.exports = { toPromise, andThen };
11 changes: 11 additions & 0 deletions src/and-then.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
'use strict';
const { andThen } = require('./and-then');

test('should call the promise function and then the callback function', async () => {
const mockFn = { promise: jest.fn().mockResolvedValue({ Items: ['foo'] }) };
const mockCallbackFn = jest.fn().mockReturnValue(['foo']);

expect(await andThen(mockCallbackFn)(mockFn)).toEqual(['foo']);
expect(mockFn.promise).toHaveBeenCalled();
expect(mockCallbackFn).toHaveBeenCalled();
});
7 changes: 4 additions & 3 deletions src/batch-get-item.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ const generateKeys = compose(

const createBatchGetFor = curry((batchGetItem, table) =>
compose(
request => request.promise(),
apply(batchGetItem),
overFirst(compose(objOf('RequestItems'), objOf(table))),
mapMergeFirstPairOfArgs(generateKeys)
Expand All @@ -46,11 +47,11 @@ const createBatchGetFor = curry((batchGetItem, table) =>
* Creates a function to allow retrieval of several documents in a single batch.
*
* @private
* @param {Object} dynamoWrapper The AWS DynamoDB client
* @param {Object} dynamodb The AWS DynamoDB client
* @returns {Object}
*/
function createGetBatcher(dynamoWrapper) {
const batchGetItem = bind(dynamoWrapper.batchGetItem, dynamoWrapper);
function createGetBatcher(dynamodb) {
const batchGetItem = bind(dynamodb.batchGetItem, dynamodb);
return {
/**
* Returns the attributes of one or more items from a table. Requested items are identified by primary key.
Expand Down
15 changes: 10 additions & 5 deletions src/batch-get-item.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
const createGetBatcher = require('./batch-get-item');

test('should generate valid `RequestItems` with generated keys', async () => {
const mockBatchGetItem = jest.fn().mockResolvedValue({});
const mockRequest = { promise: jest.fn().mockResolvedValue({}) };
const mockBatchGetItem = jest.fn().mockReturnValue(mockRequest);
const { batchGetFor } = createGetBatcher({ batchGetItem: mockBatchGetItem });
const batchGet = batchGetFor('some_table');
await batchGet([42, 33]);
Expand All @@ -16,7 +17,8 @@ test('should generate valid `RequestItems` with generated keys', async () => {
});

test('should support sending a single key as its first argument', async () => {
const mockBatchGetItem = jest.fn().mockResolvedValue({});
const mockRequest = { promise: jest.fn().mockResolvedValue({}) };
const mockBatchGetItem = jest.fn().mockReturnValue(mockRequest);
const { batchGetFor } = createGetBatcher({ batchGetItem: mockBatchGetItem });
const batchGet = batchGetFor('some_table');
await batchGet(42);
Expand All @@ -30,7 +32,8 @@ test('should support sending a single key as its first argument', async () => {
});

test('should support retrieving documents with composite keys', async () => {
const mockBatchGetItem = jest.fn().mockResolvedValue({});
const mockRequest = { promise: jest.fn().mockResolvedValue({}) };
const mockBatchGetItem = jest.fn().mockReturnValue(mockRequest);
const { batchGetFor } = createGetBatcher({ batchGetItem: mockBatchGetItem });
const batchGet = batchGetFor('some_table');
await batchGet([
Expand All @@ -50,7 +53,8 @@ test('should support retrieving documents with composite keys', async () => {
});

test('should allow sending additional request properties as its sencond argument', async () => {
const mockBatchGetItem = jest.fn().mockResolvedValue({});
const mockRequest = { promise: jest.fn().mockResolvedValue({}) };
const mockBatchGetItem = jest.fn().mockReturnValue(mockRequest);
const { batchGetFor } = createGetBatcher({ batchGetItem: mockBatchGetItem });
const batchGet = batchGetFor('some_table');
await batchGet([42, 33], { ProjectionExpression: 'name, messages, views', ConsistentRead: true });
Expand All @@ -65,7 +69,8 @@ test('should allow sending additional request properties as its sencond argument
});
});
test('should forward any additional arguments to the original `batchGetItem` function', async () => {
const mockBatchGetItem = jest.fn().mockResolvedValue({});
const mockRequest = { promise: jest.fn().mockResolvedValue({}) };
const mockBatchGetItem = jest.fn().mockReturnValue(mockRequest);
const { batchGetFor } = createGetBatcher({ batchGetItem: mockBatchGetItem });
const batchGet = batchGetFor('some_table');
await batchGet([42, 33], { ProjectionExpression: 'name' }, { extra: true }, 42);
Expand Down
13 changes: 9 additions & 4 deletions src/batch-write-item.js
Original file line number Diff line number Diff line change
Expand Up @@ -92,11 +92,16 @@ const generateRequestItems = table =>
);

const createBatchWriteFor = curry((batchWriteItem, table) =>
compose(apply(batchWriteItem), mapMergeFirstPairOfArgs(generateRequestItems(table)))
compose(
request => request.promise(),
apply(batchWriteItem),
mapMergeFirstPairOfArgs(generateRequestItems(table))
)
);

const createBatchRequestFor = curry((batchWriteItem, requestType, table) =>
compose(
request => request.promise(),
apply(batchWriteItem),
mapMergeFirstPairOfArgs(compose(generateRequestItems(table), objOf(requestType)))
)
Expand All @@ -106,11 +111,11 @@ const createBatchRequestFor = curry((batchWriteItem, requestType, table) =>
* Creates functions to allow removal and insertions of documents in a single batch.
*
* @private
* @param {Object} dynamoWrapper The AWS DynamoDB client
* @param {Object} dynamodb The AWS DynamoDB client
* @returns {Object}
*/
function createWriteBatcher(dynamoWrapper) {
const batchWriteItem = bind(dynamoWrapper.batchWriteItem, dynamoWrapper);
function createWriteBatcher(dynamodb) {
const batchWriteItem = bind(dynamodb.batchWriteItem, dynamodb);
return {
/**
* Returns a function that puts or deletes multiple items in a table. It expects a single
Expand Down
Loading