Skip to content
This repository was archived by the owner on Jun 22, 2021. It is now read-only.

feat: Adds support for loading new results. #13

Merged
merged 2 commits into from
Mar 28, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/facade.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ This package also contains some utility functions outside of the Facade that you

- [convertPropertyFilter](./utils.md#convertpropertyfilter)
- [createCursorFromEntity](./utils.md#createcursorfromentity)
- [createCursorsFromEntities](./utils.md#createcursorsfromentities)
- [createGetEntitiesResult](./utils.md#creategetentitiesresult)
- [createPaginationFilter](./utils.md#createpaginationfilter)

The [facade in this package is a TypeScript interface](../src/Facade.ts), but concrete implementations of the interface are listed below.
Expand Down
31 changes: 19 additions & 12 deletions docs/functions.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,22 +49,29 @@ Retreives a sorted paginated array of entities that match the [`filter`](./optio
```ts
import { backward, forward } from '@js-entity-repos/core/dist/types/PaginationDirection';
import { asc, desc } from '@js-entity-repos/core/dist/types/SortOrder';
import { start } from '@js-entity-repos/core/dist/types/Cursor';

const { entities, nextCursor, previousCursor } = await facade.getEntities({
const firstForwardPage = await facade.getEntities({
filter: { foo: 'demo' },
sort: { id: asc, bar: desc },
pagination: { limit: 10, direction: forward, cursor: undefined },
});
const secondPage = await facade.getEntities({
filter: { foo: 'demo' },
sort: { id: asc, bar: desc },
pagination: { limit: 10, direction: forward, cursor: nextCursor },
});
const firstPage = await facade.getEntities({
filter: { foo: 'demo' },
sort: { id: asc, bar: desc },
pagination: { limit: 10, direction: backward, cursor: secondPage.previousCursor },
pagination: { limit: 10, direction: forward, cursor: start },
});
const firstPageEntities = firstForwardPage.entities;
if (firstForwardPage.hasMoreForward) {
const secondForwardPage = await facade.getEntities({
filter: { foo: 'demo' },
sort: { id: asc, bar: desc },
pagination: { limit: 10, direction: forward, cursor: firstForwardPage.forwardCursor },
});
const secondPageEntities = secondForwardPage.entities;
if (secondForwardPage.hasMoreBackward) {
const firstPage = await facade.getEntities({
filter: { foo: 'demo' },
sort: { id: asc, bar: desc },
pagination: { limit: 10, direction: backward, cursor: secondForwardPage.backwardCursor },
});
}
}
```

This package contains the [get entities tests](../src/tests/getEntities) and the [get entities signature](../src/signatures/GetEntities.ts) for this function.
Expand Down
31 changes: 11 additions & 20 deletions docs/utils.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ This package contains some common utility functions that can be used by both use

- [convertPropertyFilter](#convertpropertyfilter)
- [createCursorFromEntity](#createcursorfromentity)
- [createCursorsFromEntities](#createcursorsfromentities)
- [createGetEntitiesResult](#creategetentitiesresult)
- [createPaginationFilter](#createpaginationfilter)

### convertPropertyFilter
Expand All @@ -21,48 +23,37 @@ convertPropertyFilter({
```

### createCursorFromEntity
Exactly what it says on the tin, this creates a cursor from an entity. A cursor is constructed by creating a [filter](./options#filter) that will filter out entities not expected in the next set of paginated results. This filter is then stringified to JSON and base 64 encoded. This function is usually used by [concrete implementations of the Facade](./facade.md#facade) like [Knex's getEntities implementation](https://github.com/js-entity-repos/knex/blob/master/src/functions/getEntities.ts)..
Exactly what it says on the tin, this creates a cursor from an entity. A cursor is constructed by creating a [filter](./options#filter) that will filter out entities not expected in the next set of paginated results. This filter is then stringified to JSON and base 64 encoded. This function is usually used by the [`createCursorsFromEntities` util](#createcursorsfromentities). Check out the [`createCursorsFromEntities` implementation](../src/utils/createCursorsFromEntities/index.ts) for an example of its use.

```ts
import createCursorFromEntity from '@js-entity-repos/core/dist/utils/createCursorFromEntity';
import { asc } from '@js-entity-repos/core/dist/types/SortOrder';

createCursorFromEntity(undefined, { id: asc });
// Returns undefined
### createCursorsFromEntities
Creates a `forwardCursor` and a `backwardCursor` from an array of entities along with a sort and cursor. This function is usually used by the [`createGetEntitiesResult` util](#creategetentitiesresult). Check out the [`createGetEntitiesResult` implementation](../src/utils/createGetEntitiesResult/index.ts) for an example of its use.

createCursorFromEntity({
booleanProp: true,
id: 'test_id_1',
numberProp: 1,
stringProp: 'test_string_prop',
}, {
id: asc,
});
// Returns eyJpZCI6InRlc3RfaWQifQ==
```
### createGetEntitiesResult
This function is usually used by [concrete implementations of the Facade](./facade.md#facade) to construct the result for a request to get entities. Check out [Knex's getEntities implementation](https://github.com/js-entity-repos/knex/blob/master/src/functions/getEntities.ts) for an example of its use.

### createPaginationFilter
Takes a [pagination option](./options#pagination) and a [sort option](./options#sort)) to produces a [filter](./options#filter) that can filter out entities not expected in the next set of paginated results. This function is usually used by [concrete implementations of the Facade](./facade.md#facade) like [Knex's getEntities implementation](https://github.com/js-entity-repos/knex/blob/master/src/functions/getEntities.ts).

```ts
import createPaginationFilter from '@js-entity-repos/core/dist/utils/createPaginationFilter';
import { start } from '@js-entity-repos/core/dist/types/Cursor';
import { asc, desc } from '@js-entity-repos/core/dist/types/SortOrder';
import { backward, forward } from '@js-entity-repos/core/dist/types/PaginationDirection';

createPaginationFilter(
{ cursor: undefined, direction: forward, limit: 1 },
{ cursor: start, direction: forward, limit: 1 },
{ id: asc, numberProp: desc }
);
// Returns {}

createPaginationFilter(
{ cursor: nextCursor, direction: forward, limit: 1 },
{ cursor: forwardCursor, direction: forward, limit: 1 },
{ id: asc, numberProp: desc }
);
// Returns the result of { id: { $gt: lastEntity.id }, numberProp: { $lte: lastEntity.numberProp } }

createPaginationFilter(
{ cursor: prevCursor, direction: backward, limit: 1 },
{ cursor: backwardCursor, direction: backward, limit: 1 },
{ id: asc, numberProp: desc }
);
// Returns the result of { id: { $lt: firstEntity.id }, numberProp: { $gte: firstEntity.numberProp } }
Expand Down
8 changes: 0 additions & 8 deletions src/errors/PaginationFilterError.ts

This file was deleted.

6 changes: 4 additions & 2 deletions src/signatures/GetEntities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@ export interface Opts<E extends Entity> {

export interface Result<E extends Entity> {
readonly entities: E[];
readonly nextCursor: Cursor;
readonly previousCursor: Cursor;
readonly forwardCursor: Cursor;
readonly backwardCursor: Cursor;
readonly hasMoreForward: boolean;
readonly hasMoreBackward: boolean;
}

export type Signature<E extends Entity> = (opts: Opts<E>) => Promise<Result<E>>;
Expand Down
106 changes: 65 additions & 41 deletions src/tests/getEntities/paginationTest.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
// tslint:disable:max-file-line-count
import 'mocha'; // tslint:disable-line:no-import-side-effect
import * as assert from 'power-assert';
import Facade from '../../Facade';
import Cursor, { end, start } from '../../types/Cursor';
import Cursor, { start } from '../../types/Cursor';
import Pagination from '../../types/Pagination';
import PaginationDirection, { backward, forward } from '../../types/PaginationDirection';
import Sort from '../../types/Sort';
Expand All @@ -10,77 +11,100 @@ import createCursorFromEntity from '../../utils/createCursorFromEntity';
import { TestEntity, testEntity } from '../utils/testEntity';

export default (facade: Facade<TestEntity>) => {
const sort: Sort<TestEntity> = { id: asc };
const firstId = 'test_id_1';
const secondId = 'test_id_2';
const firstEntity = { ...testEntity, id: firstId };
const secondEntity = { ...testEntity, id: secondId };
const sort: Sort<TestEntity> = { id: asc };
const firstCursor = createCursorFromEntity(firstEntity, sort);
const secondCursor = createCursorFromEntity(secondEntity, sort);

const createTestEntities = async () => {
await facade.createEntity({ id: firstId, entity: firstEntity });
await facade.createEntity({ id: secondId, entity: secondEntity });
};

const paginate = (cursor: Cursor, direction: PaginationDirection) => {
const paginate = async (cursor: Cursor, direction: PaginationDirection) => {
const pagination: Pagination = { cursor, direction, limit: 1 };
await createTestEntities();
return facade.getEntities({ pagination, sort });
};

it('should return all entities when pagination is not defined', async () => {
await createTestEntities();
const result = await facade.getEntities({});
assert.deepEqual(result.entities, [firstEntity, secondEntity]);
assert.equal(result.previousCursor, createCursorFromEntity(firstEntity, sort));
assert.equal(result.nextCursor, end);
assert.deepEqual(result, {
backwardCursor: firstCursor,
entities: [firstEntity, secondEntity],
forwardCursor: secondCursor,
hasMoreBackward: false,
hasMoreForward: false,
});
});

it('should return first entity when paginating forward with start cursor', async () => {
await createTestEntities();
const finalResult = await paginate(start, forward);
assert.deepEqual(finalResult.entities, [firstEntity]);
assert.equal(finalResult.previousCursor, end);
assert.equal(finalResult.nextCursor, createCursorFromEntity(firstEntity, sort));
const result = await paginate(start, forward);
assert.deepEqual(result, {
backwardCursor: firstCursor,
entities: [firstEntity],
forwardCursor: firstCursor,
hasMoreBackward: false,
hasMoreForward: true,
});
});

it('should return second entity when paginating forward with first cursor', async () => {
await createTestEntities();
const firstResult = await paginate(start, forward);
const finalResult = await paginate(firstResult.nextCursor, forward);
assert.deepEqual(finalResult.entities, [secondEntity]);
assert.equal(finalResult.previousCursor, createCursorFromEntity(secondEntity, sort));
assert.equal(finalResult.nextCursor, end);
const result = await paginate(firstCursor, forward);
assert.deepEqual(result, {
backwardCursor: secondCursor,
entities: [secondEntity],
forwardCursor: secondCursor,
hasMoreBackward: true,
hasMoreForward: false,
});
});

it('should return no entities when paginating forward with end cursor', async () => {
await createTestEntities();
const finalResult = await paginate(end, forward);
assert.deepEqual(finalResult.entities, []);
assert.equal(finalResult.previousCursor, start);
assert.equal(finalResult.nextCursor, end);
it('should return no entities when paginating forward with second cursor', async () => {
const result = await paginate(secondCursor, forward);
assert.deepEqual(result, {
backwardCursor: secondCursor,
entities: [],
forwardCursor: secondCursor,
hasMoreBackward: true,
hasMoreForward: false,
});
});

it('should return second entity when paginating backward with start cursor', async () => {
await createTestEntities();
const finalResult = await paginate(start, backward);
assert.deepEqual(finalResult.entities, [secondEntity]);
assert.equal(finalResult.previousCursor, createCursorFromEntity(secondEntity, sort));
assert.equal(finalResult.nextCursor, end);
const result = await paginate(start, backward);
assert.deepEqual(result, {
backwardCursor: secondCursor,
entities: [secondEntity],
forwardCursor: secondCursor,
hasMoreBackward: true,
hasMoreForward: false,
});
});

it('should return first entity when paginating backward with first cursor', async () => {
await createTestEntities();
const firstResult = await paginate(start, backward);
const finalResult = await paginate(firstResult.previousCursor, backward);
assert.deepEqual(finalResult.entities, [firstEntity]);
assert.equal(finalResult.previousCursor, end);
assert.equal(finalResult.nextCursor, createCursorFromEntity(firstEntity, sort));
it('should return first entity when paginating backward with second cursor', async () => {
const result = await paginate(secondCursor, backward);
assert.deepEqual(result, {
backwardCursor: firstCursor,
entities: [firstEntity],
forwardCursor: firstCursor,
hasMoreBackward: false,
hasMoreForward: true,
});
});

it('should return no entities when paginating backward with end cursor', async () => {
await createTestEntities();
const finalResult = await paginate(end, backward);
assert.deepEqual(finalResult.entities, []);
assert.equal(finalResult.previousCursor, end);
assert.equal(finalResult.nextCursor, start);
it('should return no entities when paginating backward with first cursor', async () => {
const result = await paginate(firstCursor, backward);
assert.deepEqual(result, {
backwardCursor: firstCursor,
entities: [],
forwardCursor: firstCursor,
hasMoreBackward: false,
hasMoreForward: true,
});
});
};
3 changes: 1 addition & 2 deletions src/types/Cursor.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
export const start = undefined;
export const end = '';

type Cursor = string | typeof start | typeof end;
type Cursor = string | typeof start;

export default Cursor;
6 changes: 0 additions & 6 deletions src/utils/createCursorFromEntity/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,10 @@
import 'mocha'; // tslint:disable-line:no-import-side-effect
import * as assert from 'power-assert';
import { TestEntity, testEntity } from '../../tests/utils/testEntity';
import { end } from '../../types/Cursor';
import { asc } from '../../types/SortOrder';
import createCursorFromEntity from './index';

describe('createCursorFromEntity', () => {
it('should return undefined when the entity is undefined', () => {
const actualResult = createCursorFromEntity<TestEntity>(undefined, { id: asc });
assert.equal(actualResult, end);
});

it('should return the correct cursor when the entity is defined', () => {
const actualResult = createCursorFromEntity<TestEntity>(testEntity, { id: asc });
assert.equal(actualResult, 'eyJpZCI6InRlc3RfaWQifQ==');
Expand Down
7 changes: 2 additions & 5 deletions src/utils/createCursorFromEntity/index.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
import * as btoa from 'btoa';
import { get, set } from 'lodash';
import Cursor, { end } from '../../types/Cursor';
import Cursor from '../../types/Cursor';
import Entity from '../../types/Entity';
import Sort from '../../types/Sort';

export default <E extends Entity>(entity: E | undefined, sort: Sort<E>): Cursor => {
if (entity === undefined) {
return end;
}
export default <E extends Entity>(entity: E, sort: Sort<E>): Cursor => {
const sortKeys = Object.keys(sort);
const cursorResult = sortKeys.reduce<Partial<E>>((result, sortKey) => {
return set(result, sortKey, get(entity, sortKey));
Expand Down
47 changes: 47 additions & 0 deletions src/utils/createCursorsFromEntities/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import 'mocha'; // tslint:disable-line:no-import-side-effect
import * as assert from 'power-assert';
import { TestEntity, testEntity } from '../../tests/utils/testEntity';
import { start } from '../../types/Cursor';
import Sort from '../../types/Sort';
import { asc } from '../../types/SortOrder';
import createCursorsFromEntities from './index';

describe('createCursorsFromEntities', () => {
const sort: Sort<TestEntity> = { id: asc };
const firstId = 'test_id_1';
const secondId = 'test_id_2';
const firstEntity = { ...testEntity, id: firstId };
const secondEntity = { ...testEntity, id: secondId };
const firstCursor = 'eyJpZCI6InRlc3RfaWRfMSJ9';
const secondCursor = 'eyJpZCI6InRlc3RfaWRfMiJ9';

it('should return the correct cursors when there are no entities', () => {
const entities: TestEntity[] = [];
const cursor = start;
const actualResult = createCursorsFromEntities({ cursor, entities, sort });
assert.deepEqual(actualResult, {
backwardCursor: start,
forwardCursor: start,
});
});

it('should return the correct cursors when there is one entity', () => {
const entities = [firstEntity];
const cursor = start;
const actualResult = createCursorsFromEntities({ cursor, entities, sort });
assert.deepEqual(actualResult, {
backwardCursor: firstCursor,
forwardCursor: firstCursor,
});
});

it('should return the correct cursors when there are many entities', () => {
const entities = [firstEntity, secondEntity];
const cursor = start;
const actualResult = createCursorsFromEntities({ cursor, entities, sort });
assert.deepEqual(actualResult, {
backwardCursor: firstCursor,
forwardCursor: secondCursor,
});
});
});
Loading