nestjs-paginate
Version:
Pagination and filtering helper method for TypeORM repositories or query builders using Nest.js framework.
1,187 lines (1,186 loc) • 160 kB
JavaScript
"use strict";
var __rest = (this && this.__rest) || function (s, e) {
var t = {};
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
t[p] = s[p];
if (s != null && typeof Object.getOwnPropertySymbols === "function")
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
t[p[i]] = s[p[i]];
}
return t;
};
Object.defineProperty(exports, "__esModule", { value: true });
const common_1 = require("@nestjs/common");
const lodash_1 = require("lodash");
const process = require("process");
const typeorm_1 = require("typeorm");
const cat_hair_entity_1 = require("./__tests__/cat-hair.entity");
const cat_home_pillow_brand_entity_1 = require("./__tests__/cat-home-pillow-brand.entity");
const cat_home_pillow_entity_1 = require("./__tests__/cat-home-pillow.entity");
const cat_home_entity_1 = require("./__tests__/cat-home.entity");
const cat_toy_entity_1 = require("./__tests__/cat-toy.entity");
const cat_entity_1 = require("./__tests__/cat.entity");
const toy_shop_address_entity_1 = require("./__tests__/toy-shop-address.entity");
const toy_shop_entity_1 = require("./__tests__/toy-shop.entity");
const filter_1 = require("./filter");
const paginate_1 = require("./paginate");
// Disable debug logs during tests
beforeAll(() => {
jest.spyOn(common_1.Logger.prototype, 'debug').mockImplementation(() => { });
});
afterAll(() => {
jest.restoreAllMocks(); // Restore default logger behavior
});
const isoStringToDate = (isoString) => new Date(isoString);
describe('paginate', () => {
let dataSource;
let catRepo;
let catToyRepo;
let catHairRepo;
let toyShopRepo;
let toyShopAddressRepository;
let catHomeRepo;
let catHomePillowRepo;
let catHomePillowBrandRepo;
let cats;
let catToys;
let catToysWithoutShop;
let toyShopsAddresses;
let toysShops;
let catHomes;
let catHomePillows;
let naptimePillow;
let pillowBrand;
let catHairs = [];
let underCoats = [];
beforeAll(async () => {
const dbOptions = {
dropSchema: true,
synchronize: true,
logging: ['error'],
entities: [
cat_entity_1.CatEntity,
cat_toy_entity_1.CatToyEntity,
toy_shop_address_entity_1.ToyShopAddressEntity,
cat_home_entity_1.CatHomeEntity,
cat_home_pillow_entity_1.CatHomePillowEntity,
cat_home_pillow_brand_entity_1.CatHomePillowBrandEntity,
toy_shop_entity_1.ToyShopEntity,
process.env.DB === 'postgres' ? cat_hair_entity_1.CatHairEntity : undefined,
],
};
switch (process.env.DB) {
case 'postgres':
dataSource = new typeorm_1.DataSource(Object.assign(Object.assign({}, dbOptions), { type: 'postgres', host: process.env.DB_HOST || 'localhost', port: +process.env.POSTGRESS_DB_PORT || 5432, username: process.env.DB_USERNAME || 'root', password: process.env.DB_PASSWORD || 'pass', database: process.env.DB_DATABASE || 'test' }));
break;
case 'mariadb':
dataSource = new typeorm_1.DataSource(Object.assign(Object.assign({}, dbOptions), { type: 'mariadb', host: process.env.DB_HOST || 'localhost', port: +process.env.MARIA_DB_PORT || 3306, username: process.env.DB_USERNAME || 'root', password: process.env.DB_PASSWORD || 'pass', database: process.env.DB_DATABASE || 'test' }));
break;
case 'sqlite':
dataSource = new typeorm_1.DataSource(Object.assign(Object.assign({}, dbOptions), { type: 'sqlite', database: ':memory:' }));
break;
default:
throw new Error('Invalid DB');
}
await dataSource.initialize();
catRepo = dataSource.getRepository(cat_entity_1.CatEntity);
catToyRepo = dataSource.getRepository(cat_toy_entity_1.CatToyEntity);
catHomeRepo = dataSource.getRepository(cat_home_entity_1.CatHomeEntity);
catHomePillowRepo = dataSource.getRepository(cat_home_pillow_entity_1.CatHomePillowEntity);
catHomePillowBrandRepo = dataSource.getRepository(cat_home_pillow_brand_entity_1.CatHomePillowBrandEntity);
toyShopRepo = dataSource.getRepository(toy_shop_entity_1.ToyShopEntity);
toyShopAddressRepository = dataSource.getRepository(toy_shop_address_entity_1.ToyShopAddressEntity);
cats = await catRepo.save([
catRepo.create({
name: 'Milo',
color: 'brown',
age: 6,
cutenessLevel: cat_entity_1.CutenessLevel.HIGH,
lastVetVisit: isoStringToDate('2022-12-19T10:00:00.000Z'),
size: { height: 25, width: 10, length: 40 },
weightChange: -0.75,
}),
catRepo.create({
name: 'Garfield',
color: 'ginger',
age: 5,
cutenessLevel: cat_entity_1.CutenessLevel.MEDIUM,
lastVetVisit: isoStringToDate('2022-12-20T10:00:00.000Z'),
size: { height: 30, width: 15, length: 45 },
weightChange: 5.25,
}),
catRepo.create({
name: 'Shadow',
color: 'black',
age: 4,
cutenessLevel: cat_entity_1.CutenessLevel.HIGH,
lastVetVisit: isoStringToDate('2022-12-21T10:00:00.000Z'),
size: { height: 25, width: 10, length: 50 },
weightChange: -3,
}),
catRepo.create({
name: 'George',
color: 'white',
age: 3,
cutenessLevel: cat_entity_1.CutenessLevel.LOW,
lastVetVisit: null,
size: { height: 35, width: 12, length: 40 },
weightChange: 0,
}),
catRepo.create({
name: 'Leche',
color: 'white',
age: null,
cutenessLevel: cat_entity_1.CutenessLevel.HIGH,
lastVetVisit: null,
size: { height: 10, width: 5, length: 15 },
weightChange: -1.25,
}),
catRepo.create({
name: 'Baby',
color: 'brown',
age: 0,
cutenessLevel: cat_entity_1.CutenessLevel.HIGH,
lastVetVisit: null,
size: { height: 10, width: 5, length: 10 },
weightChange: 0.01,
}),
catRepo.create({
name: 'Adam',
color: 'black',
age: 4,
cutenessLevel: cat_entity_1.CutenessLevel.LOW,
lastVetVisit: isoStringToDate('2022-12-22T10:00:00.000Z'),
size: { height: 20, width: 15, length: 50 },
weightChange: 4.75,
}),
]);
toyShopsAddresses = await toyShopAddressRepository.save([
toyShopAddressRepository.create({ address: '123 Main St' }),
]);
toysShops = await toyShopRepo.save([
toyShopRepo.create({ shopName: 'Best Toys', address: toyShopsAddresses[0] }),
toyShopRepo.create({ shopName: 'Lovely Toys' }),
]);
catToys = await catToyRepo.save([
catToyRepo.create({ name: 'Fuzzy Thing', cat: cats[0], size: { height: 10, width: 10, length: 10 } }),
catToyRepo.create({
name: 'Stuffed Mouse',
shop: toysShops[0],
cat: cats[0],
size: { height: 5, width: 5, length: 12 },
}),
catToyRepo.create({
name: 'Mouse',
shop: toysShops[1],
cat: cats[0],
size: { height: 6, width: 4, length: 13 },
}),
catToyRepo.create({ name: 'String', cat: cats[1], size: { height: 1, width: 1, length: 50 } }),
]);
catToysWithoutShop = catToys.map((_a) => {
var { shop: _ } = _a, other = __rest(_a, ["shop"]);
const newInstance = new cat_toy_entity_1.CatToyEntity();
for (const otherKey in other) {
newInstance[otherKey] = other[otherKey];
}
return newInstance;
});
pillowBrand = await catHomePillowBrandRepo.save({ name: 'Purrfection', quality: null });
naptimePillow = await catHomePillowRepo.save({ color: 'black', brand: pillowBrand });
catHomes = await catHomeRepo.save([
catHomeRepo.create({ name: 'Box', cat: cats[0], street: null, naptimePillow: null }),
catHomeRepo.create({ name: 'House', cat: cats[1], street: 'Mainstreet', naptimePillow: null }),
catHomeRepo.create({ name: 'Mansion', cat: cats[2], street: 'Boulevard Avenue', naptimePillow }),
]);
catHomePillows = await catHomePillowRepo.save([
catHomePillowRepo.create({ color: 'red', home: catHomes[0] }),
catHomePillowRepo.create({ color: 'yellow', home: catHomes[0] }),
catHomePillowRepo.create({ color: 'blue', home: catHomes[0] }),
catHomePillowRepo.create({ color: 'pink', home: catHomes[1] }),
catHomePillowRepo.create({ color: 'purple', home: catHomes[1] }),
catHomePillowRepo.create({ color: 'teal', home: catHomes[1] }),
]);
// add friends to Milo
await catRepo.save(Object.assign(Object.assign({}, cats[0]), { friends: cats.slice(1) }));
catHairs = [];
underCoats = [];
if (process.env.DB === 'postgres') {
catHairRepo = dataSource.getRepository(cat_hair_entity_1.CatHairEntity);
catHairs = await catHairRepo.save([
catHairRepo.create({
name: 'short',
colors: ['white', 'brown', 'black'],
metadata: { length: 5, thickness: 1 },
}),
catHairRepo.create({
name: 'long',
colors: ['white', 'brown'],
metadata: { length: 20, thickness: 5 },
}),
catHairRepo.create({
name: 'buzzed',
colors: ['white'],
metadata: { length: 0.5, thickness: 10 },
}),
catHairRepo.create({ name: 'none' }),
]);
}
});
if (process.env.DB === 'postgres') {
afterAll(async () => {
const entities = dataSource.entityMetadatas;
const tableNames = entities.map((entity) => `"${entity.tableName}"`).join(', ');
await dataSource.query(`TRUNCATE ${tableNames} RESTART IDENTITY CASCADE;`);
});
}
it('should return an instance of Paginated', async () => {
const config = {
sortableColumns: ['id'],
defaultSortBy: [['id', 'ASC']],
defaultLimit: 1,
};
const query = {
path: '',
};
const result = await (0, paginate_1.paginate)(query, catRepo, config);
expect(result).toBeInstanceOf(paginate_1.Paginated);
expect(result.data).toStrictEqual(cats.slice(0, 1));
});
it('should accept a query builder', async () => {
const config = {
sortableColumns: ['id'],
defaultSortBy: [['id', 'ASC']],
defaultLimit: 1,
};
const query = {
path: '',
};
const queryBuilder = await catRepo.createQueryBuilder('cats');
const result = await (0, paginate_1.paginate)(query, queryBuilder, config);
expect(result.data).toStrictEqual(cats.slice(0, 1));
});
it('should accept a query builder with custom condition', async () => {
const config = {
sortableColumns: ['id'],
defaultSortBy: [['id', 'ASC']],
};
const query = {
path: '',
};
const queryBuilder = await dataSource
.createQueryBuilder()
.select('cats')
.from(cat_entity_1.CatEntity, 'cats')
.where('cats.color = :color', { color: 'white' });
const result = await (0, paginate_1.paginate)(query, queryBuilder, config);
expect(result.data).toStrictEqual(cats.slice(3, 5));
});
it('should accept query builder and work with query filter', async () => {
const config = {
sortableColumns: ['id'],
defaultSortBy: [['id', 'ASC']],
filterableColumns: {
'size.height': true,
},
};
const query = {
path: '',
filter: {
'size.height': '$gte:20',
},
};
const queryBuilder = await dataSource
.createQueryBuilder()
.select('cats')
.from(cat_entity_1.CatEntity, 'cats')
.where('cats.color = :color', { color: 'white' });
const result = await (0, paginate_1.paginate)(query, queryBuilder, config);
expect(result.data).toStrictEqual(cats.slice(3, 4));
});
it('should default to page 1, if negative page is given', async () => {
const config = {
sortableColumns: ['id'],
defaultLimit: 1,
};
const query = {
path: '',
page: -1,
};
const result = await (0, paginate_1.paginate)(query, catRepo, config);
expect(result.meta.currentPage).toBe(1);
expect(result.data).toStrictEqual(cats.slice(0, 1));
});
it('should default to limit maxLimit, if maxLimit is not 0', async () => {
const config = {
sortableColumns: ['id'],
maxLimit: 1,
defaultLimit: 1,
};
const query = {
path: '',
limit: paginate_1.PaginationLimit.NO_PAGINATION,
};
const result = await (0, paginate_1.paginate)(query, catRepo, config);
expect(result.data).toStrictEqual(cats.slice(0, 1));
});
it('should return all cats', async () => {
const config = {
sortableColumns: ['id'],
maxLimit: paginate_1.PaginationLimit.NO_PAGINATION,
defaultLimit: 1,
};
const query = {
path: '',
limit: paginate_1.PaginationLimit.NO_PAGINATION,
};
const result = await (0, paginate_1.paginate)(query, catRepo, config);
expect(result.data).toStrictEqual(cats);
});
it('should limit to query limit, even if maxLimit is set to NO_PAGINATION', async () => {
const config = {
sortableColumns: ['id'],
maxLimit: paginate_1.PaginationLimit.NO_PAGINATION,
};
const query = {
path: '',
limit: 2,
};
const result = await (0, paginate_1.paginate)(query, catRepo, config);
expect(result.meta.itemsPerPage).toBe(2);
});
it('should default to limit defaultLimit, if maxLimit is NO_PAGINATION', async () => {
const config = {
sortableColumns: ['id'],
maxLimit: paginate_1.PaginationLimit.NO_PAGINATION,
defaultLimit: 1,
};
const query = {
path: '',
};
const result = await (0, paginate_1.paginate)(query, catRepo, config);
expect(result.data).toStrictEqual(cats.slice(0, 1));
});
it('should default to limit maxLimit, if more than maxLimit is given', async () => {
const config = {
sortableColumns: ['id'],
defaultLimit: 5,
maxLimit: 2,
};
const query = {
path: '',
page: 1,
limit: 20,
};
const result = await (0, paginate_1.paginate)(query, catRepo, config);
expect(result.data).toStrictEqual(cats.slice(0, 2));
});
it('should limit cats by query', async () => {
const config = {
sortableColumns: ['id'],
maxLimit: Number.MAX_SAFE_INTEGER,
defaultLimit: Number.MAX_SAFE_INTEGER,
};
const query = {
path: '',
limit: 2,
};
const result = await (0, paginate_1.paginate)(query, catRepo, config);
expect(result.data).toStrictEqual(cats.slice(0, 2));
});
it('maxLimit should limit defaultLimit', async () => {
const config = {
sortableColumns: ['id'],
maxLimit: 1,
defaultLimit: 2,
};
const query = {
path: '',
};
const result = await (0, paginate_1.paginate)(query, catRepo, config);
expect(result.data).toStrictEqual(cats.slice(0, 1));
});
it('limit should bypass defaultLimit', async () => {
const config = {
sortableColumns: ['id'],
defaultLimit: 1,
};
const query = {
path: '',
limit: 2,
};
const result = await (0, paginate_1.paginate)(query, catRepo, config);
expect(result.data).toStrictEqual(cats.slice(0, 2));
});
it('DEFAULT_LIMIT should be used as the limit if limit is set to NO_PAGINATION and maxLimit is not specified.', async () => {
const config = {
sortableColumns: ['id'],
};
const query = {
path: '',
limit: paginate_1.PaginationLimit.NO_PAGINATION,
};
const result = await (0, paginate_1.paginate)(query, catRepo, config);
expect(result.data).toStrictEqual(cats.slice(0, paginate_1.PaginationLimit.DEFAULT_LIMIT));
});
it('should return the count without data ignoring maxLimit if limit is COUNTER_ONLY', async () => {
const config = {
sortableColumns: ['id'],
maxLimit: paginate_1.PaginationLimit.NO_PAGINATION,
};
const query = {
path: '',
limit: 0,
};
const result = await (0, paginate_1.paginate)(query, catRepo, config);
expect(result.data).toStrictEqual([]);
expect(result.meta.totalItems).toBe(7);
});
it('should return correct result for limited one-to-many relations', async () => {
const config = {
relations: ['toys'],
sortableColumns: ['id', 'toys.id'],
searchableColumns: ['name', 'toys.name'],
defaultLimit: 4,
};
const query = {
path: '',
};
const result = await (0, paginate_1.paginate)(query, catRepo, config);
expect(result.data.length).toStrictEqual(4);
});
it('should return correct links for some results', async () => {
const config = {
sortableColumns: ['id'],
};
const query = {
path: '',
page: 2,
limit: 2,
};
const { links } = await (0, paginate_1.paginate)(query, catRepo, config);
expect(links.first).toBe('?page=1&limit=2&sortBy=id:ASC');
expect(links.previous).toBe('?page=1&limit=2&sortBy=id:ASC');
expect(links.current).toBe('?page=2&limit=2&sortBy=id:ASC');
expect(links.next).toBe('?page=3&limit=2&sortBy=id:ASC');
expect(links.last).toBe('?page=4&limit=2&sortBy=id:ASC');
});
it('should return a relative path', async () => {
const config = {
sortableColumns: ['id'],
relativePath: true,
};
const query = {
path: 'http://localhost/cats',
page: 2,
limit: 2,
};
const { links } = await (0, paginate_1.paginate)(query, catRepo, config);
expect(links.first).toBe('/cats?page=1&limit=2&sortBy=id:ASC');
expect(links.previous).toBe('/cats?page=1&limit=2&sortBy=id:ASC');
expect(links.current).toBe('/cats?page=2&limit=2&sortBy=id:ASC');
expect(links.next).toBe('/cats?page=3&limit=2&sortBy=id:ASC');
expect(links.last).toBe('/cats?page=4&limit=2&sortBy=id:ASC');
});
it('should return an absolute path', async () => {
const config = {
sortableColumns: ['id'],
relativePath: false,
};
const query = {
path: 'http://localhost/cats',
page: 2,
limit: 2,
};
const { links } = await (0, paginate_1.paginate)(query, catRepo, config);
expect(links.first).toBe('http://localhost/cats?page=1&limit=2&sortBy=id:ASC');
expect(links.previous).toBe('http://localhost/cats?page=1&limit=2&sortBy=id:ASC');
expect(links.current).toBe('http://localhost/cats?page=2&limit=2&sortBy=id:ASC');
expect(links.next).toBe('http://localhost/cats?page=3&limit=2&sortBy=id:ASC');
expect(links.last).toBe('http://localhost/cats?page=4&limit=2&sortBy=id:ASC');
});
it('should return an absolute path with new origin', async () => {
const config = {
sortableColumns: ['id'],
relativePath: false,
origin: 'http://cats.example',
};
const query = {
path: 'http://localhost/cats',
page: 2,
limit: 2,
};
const { links } = await (0, paginate_1.paginate)(query, catRepo, config);
expect(links.first).toBe('http://cats.example/cats?page=1&limit=2&sortBy=id:ASC');
expect(links.previous).toBe('http://cats.example/cats?page=1&limit=2&sortBy=id:ASC');
expect(links.current).toBe('http://cats.example/cats?page=2&limit=2&sortBy=id:ASC');
expect(links.next).toBe('http://cats.example/cats?page=3&limit=2&sortBy=id:ASC');
expect(links.last).toBe('http://cats.example/cats?page=4&limit=2&sortBy=id:ASC');
});
it('should return only current link if zero results', async () => {
const config = {
sortableColumns: ['id'],
searchableColumns: ['name'],
};
const query = {
path: '',
page: 1,
limit: 2,
search: 'Pluto',
};
const { links } = await (0, paginate_1.paginate)(query, catRepo, config);
expect(links.first).toBe(undefined);
expect(links.previous).toBe(undefined);
expect(links.current).toBe('?page=1&limit=2&sortBy=id:ASC&search=Pluto');
expect(links.next).toBe(undefined);
expect(links.last).toBe(undefined);
});
it('should default to defaultSortBy if query sortBy does not exist', async () => {
const config = {
sortableColumns: ['id', 'createdAt'],
defaultSortBy: [['id', 'DESC']],
};
const query = {
path: '',
};
const result = await (0, paginate_1.paginate)(query, catRepo, config);
expect(result.meta.sortBy).toStrictEqual([['id', 'DESC']]);
expect(result.data).toStrictEqual(cats.slice(0).reverse());
});
it('should put null values last when sorting', async () => {
const config = {
sortableColumns: ['age', 'createdAt'],
nullSort: 'last',
defaultSortBy: [['age', 'ASC']],
};
const query = {
path: '',
};
const result = await (0, paginate_1.paginate)(query, catRepo, config);
// Extracting the indexes of non-null values and null values
const notNullIndexes = result.data
.map((cat, index) => (cat.age !== null ? index : -1))
.filter((index) => index !== -1);
const nullIndexes = result.data
.map((cat, index) => (cat.age === null ? index : -1))
.filter((index) => index !== -1);
expect(result.meta.sortBy).toStrictEqual([['age', 'ASC']]);
expect(Math.max(...notNullIndexes)).toBeLessThan(Math.min(...nullIndexes));
});
it('should put null values first when sorting', async () => {
const config = {
sortableColumns: ['age', 'createdAt'],
nullSort: 'first',
defaultSortBy: [['age', 'ASC']],
};
const query = {
path: '',
};
const result = await (0, paginate_1.paginate)(query, catRepo, config);
const nullIndexes = result.data
.map((cat, index) => (cat.age === null ? index : -1))
.filter((index) => index !== -1);
const notNullIndexes = result.data
.map((cat, index) => (cat.age !== null ? index : -1))
.filter((index) => index !== -1);
expect(result.meta.sortBy).toStrictEqual([['age', 'ASC']]);
expect(Math.max(...nullIndexes)).toBeLessThan(Math.min(...notNullIndexes));
});
it('should sort result by multiple columns', async () => {
const config = {
sortableColumns: ['name', 'color'],
};
const query = {
path: '',
sortBy: [
['color', 'DESC'],
['name', 'ASC'],
],
};
const result = await (0, paginate_1.paginate)(query, catRepo, config);
const sortedCats = cats.slice(0).sort((a, b) => {
if (a.color === b.color) {
return a.name.localeCompare(b.name);
}
return b.color.localeCompare(a.color);
});
expect(result.meta.sortBy).toStrictEqual([
['color', 'DESC'],
['name', 'ASC'],
]);
expect(result.data).toStrictEqual(sortedCats);
});
it('should sort result by camelcase columns', async () => {
const config = {
sortableColumns: ['cutenessLevel', 'name'],
};
const query = {
path: '',
sortBy: [
['cutenessLevel', 'ASC'],
['name', 'ASC'],
],
};
const result = await (0, paginate_1.paginate)(query, catRepo, config);
const sortedCats = cats.slice(0).sort((a, b) => {
if (a.cutenessLevel === b.cutenessLevel) {
return a.name.localeCompare(b.name);
}
return a.cutenessLevel.localeCompare(b.cutenessLevel);
});
expect(result.meta.sortBy).toStrictEqual([
['cutenessLevel', 'ASC'],
['name', 'ASC'],
]);
expect(result.data).toStrictEqual(sortedCats);
});
it('should return result based on search term', async () => {
const config = {
sortableColumns: ['id', 'name', 'color'],
searchableColumns: ['name', 'color'],
};
const query = {
path: '',
search: 'i',
};
const result = await (0, paginate_1.paginate)(query, catRepo, config);
expect(result.meta.search).toStrictEqual('i');
expect(result.data).toStrictEqual([cats[0], cats[1], cats[3], cats[4]]);
expect(result.links.current).toBe('?page=1&limit=20&sortBy=id:ASC&search=i');
});
it('should return result based on search term on a camelcase named column', async () => {
const config = {
sortableColumns: ['id', 'name', 'color'],
searchableColumns: ['cutenessLevel'],
};
const query = {
path: '',
search: 'hi',
};
const result = await (0, paginate_1.paginate)(query, catRepo, config);
const expectedCats = cats.filter((cat) => cat.cutenessLevel === cat_entity_1.CutenessLevel.HIGH);
expect(result.meta.search).toStrictEqual('hi');
expect(result.data).toStrictEqual(expectedCats);
expect(result.links.current).toBe('?page=1&limit=20&sortBy=id:ASC&search=hi');
});
it('should not result in a sql syntax error when attempting a sql injection', async () => {
const config = {
sortableColumns: ['id', 'name', 'color'],
searchableColumns: ['name', 'color'],
};
const query = {
path: '',
search: "i UNION SELECT tbl_name FROM sqlite_master WHERE type='table' and tbl_name NOT like 'sqlite_%'",
};
const result = await (0, paginate_1.paginate)(query, catRepo, config);
expect(result.data).toStrictEqual([]);
});
it('should return result based on search term on many-to-one relation', async () => {
const config = {
relations: ['cat'],
sortableColumns: ['id', 'name'],
searchableColumns: ['name', 'cat.name'],
};
const query = {
path: '',
search: 'Milo',
};
const result = await (0, paginate_1.paginate)(query, catToyRepo, config);
expect(result.meta.search).toStrictEqual('Milo');
expect(result.data).toStrictEqual([catToysWithoutShop[0], catToysWithoutShop[1], catToysWithoutShop[2]]);
expect(result.links.current).toBe('?page=1&limit=20&sortBy=id:ASC&search=Milo');
});
it('should return result based on search term on one-to-many relation', async () => {
const config = {
relations: ['toys'],
sortableColumns: ['id', 'toys.id'],
searchableColumns: ['name', 'toys.name'],
};
const query = {
path: '',
search: 'Mouse',
sortBy: [
['id', 'ASC'],
['toys.id', 'DESC'],
],
};
const result = await (0, paginate_1.paginate)(query, catRepo, config);
expect(result.meta.search).toStrictEqual('Mouse');
const toy = (0, lodash_1.clone)(catToysWithoutShop[1]);
delete toy.cat;
const toy2 = (0, lodash_1.clone)(catToysWithoutShop[2]);
delete toy2.cat;
expect(result.data).toStrictEqual([Object.assign((0, lodash_1.clone)(cats[0]), { toys: [toy2, toy] })]);
expect(result.links.current).toBe('?page=1&limit=20&sortBy=id:ASC&sortBy=toys.id:DESC&search=Mouse');
});
it('should return result based on search term on one-to-one relation', async () => {
const config = {
relations: ['cat', 'naptimePillow.brand'],
sortableColumns: ['id', 'name', 'cat.id'],
};
const query = {
path: '',
sortBy: [['cat.id', 'DESC']],
};
const result = await (0, paginate_1.paginate)(query, catHomeRepo, config);
expect(result.meta.sortBy).toStrictEqual([['cat.id', 'DESC']]);
const catHomesClone = (0, lodash_1.clone)([catHomes[0], catHomes[1], catHomes[2]]);
catHomesClone[0].countCat = cats.filter((cat) => cat.id === catHomesClone[0].cat.id).length;
catHomesClone[1].countCat = cats.filter((cat) => cat.id === catHomesClone[1].cat.id).length;
catHomesClone[2].countCat = cats.filter((cat) => cat.id === catHomesClone[2].cat.id).length;
expect(result.data).toStrictEqual(catHomesClone.sort((a, b) => b.cat.id - a.cat.id));
expect(result.links.current).toBe('?page=1&limit=20&sortBy=cat.id:DESC');
});
it('should return result based on sort and search on many-to-one relation', async () => {
const config = {
relations: ['cat'],
sortableColumns: ['id', 'name', 'cat.id'],
searchableColumns: ['name', 'cat.name'],
};
const query = {
path: '',
sortBy: [['cat.id', 'DESC']],
search: 'Milo',
};
const result = await (0, paginate_1.paginate)(query, catToyRepo, config);
expect(result.meta.search).toStrictEqual('Milo');
expect(result.data).toStrictEqual([catToysWithoutShop[0], catToysWithoutShop[1], catToysWithoutShop[2]].sort((a, b) => b.cat.id - a.cat.id));
expect(result.links.current).toBe('?page=1&limit=20&sortBy=cat.id:DESC&search=Milo');
});
it('should return result based on sort on one-to-many relation', async () => {
const config = {
relations: ['toys', 'toys.shop', 'toys.shop.address'],
sortableColumns: ['id', 'name', 'toys.id'],
searchableColumns: ['name', 'toys.name'],
};
const query = {
path: '',
sortBy: [['toys.id', 'DESC']],
search: 'Mouse',
};
const result = await (0, paginate_1.paginate)(query, catRepo, config);
expect(result.meta.search).toStrictEqual('Mouse');
const toy1 = (0, lodash_1.clone)(catToys[1]);
delete toy1.cat;
const toy2 = (0, lodash_1.clone)(catToys[2]);
delete toy2.cat;
delete result.data[0].toys[0].shop.address;
expect(result.data).toStrictEqual([Object.assign((0, lodash_1.clone)(cats[0]), { toys: [toy2, toy1] })]);
expect(result.links.current).toBe('?page=1&limit=20&sortBy=toys.id:DESC&search=Mouse');
});
it('should return result based on sort on one-to-one relation', async () => {
const config = {
relations: ['cat', 'naptimePillow.brand'],
sortableColumns: ['id', 'name'],
searchableColumns: ['name', 'cat.name'],
};
const query = {
path: '',
search: 'Garfield',
};
const result = await (0, paginate_1.paginate)(query, catHomeRepo, config);
expect(result.meta.search).toStrictEqual('Garfield');
const catHomesClone = (0, lodash_1.clone)(catHomes[1]);
catHomesClone.countCat = cats.filter((cat) => cat.id === catHomesClone.cat.id).length;
expect(result.data).toStrictEqual([catHomesClone]);
expect(result.links.current).toBe('?page=1&limit=20&sortBy=id:ASC&search=Garfield');
});
it('should load nested relations (object notation)', async () => {
const config = {
relations: { home: { pillows: true, naptimePillow: { brand: true } } },
sortableColumns: ['id', 'name'],
searchableColumns: ['name'],
};
const query = {
path: '',
search: 'Garfield',
};
const result = await (0, paginate_1.paginate)(query, catRepo, config);
const cat = (0, lodash_1.clone)(cats[1]);
const catHomesClone = (0, lodash_1.clone)(catHomes[1]);
const catHomePillowsClone3 = (0, lodash_1.clone)(catHomePillows[3]);
delete catHomePillowsClone3.home;
const catHomePillowsClone4 = (0, lodash_1.clone)(catHomePillows[4]);
delete catHomePillowsClone4.home;
const catHomePillowsClone5 = (0, lodash_1.clone)(catHomePillows[5]);
delete catHomePillowsClone5.home;
catHomesClone.countCat = cats.filter((cat) => cat.id === catHomesClone.cat.id).length;
catHomesClone.pillows = [catHomePillowsClone3, catHomePillowsClone4, catHomePillowsClone5];
cat.home = catHomesClone;
delete cat.home.cat;
expect(result.meta.search).toStrictEqual('Garfield');
expect(result.data).toStrictEqual([cat]);
expect(result.data[0].home).toBeDefined();
expect(result.data[0].home.pillows).toStrictEqual(cat.home.pillows);
});
it('should load nested relations (array notation)', async () => {
const config = {
relations: ['home.pillows', 'home.naptimePillow.brand'],
sortableColumns: ['id', 'name'],
searchableColumns: ['name'],
};
const query = {
path: '',
search: 'Garfield',
};
const result = await (0, paginate_1.paginate)(query, catRepo, config);
const cat = (0, lodash_1.clone)(cats[1]);
const catHomesClone = (0, lodash_1.clone)(catHomes[1]);
const catHomePillowsClone3 = (0, lodash_1.clone)(catHomePillows[3]);
delete catHomePillowsClone3.home;
const catHomePillowsClone4 = (0, lodash_1.clone)(catHomePillows[4]);
delete catHomePillowsClone4.home;
const catHomePillowsClone5 = (0, lodash_1.clone)(catHomePillows[5]);
delete catHomePillowsClone5.home;
catHomesClone.countCat = cats.filter((cat) => cat.id === catHomesClone.cat.id).length;
catHomesClone.pillows = [catHomePillowsClone3, catHomePillowsClone4, catHomePillowsClone5];
cat.home = catHomesClone;
delete cat.home.cat;
expect(result.meta.search).toStrictEqual('Garfield');
expect(result.data).toStrictEqual([cat]);
expect(result.data[0].home).toBeDefined();
expect(result.data[0].home.pillows).toStrictEqual(cat.home.pillows);
});
it('should throw an error when nonexistent relation loaded', async () => {
const config = {
relations: ['homee'],
sortableColumns: ['id'],
};
const query = {
path: '',
};
try {
await (0, paginate_1.paginate)(query, catRepo, config);
}
catch (err) {
expect(err).toBeInstanceOf(typeorm_1.TypeORMError);
}
});
it('should return result based on search term and searchBy columns', async () => {
const config = {
sortableColumns: ['id', 'name', 'color'],
searchableColumns: ['name', 'color'],
};
const searchTerm = 'white';
const expectedResultData = cats.filter((cat) => cat.color === searchTerm);
const query = {
path: '',
search: searchTerm,
searchBy: ['color'],
};
const result = await (0, paginate_1.paginate)(query, catRepo, config);
expect(result.meta.search).toStrictEqual(searchTerm);
expect(result.meta.searchBy).toStrictEqual(['color']);
expect(result.data).toStrictEqual(expectedResultData);
expect(result.links.current).toBe('?page=1&limit=20&sortBy=id:ASC&search=white&searchBy=color');
});
it('should return result based on where config and filter', async () => {
const config = {
sortableColumns: ['id'],
where: {
color: 'white',
},
filterableColumns: {
name: [filter_1.FilterSuffix.NOT],
},
};
const query = {
path: '',
filter: {
name: '$not:Leche',
},
};
const result = await (0, paginate_1.paginate)(query, catRepo, config);
expect(result.meta.filter).toStrictEqual({
name: '$not:Leche',
});
expect(result.data).toStrictEqual([cats[3]]);
expect(result.links.current).toBe('?page=1&limit=20&sortBy=id:ASC&filter.name=$not:Leche');
});
it('should return based on a nested many-to-one where condition', async () => {
const config = {
sortableColumns: ['id'],
relations: ['cat'],
where: {
cat: {
id: cats[0].id,
},
},
};
const query = {
path: '',
};
const result = await (0, paginate_1.paginate)(query, catToyRepo, config);
expect(result.meta.totalItems).toBe(3);
result.data.forEach((toy) => {
expect(toy.cat.id).toBe(cats[0].id);
});
expect(result.links.current).toBe('?page=1&limit=20&sortBy=id:ASC');
});
it('should return valid data filtering by not id field many-to-one', async () => {
const config = {
sortableColumns: ['id', 'name'],
relations: ['cat'],
where: {
cat: {
name: cats[0].name,
},
},
};
const query = {
path: '',
};
const result = await (0, paginate_1.paginate)(query, catToyRepo, config);
expect(result.meta.totalItems).toBe(3);
result.data.forEach((toy) => {
expect(toy.cat.id).toBe(cats[0].id);
});
expect(result.links.current).toBe('?page=1&limit=20&sortBy=id:ASC');
});
it('should return result based on where one-to-many relation', async () => {
const config = {
relations: ['toys'],
sortableColumns: ['id', 'name'],
where: {
toys: {
name: 'Stuffed Mouse',
},
},
};
const query = {
path: '',
};
const result = await (0, paginate_1.paginate)(query, catRepo, config);
expect(result.data.length).toBe(1);
expect(result.data[0].toys.length).toBe(1);
expect(result.data[0].toys[0].name).toBe('Stuffed Mouse');
});
it('should return all cats with a toys from the lovely shop', async () => {
const config = {
relations: ['toys', 'toys.shop'],
sortableColumns: ['id', 'name'],
where: {
toys: {
shop: {
shopName: 'Lovely Toys',
},
},
},
};
const query = {
path: '',
};
const result = await (0, paginate_1.paginate)(query, catRepo, config);
expect(result.data.length).toBe(1);
expect(result.data[0].toys.length).toBe(1);
expect(result.data[0].toys[0].shop.id).toStrictEqual(toysShops[1].id);
expect(result.data[0].toys[0].name).toBe('Mouse');
});
it('should return all cats from shop where street name like 123', async () => {
const config = {
relations: ['toys', 'toys.shop', 'toys.shop.address'],
sortableColumns: ['id', 'name'],
where: {
toys: {
shop: {
address: {
address: (0, typeorm_1.Like)('%123%'),
},
},
},
},
};
const query = {
path: '',
};
const result = await (0, paginate_1.paginate)(query, catRepo, config);
expect(result.data.length).toBe(1);
expect(result.data[0].toys.length).toBe(1);
expect(result.data[0].toys[0].shop).toStrictEqual(toysShops[0]);
expect(result.data[0].toys[0].name).toBe('Stuffed Mouse');
});
it('should return result based on filter on many-to-one relation', async () => {
const config = {
relations: ['cat'],
sortableColumns: ['id', 'name'],
filterableColumns: {
'cat.name': [filter_1.FilterSuffix.NOT],
},
};
const query = {
path: '',
filter: {
'cat.name': '$not:Milo',
},
};
const result = await (0, paginate_1.paginate)(query, catToyRepo, config);
expect(result.meta.filter).toStrictEqual({
'cat.name': '$not:Milo',
});
expect(result.data).toStrictEqual([catToys[3]]);
expect(result.links.current).toBe('?page=1&limit=20&sortBy=id:ASC&filter.cat.name=$not:Milo');
});
it('should be possible to filter by relation without loading it', async () => {
const config = {
relations: ['cat'],
sortableColumns: ['id'],
where: { cat: { toys: { name: catToys[0].name } } },
};
const query = {
path: '',
};
const result = await (0, paginate_1.paginate)(query, catToyRepo, config);
expect(result.data.length).toStrictEqual(3);
});
it('should be possible to filter by relation without loading it 4th level', async () => {
const config = {
relations: ['cat'],
sortableColumns: ['id'],
where: { cat: { toys: { shop: { address: { address: (0, typeorm_1.Like)('%123%') } } } } },
};
const query = {
path: '',
};
const result = await (0, paginate_1.paginate)(query, catToyRepo, config);
expect(result.data.length).toStrictEqual(3);
});
it('should be possible to filter by relation without loading it 4th level with load eager', async () => {
const config = {
loadEagerRelations: true,
sortableColumns: ['id'],
where: { cat: { toys: { shop: { address: { address: (0, typeorm_1.Like)('%123%') } } } } },
};
const query = {
path: '',
};
const result = await (0, paginate_1.paginate)(query, catToyRepo, config);
expect(result.data.length).toStrictEqual(3);
});
it('should be possible to filter by relation without including any relations', async () => {
const config = {
loadEagerRelations: false,
sortableColumns: ['id'],
where: { cat: { toys: { shop: { address: { address: (0, typeorm_1.Like)('%123%') } } } } },
};
const query = {
path: '',
};
const result = await (0, paginate_1.paginate)(query, catToyRepo, config);
expect(result.data.length).toStrictEqual(3);
});
it('should return result based on filter on one-to-many relation', async () => {
const config = {
relations: ['toys'],
sortableColumns: ['id', 'name'],
filterableColumns: {
'toys.name': [filter_1.FilterSuffix.NOT],
},
};
const query = {
path: '',
filter: {
'toys.name': '$not:Stuffed Mouse',
},
};
const result = await (0, paginate_1.paginate)(query, catRepo, config);
const cat1 = (0, lodash_1.clone)(cats[0]);
const cat2 = (0, lodash_1.clone)(cats[1]);
const catToys1 = (0, lodash_1.clone)(catToysWithoutShop[0]);
const catToys2 = (0, lodash_1.clone)(catToysWithoutShop[2]);
const catToys3 = (0, lodash_1.clone)(catToysWithoutShop[3]);
delete catToys1.cat;
delete catToys2.cat;
delete catToys3.cat;
cat1.toys = [catToys1, catToys2];
cat2.toys = [catToys3];
expect(result.meta.filter).toStrictEqual({
'toys.name': '$not:Stuffed Mouse',
});
expect(result.data).toStrictEqual([cat1, cat2]);
expect(result.links.current).toBe('?page=1&limit=20&sortBy=id:ASC&filter.toys.name=$not:Stuffed Mouse');
});
it('should return result based on filter on one-to-one relation', async () => {
const config = {
relations: ['cat', 'naptimePillow.brand'],
sortableColumns: ['id', 'name'],
filterableColumns: {
'cat.name': [filter_1.FilterSuffix.NOT],
},
};
const query = {
path: '',
filter: {
'cat.name': '$not:Garfield',
},
};
const result = await (0, paginate_1.paginate)(query, catHomeRepo, config);
expect(result.meta.filter).toStrictEqual({
'cat.name': '$not:Garfield',
});
const catHomesClones = [(0, lodash_1.clone)(catHomes[0]), (0, lodash_1.clone)(catHomes[2])];
catHomesClones[0].countCat = cats.filter((cat) => cat.id === catHomesClones[0].cat.id).length;
catHomesClones[1].countCat = cats.filter((cat) => cat.id === catHomesClones[1].cat.id).length;
expect(result.data).toStrictEqual(catHomesClones);
expect(result.links.current).toBe('?page=1&limit=20&sortBy=id:ASC&filter.cat.name=$not:Garfield');
});
it('should return result based on $in filter on one-to-one relation', async () => {
const config = {
relations: ['cat', 'naptimePillow.brand'],
sortableColumns: ['id', 'name'],
filterableColumns: {
'cat.age': [filter_1.FilterOperator.IN],
},
};
const query = {
path: '',
filter: {
'cat.age': '$in:4,6',
},
};
const result = await (0, paginate_1.paginate)(query, catHomeRepo, config);
expect(result.meta.filter).toStrictEqual({
'cat.age': '$in:4,6',
});
const catHomesClones = [(0, lodash_1.clone)(catHomes[0]), (0, lodash_1.clone)(catHomes[2])];
catHomesClones[0].countCat = cats.filter((cat) => cat.id === catHomesClones[0].cat.id).length;
catHomesClones[1].countCat = cats.filter((cat) => cat.id === catHomesClones[1].cat.id).length;
expect(result.data).toStrictEqual(catHomesClones);
expect(result.links.current).toBe('?page=1&limit=20&sortBy=id:ASC&filter.cat.age=$in:4,6');
});
it('should return result based on $btw filter on one-to-one relation', async () => {
const config = {
relations: ['cat', 'naptimePillow.brand'],
sortableColumns: ['id', 'name'],
filterableColumns: {
'cat.age': [filter_1.FilterOperator.BTW],
},
};
const query = {
path: '',
filter: {
'cat.age': '$btw:6,10',
},
};
const result = await (0, paginate_1.paginate)(query, catHomeRepo, config);
expect(result.meta.filter).toStrictEqual({
'cat.age': '$btw:6,10',
});
const catHomesClone = (0, lodash_1.clone)(catHomes[0]);
catHomesClone.countCat = cats.filter((cat) => cat.id === catHomesClone.cat.id).length;
expect(result.data).toStrictEqual([catHomesClone]);
expect(result.links.current).toBe('?page=1&limit=20&sortBy=id:ASC&filter.cat.age=$btw:6,10');
});
it('should return result based on sort on embedded entity', async () => {
const config = {
sortableColumns: ['id', 'name', 'size.height', 'size.length', 'size.width'],
searchableColumns: ['name'],
};
const query = {
path: '',
sortBy: [
['size.height', 'ASC'],
['size.length', 'ASC'],
],
};
const result = await (0, paginate_1.paginate)(query, catRepo, config);