Skip to content

Commit

Permalink
Refactored code and added documentation (#4)
Browse files Browse the repository at this point in the history
  • Loading branch information
wpoynter authored Oct 1, 2024
1 parent 1ca9bbb commit 288a865
Show file tree
Hide file tree
Showing 10 changed files with 329 additions and 130 deletions.
15 changes: 15 additions & 0 deletions .github/actions/setup-codeclimate/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
name: 'Setup CodeClimate'
description: 'Downloads and sets up the CodeClimate Test Reporter'

runs:
using: 'composite'
steps:
- name: Download CodeClimate Test Reporter
run: |
curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter
chmod +x ./cc-test-reporter
shell: bash

- name: Run CodeClimate Test Reporter before-build
run: ./cc-test-reporter before-build
shell: bash
71 changes: 68 additions & 3 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: AI Code Reviewer
name: Checks

on:
pull_request:
Expand Down Expand Up @@ -60,6 +60,8 @@ jobs:
test:
runs-on: ubuntu-latest
name: Unit Test Project
env:
CC_TEST_REPORTER_ID: ${{ secrets.CC_TEST_REPORTER_ID }}
steps:
- name: Checkout code
uses: actions/checkout@v3
Expand All @@ -69,8 +71,18 @@ jobs:
node-version: '20'
bun-version: 'latest'

- name: Setup CodeClimate
uses: ./.github/actions/setup-codeclimate

- name: Run tests
run: bun run test
run: bun run test -- --coverage --coverage-reporter=lcov
continue-on-error: true

- name: Upload coverage artifact
uses: actions/upload-artifact@v3
with:
name: unit-test-coverage
path: coverage/lcov.info

review:
runs-on: ubuntu-latest
Expand All @@ -92,6 +104,8 @@ jobs:
runs-on: ubuntu-latest
name: Integration Test Project
needs: [lint, format, build, test]
env:
CC_TEST_REPORTER_ID: ${{ secrets.CC_TEST_REPORTER_ID }}
services:
dynamodb:
image: amazon/dynamodb-local
Expand All @@ -117,10 +131,61 @@ jobs:
echo "Waiting for DynamoDB..."
sleep 2
done
- name: Setup CodeClimate
uses: ./.github/actions/setup-codeclimate

- name: Run integration tests
env:
AWS_ACCESS_KEY_ID: 'fakeMyKeyId'
AWS_SECRET_ACCESS_KEY: 'fakeSecretAccessKey'
AWS_REGION: 'local'
AWS_DYNAMODB_ENDPOINT: 'http://localhost:8000'
run: bun run test:integration
run: bun run test:integration -- --coverage --coverage-reporter=lcov
continue-on-error: true

- name: Upload coverage artifact
uses: actions/upload-artifact@v3
with:
name: unit-test-coverage
path: coverage/lcov.info

coverage-report:
runs-on: ubuntu-latest
needs: [test, integration-test]
env:
CC_TEST_REPORTER_ID: ${{ secrets.CC_TEST_REPORTER_ID }}
steps:
- name: Checkout code
uses: actions/checkout@v3

- name: Download unit test coverage artifact
uses: actions/download-artifact@v3
with:
name: unit-test-coverage
path: ./coverage/unit

- name: Download integration test coverage artifact
uses: actions/download-artifact@v3
with:
name: integration-test-coverage
path: ./coverage/integration

- name: Setup CodeClimate
uses: ./.github/actions/setup-codeclimate

- name: Format unit test coverage
run: |
./cc-test-reporter format-coverage -t lcov -o codeclimate.unit.json ./coverage/unit/lcov.info
- name: Format integration test coverage
run: |
./cc-test-reporter format-coverage -t lcov -o codeclimate.integration.json ./coverage/integration/lcov.info
- name: Sum coverage reports
run: |
./cc-test-reporter sum-coverage codeclimate.unit.json codeclimate.integration.json -o codeclimate.total.json
- name: Upload coverage to CodeClimate
run: |
./cc-test-reporter upload-coverage -i codeclimate.total.json
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

Implementation of a LangGraph.js CheckpointSaver that uses a AWS's DynamoDB

## Package name

`@langgraph/checkpoint-dynamodb`

## Inspiration

Guidance and inspiration has been taken from the existing checkpoint savers
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
},
"repository": {
"type": "git",
"url": "git+https://github.com/langgraphjs-checkpoint-dynamodb.git"
"url": "https://github.com/langgraphjs-checkpoint-dynamodb.git"
},
"scripts": {
"build": "bun run build:esm && bun run build:cjs",
Expand Down
76 changes: 45 additions & 31 deletions src/__mocks__/DynamoDBDocument.mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,22 @@ export class MockDynamoDBDocument {
const table = this.tables[TableName] || {};
const items = Object.values(table);

// Simulate filtering based on KeyConditionExpression (simplified for example)
const filteredItems = items.filter(item => {
const filteredItems = this.filterItems(items, ExpressionAttributeValues);
const sortedItems = this.sortItems(filteredItems, ScanIndexForward);
const limitedItems = this.limitItems(sortedItems, Limit);

return { Items: limitedItems };
}

async batchWrite(params: { RequestItems: Record<string, any[]> }) {
for (const TableName in params.RequestItems) {
this.processTableRequests(TableName, params.RequestItems[TableName]);
}
return {};
}

private filterItems(items: any[], ExpressionAttributeValues: Record<string, any>): any[] {
return items.filter(item => {
for (const key in ExpressionAttributeValues) {
const attributeName = key.replace(':', '');
if (item[attributeName] !== ExpressionAttributeValues[key]) {
Expand All @@ -51,42 +65,42 @@ export class MockDynamoDBDocument {
}
return true;
});
}

// Sort items if ScanIndexForward is specified
if (ScanIndexForward !== undefined) {
filteredItems.sort((a, b) => {
const aKey = a.checkpoint_id;
const bKey = b.checkpoint_id;
if (ScanIndexForward) {
return aKey.localeCompare(bKey);
} else {
return bKey.localeCompare(aKey);
}
});
}
private sortItems(items: any[], ScanIndexForward?: boolean): any[] {
if (ScanIndexForward === undefined) return items;

// Apply Limit if specified
const limitedItems = Limit ? filteredItems.slice(0, Limit) : filteredItems;
return items.sort((a, b) => {
const aKey = a.checkpoint_id;
const bKey = b.checkpoint_id;
if (ScanIndexForward) {
return aKey.localeCompare(bKey);
} else {
return bKey.localeCompare(aKey);
}
});
}

return { Items: limitedItems };
private limitItems(items: any[], Limit?: number): any[] {
return Limit ? items.slice(0, Limit) : items;
}

async batchWrite(params: { RequestItems: Record<string, any[]> }) {
for (const TableName in params.RequestItems) {
const table = this.tables[TableName] || {};
for (const request of params.RequestItems[TableName]) {
if (request.PutRequest) {
const Item = request.PutRequest.Item;
this.sizeGuard(Item);
const keyAttributes = this.getKeyAttributes(Item);
const itemKey = JSON.stringify(keyAttributes);
table[itemKey] = Item;
}
// Handle DeleteRequest if needed
private processTableRequests(TableName: string, requests: any[]) {
const table = this.tables[TableName] || {};
for (const request of requests) {
if (request.PutRequest) {
this.processPutRequest(table, request.PutRequest.Item);
}
this.tables[TableName] = table;
// Handle other requests if needed
}
return {};
this.tables[TableName] = table;
}

private processPutRequest(table: Record<string, any>, Item: any) {
this.sizeGuard(Item);
const keyAttributes = this.getKeyAttributes(Item);
const itemKey = JSON.stringify(keyAttributes);
table[itemKey] = Item;
}

private calculateItemSize(item: any): number {
Expand Down
15 changes: 15 additions & 0 deletions src/__tests__/helpers/expectErrorMessageToBeThrown.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { expect } from 'bun:test';

export async function expectErrorMessageToBeThrown(
callback: () => Promise<unknown>,
message: string
) {
try {
await callback();
throw new Error("Expected function to throw an error, but it didn't");
} catch (error) {
expect(error).toBeInstanceOf(Error);
expect((error as any).message).toBe(message);
}
}
Loading

0 comments on commit 288a865

Please sign in to comment.