diff --git a/.codeclimate.yml b/.codeclimate.yml index fc88d5cceb..ee76ac2f57 100644 --- a/.codeclimate.yml +++ b/.codeclimate.yml @@ -37,6 +37,7 @@ plugins: count_threshold: 3 exclude_patterns: - "**/test/*" + - "**/adapter-commons/src/sort.ts" - "**/adapter-tests/lib/*" - "**/dist/*" - "**/*.dist.js" diff --git a/.gitignore b/.gitignore index 92ae34318d..5ec76ed6a5 100644 --- a/.gitignore +++ b/.gitignore @@ -40,6 +40,7 @@ dist/ .mocha-puppeteer # TypeScript compiled files +packages/adapter-commons/lib packages/authentication/lib packages/authentication-local/lib packages/authentication-client/lib diff --git a/.nycrc b/.nycrc index a13ed2d88a..006dee8616 100644 --- a/.nycrc +++ b/.nycrc @@ -15,7 +15,8 @@ "**/test/*", "**/dist/*", "**/*.dist.js", - "**/templates/*" + "**/templates/*", + "**/adapter-commons/src/sort.ts" ], "print": "detail", "reporter": [ diff --git a/packages/adapter-commons/.npmignore b/packages/adapter-commons/.npmignore index 65e3ba2eda..d423d51243 100644 --- a/packages/adapter-commons/.npmignore +++ b/packages/adapter-commons/.npmignore @@ -1 +1,2 @@ test/ +tsconfig.json diff --git a/packages/adapter-commons/package.json b/packages/adapter-commons/package.json index 2fddc9e49d..f349278072 100644 --- a/packages/adapter-commons/package.json +++ b/packages/adapter-commons/package.json @@ -25,7 +25,9 @@ }, "main": "lib/index.js", "scripts": { - "test": "mocha --opts ../../mocha.opts" + "prepublish": "npm run compile", + "compile": "shx rm -rf lib/ && tsc", + "test": "mocha --opts ../../mocha.ts.opts --recursive test/**.test.ts test/**/*.test.ts" }, "directories": { "lib": "lib" @@ -33,13 +35,20 @@ "publishConfig": { "access": "public" }, - "devDependencies": { - "mocha": "^6.1.4", - "mongodb": "^3.2.6" - }, "dependencies": { + "@feathersjs/feathers": "^4.0.0-pre.3", "@feathersjs/commons": "^4.0.0-pre.3", "@feathersjs/errors": "^4.0.0-pre.3" }, - "gitHead": "19eb75737659e3e4b57765c1653d23c86fd2e1c3" + "devDependencies": { + "@types/mongodb": "^3.1.28", + "mocha": "^6.1.4", + "mongodb": "^3.2.6", + "@types/mocha": "^5.2.6", + "@types/node": "^12.0.2", + "mocha": "^6.1.4", + "shx": "^0.3.2", + "ts-node": "^8.2.0", + "typescript": "^3.4.5" + } } diff --git a/packages/adapter-commons/lib/filter-query.js b/packages/adapter-commons/src/filter-query.ts similarity index 68% rename from packages/adapter-commons/lib/filter-query.js rename to packages/adapter-commons/src/filter-query.ts index 6f63677cbb..2d2be325eb 100644 --- a/packages/adapter-commons/lib/filter-query.js +++ b/packages/adapter-commons/src/filter-query.ts @@ -1,15 +1,17 @@ -const { _ } = require('@feathersjs/commons'); -const { BadRequest } = require('@feathersjs/errors'); +import { _ } from '@feathersjs/commons'; +import { BadRequest } from '@feathersjs/errors'; -function parse (number) { +function parse (number: any) { if (typeof number !== 'undefined') { return Math.abs(parseInt(number, 10)); } + + return undefined; } // Returns the pagination limit and will take into account the // default and max pagination settings -function getLimit (limit, paginate) { +function getLimit (limit: any, paginate: any) { if (paginate && paginate.default) { const lower = typeof limit === 'number' ? limit : paginate.default; const upper = typeof paginate.max === 'number' ? paginate.max : Number.MAX_VALUE; @@ -21,7 +23,7 @@ function getLimit (limit, paginate) { } // Makes sure that $sort order is always converted to an actual number -function convertSort (sort) { +function convertSort (sort: any) { if (typeof sort !== 'object' || Array.isArray(sort)) { return sort; } @@ -31,12 +33,12 @@ function convertSort (sort) { ? sort[key] : parseInt(sort[key], 10); return result; - }, {}); + }, {} as { [key: string]: number }); } -function cleanQuery (query, operators, filters) { +function cleanQuery (query: any, operators: any, filters: any) { if (_.isObject(query) && query.constructor === {}.constructor) { - const result = {}; + const result: { [key: string]: any } = {}; _.each(query, (value, key) => { if (key[0] === '$') { @@ -53,6 +55,7 @@ function cleanQuery (query, operators, filters) { }); Object.getOwnPropertySymbols(query).forEach(symbol => { + // @ts-ignore result[symbol] = query[symbol]; }); @@ -62,7 +65,7 @@ function cleanQuery (query, operators, filters) { return query; } -function assignFilters (object, query, filters, options) { +function assignFilters (object: any, query: any, filters: any, options: any) { if (Array.isArray(filters)) { _.each(filters, (key) => { if (query[key] !== undefined) { @@ -82,24 +85,24 @@ function assignFilters (object, query, filters, options) { return object; } -const FILTERS = { - $sort: (value) => convertSort(value), - $limit: (value, options) => getLimit(parse(value), options.paginate), - $skip: (value) => parse(value), - $select: (value) => value +export const FILTERS = { + $sort: (value: any) => convertSort(value), + $limit: (value: any, options: any) => getLimit(parse(value), options.paginate), + $skip: (value: any) => parse(value), + $select: (value: any) => value }; -const OPERATORS = ['$in', '$nin', '$lt', '$lte', '$gt', '$gte', '$ne', '$or']; +export const OPERATORS = ['$in', '$nin', '$lt', '$lte', '$gt', '$gte', '$ne', '$or']; // Converts Feathers special query parameters and pagination settings // and returns them separately a `filters` and the rest of the query // as `query` -module.exports = function filterQuery (query, options = {}) { +export default function filterQuery (query: any, options: any = {}) { const { filters: additionalFilters = {}, operators: additionalOperators = [] } = options; - const result = {}; + const result: { [key: string]: any } = {}; result.filters = assignFilters({}, query, FILTERS, options); result.filters = assignFilters(result.filters, query, additionalFilters, options); @@ -107,6 +110,8 @@ module.exports = function filterQuery (query, options = {}) { result.query = cleanQuery(query, OPERATORS.concat(additionalOperators), result.filters); return result; -}; +} -Object.assign(module.exports, { OPERATORS, FILTERS }); +if (typeof module !== 'undefined') { + module.exports = Object.assign(filterQuery, module.exports); +} diff --git a/packages/adapter-commons/lib/index.js b/packages/adapter-commons/src/index.ts similarity index 58% rename from packages/adapter-commons/lib/index.js rename to packages/adapter-commons/src/index.ts index ee2ab501e1..d5ad7d0399 100644 --- a/packages/adapter-commons/lib/index.js +++ b/packages/adapter-commons/src/index.ts @@ -1,20 +1,20 @@ -const { _ } = require('@feathersjs/commons'); +import { _ } from '@feathersjs/commons'; -const AdapterService = require('./service'); -const filterQuery = require('./filter-query'); -const sort = require('./sort'); +export { AdapterService, InternalServiceMethods, ServiceOptions } from './service'; +export { default as filterQuery, FILTERS, OPERATORS } from './filter-query'; +export * from './sort'; // Return a function that filters a result object or array // and picks only the fields passed as `params.query.$select` // and additional `otherFields` -const select = function select (params, ...otherFields) { +export function select (params: any, ...otherFields: any[]) { const fields = params && params.query && params.query.$select; if (Array.isArray(fields) && otherFields.length) { fields.push(...otherFields); } - const convert = result => { + const convert = (result: any) => { if (!Array.isArray(fields)) { return result; } @@ -22,17 +22,11 @@ const select = function select (params, ...otherFields) { return _.pick(result, ...fields); }; - return result => { + return (result: any) => { if (Array.isArray(result)) { return result.map(convert); } return convert(result); }; -}; - -module.exports = Object.assign({ - select, - filterQuery, - AdapterService -}, sort); +} diff --git a/packages/adapter-commons/lib/service.js b/packages/adapter-commons/src/service.ts similarity index 55% rename from packages/adapter-commons/lib/service.js rename to packages/adapter-commons/src/service.ts index efbf192af5..a3d33d7c95 100644 --- a/packages/adapter-commons/lib/service.js +++ b/packages/adapter-commons/src/service.ts @@ -1,7 +1,8 @@ -const { NotImplemented, BadRequest, MethodNotAllowed } = require('@feathersjs/errors'); -const filterQuery = require('./filter-query'); +import { NotImplemented, BadRequest, MethodNotAllowed } from '@feathersjs/errors'; +import { ServiceMethods, Params, Paginated, Id, NullableId } from '@feathersjs/feathers'; +import filterQuery from './filter-query'; -const callMethod = (self, name, ...args) => { +const callMethod = (self: any, name: any, ...args: any[]) => { if (typeof self[name] !== 'function') { return Promise.reject(new NotImplemented(`Method ${name} not available`)); } @@ -9,18 +10,41 @@ const callMethod = (self, name, ...args) => { return self[name](...args); }; -const alwaysMulti = { +const alwaysMulti: { [key: string]: boolean } = { find: true, get: false, update: false }; -module.exports = class AdapterService { - constructor (options) { +export interface ServiceOptions { + events: string[]; + multi: boolean|string[]; + id: string; + paginate: any; + whitelist: string[]; + filters: string[]; +} + +export interface InternalServiceMethods { + _find (params?: Params): Promise>; + _get (id: Id, params?: Params): Promise; + _create (data: Partial | Array>, params?: Params): Promise; + _update (id: NullableId, data: T, params?: Params): Promise; + _patch (id: NullableId, data: Partial, params?: Params): Promise; + _remove (id: NullableId, params?: Params): Promise; +} + +export class AdapterService implements ServiceMethods { + options: ServiceOptions; + + constructor (options: Partial) { this.options = Object.assign({ + id: 'id', events: [], paginate: {}, - multi: false + multi: false, + filters: [], + whitelist: [] }, options); } @@ -32,7 +56,7 @@ module.exports = class AdapterService { return this.options.events; } - filterQuery (params = {}, opts = {}) { + filterQuery (params: Params = {}, opts: any = {}) { const paginate = typeof params.paginate !== 'undefined' ? params.paginate : this.options.paginate; const { query = {} } = params; @@ -46,7 +70,7 @@ module.exports = class AdapterService { return Object.assign(result, { paginate }); } - allowsMulti (method) { + allowsMulti (method: string) { const always = alwaysMulti[method]; if (typeof always !== 'undefined') { @@ -62,15 +86,15 @@ module.exports = class AdapterService { } } - find (params) { + find (params?: Params): Promise> { return callMethod(this, '_find', params); } - get (id, params) { + get (id: Id, params?: Params): Promise { return callMethod(this, '_get', id, params); } - create (data, params) { + create (data: Partial | Array>, params?: Params): Promise { if (Array.isArray(data) && !this.allowsMulti('create')) { return Promise.reject(new MethodNotAllowed(`Can not create multiple entries`)); } @@ -78,7 +102,7 @@ module.exports = class AdapterService { return callMethod(this, '_create', data, params); } - update (id, data, params) { + update (id: NullableId, data: T, params?: Params): Promise { if (id === null || Array.isArray(data)) { return Promise.reject(new BadRequest( `You can not replace multiple instances. Did you mean 'patch'?` @@ -88,7 +112,7 @@ module.exports = class AdapterService { return callMethod(this, '_update', id, data, params); } - patch (id, data, params) { + patch (id: NullableId, data: Partial, params?: Params): Promise { if (id === null && !this.allowsMulti('patch')) { return Promise.reject(new MethodNotAllowed(`Can not patch multiple entries`)); } @@ -96,11 +120,11 @@ module.exports = class AdapterService { return callMethod(this, '_patch', id, data, params); } - remove (id, params) { + remove (id: NullableId, params?: Params): Promise { if (id === null && !this.allowsMulti('remove')) { return Promise.reject(new MethodNotAllowed(`Can not remove multiple entries`)); } return callMethod(this, '_remove', id, params); } -}; +} diff --git a/packages/adapter-commons/lib/sort.js b/packages/adapter-commons/src/sort.ts similarity index 88% rename from packages/adapter-commons/lib/sort.js rename to packages/adapter-commons/src/sort.ts index a1aba4fde6..5803a000c3 100644 --- a/packages/adapter-commons/lib/sort.js +++ b/packages/adapter-commons/src/sort.ts @@ -1,15 +1,15 @@ // Sorting algorithm taken from NeDB (https://github.com/louischatriot/nedb) // See https://github.com/louischatriot/nedb/blob/e3f0078499aa1005a59d0c2372e425ab789145c1/lib/model.js#L189 -exports.compareNSB = function (a, b) { +export function compareNSB (a: any, b: any) { if (a < b) { return -1; } if (a > b) { return 1; } return 0; -}; +} -exports.compareArrays = function (a, b) { - var i; - var comp; +export function compareArrays (a: any, b: any) { + let i; + let comp; for (i = 0; i < Math.min(a.length, b.length); i += 1) { comp = exports.compare(a[i], b[i]); @@ -19,9 +19,9 @@ exports.compareArrays = function (a, b) { // Common section was identical, longest one wins return exports.compareNSB(a.length, b.length); -}; +} -exports.compare = function (a, b, compareStrings = exports.compareNSB) { +export function compare (a: any, b: any, compareStrings: any = exports.compareNSB) { const { compareNSB, compare, compareArrays } = exports; // undefined @@ -64,23 +64,21 @@ exports.compare = function (a, b, compareStrings = exports.compareNSB) { } return compareNSB(aKeys.length, bKeys.length); -}; +} // An in-memory sorting function according to the // $sort special query parameter -exports.sorter = function ($sort) { +export function sorter ($sort: any) { const criteria = Object.keys($sort).map(key => { const direction = $sort[key]; return { key, direction }; }); - return function (a, b) { + return function (a: any, b: any) { let compare; - for (let i = 0; i < criteria.length; i++) { - const criterion = criteria[i]; - + for (const criterion of criteria) { compare = criterion.direction * exports.compare(a[criterion.key], b[criterion.key]); if (compare !== 0) { @@ -90,4 +88,4 @@ exports.sorter = function ($sort) { return 0; }; -}; +} diff --git a/packages/adapter-commons/test/commons.test.js b/packages/adapter-commons/test/commons.test.ts similarity index 95% rename from packages/adapter-commons/test/commons.test.js rename to packages/adapter-commons/test/commons.test.ts index fb818af934..8a74671e07 100644 --- a/packages/adapter-commons/test/commons.test.js +++ b/packages/adapter-commons/test/commons.test.ts @@ -1,5 +1,5 @@ -const assert = require('assert'); -const { select } = require('../lib'); +import assert from 'assert'; +import { select } from '../src'; describe('@feathersjs/adapter-commons', () => { describe('select', () => { diff --git a/packages/adapter-commons/test/filter-query.test.js b/packages/adapter-commons/test/filter-query.test.ts similarity index 89% rename from packages/adapter-commons/test/filter-query.test.js rename to packages/adapter-commons/test/filter-query.test.ts index 585bcae206..8f5995121c 100644 --- a/packages/adapter-commons/test/filter-query.test.js +++ b/packages/adapter-commons/test/filter-query.test.ts @@ -1,6 +1,6 @@ -const assert = require('assert'); -const { ObjectId } = require('mongodb'); -const { filterQuery } = require('../lib'); +import assert from 'assert'; +import { ObjectId } from 'mongodb'; +import { filterQuery } from '../src'; describe('@feathersjs/adapter-commons/filterQuery', () => { describe('$sort', () => { @@ -57,12 +57,14 @@ describe('@feathersjs/adapter-commons/filterQuery', () => { }); describe('$limit', () => { + let testQuery: any; + beforeEach(() => { - this.query = { $limit: 1 }; + testQuery = { $limit: 1 }; }); it('returns $limit when present in query', () => { - const { filters, query } = filterQuery(this.query); + const { filters, query } = filterQuery(testQuery); assert.strictEqual(filters.$limit, 1); assert.deepStrictEqual(query, {}); @@ -76,7 +78,7 @@ describe('@feathersjs/adapter-commons/filterQuery', () => { }); it('removes $limit from query when present', () => { - assert.deepStrictEqual(filterQuery(this.query).query, {}); + assert.deepStrictEqual(filterQuery(testQuery).query, {}); }); it('parses $limit strings into integers (#4)', () => { @@ -109,18 +111,20 @@ describe('@feathersjs/adapter-commons/filterQuery', () => { }); describe('$skip', () => { + let testQuery: any; + beforeEach(() => { - this.query = { $skip: 1 }; + testQuery = { $skip: 1 }; }); it('returns $skip when present in query', () => { - const { filters } = filterQuery(this.query); + const { filters } = filterQuery(testQuery); assert.strictEqual(filters.$skip, 1); }); it('removes $skip from query when present', () => { - assert.deepStrictEqual(filterQuery(this.query).query, {}); + assert.deepStrictEqual(filterQuery(testQuery).query, {}); }); it('returns undefined when not present in query', () => { @@ -138,18 +142,20 @@ describe('@feathersjs/adapter-commons/filterQuery', () => { }); describe('$select', () => { + let testQuery: any; + beforeEach(() => { - this.query = { $select: 1 }; + testQuery = { $select: 1 }; }); it('returns $select when present in query', () => { - const { filters } = filterQuery(this.query); + const { filters } = filterQuery(testQuery); assert.strictEqual(filters.$select, 1); }); it('removes $select from query when present', () => { - assert.deepStrictEqual(filterQuery(this.query).query, {}); + assert.deepStrictEqual(filterQuery(testQuery).query, {}); }); it('returns undefined when not present in query', () => { @@ -177,7 +183,7 @@ describe('@feathersjs/adapter-commons/filterQuery', () => { }); it('only converts plain objects', () => { - const userId = ObjectId(); + const userId = new ObjectId(); const original = { userId }; @@ -211,7 +217,7 @@ describe('@feathersjs/adapter-commons/filterQuery', () => { const { filters } = filterQuery({ $known: 1, $select: 1 - }, { filters: { $known: (value) => value.toString() } }); + }, { filters: { $known: (value: any) => value.toString() } }); assert.strictEqual(filters.$unknown, undefined); assert.strictEqual(filters.$known, '1'); diff --git a/packages/adapter-commons/test/service.test.js b/packages/adapter-commons/test/service.test.ts similarity index 80% rename from packages/adapter-commons/test/service.test.js rename to packages/adapter-commons/test/service.test.ts index c46185a4a7..0cdc4f89ad 100644 --- a/packages/adapter-commons/test/service.test.js +++ b/packages/adapter-commons/test/service.test.ts @@ -1,7 +1,9 @@ -const assert = require('assert'); -const { NotImplemented } = require('@feathersjs/errors'); -const { AdapterService } = require('../lib'); -const METHODS = [ 'find', 'get', 'create', 'update', 'patch', 'remove' ]; +import assert from 'assert'; +import { NotImplemented } from '@feathersjs/errors'; +import { AdapterService, InternalServiceMethods } from '../src'; +import { Params, Id, NullableId } from '@feathersjs/feathers'; + +const METHODS: [ 'find', 'get', 'create', 'update', 'patch', 'remove' ] = [ 'find', 'get', 'create', 'update', 'patch', 'remove' ]; describe('@feathersjs/adapter-commons/service', () => { class CustomService extends AdapterService { @@ -10,11 +12,12 @@ describe('@feathersjs/adapter-commons/service', () => { describe('errors when method does not exit', () => { METHODS.forEach(method => { it(`${method}`, () => { - const service = new CustomService(); + const service = new CustomService({}); + // @ts-ignore return service[method]().then(() => { throw new Error('Should never get here'); - }).catch(error => { + }).catch((error: Error) => { assert.ok(error instanceof NotImplemented); assert.strictEqual(error.message, `Method _${method} not available`); }); @@ -23,35 +26,35 @@ describe('@feathersjs/adapter-commons/service', () => { }); describe('works when methods exist', () => { - class MethodService extends AdapterService { - _find () { + class MethodService extends AdapterService implements InternalServiceMethods { + _find (_params?: Params) { return Promise.resolve([]); } - _get (id) { + _get (id: Id, _params?: Params) { return Promise.resolve({ id }); } - _create (data) { + _create (data: Partial | Array>, _params?: Params) { return Promise.resolve(data); } - _update (id) { + _update (id: NullableId, _data: any, _params?: Params) { return Promise.resolve({ id }); } - _patch (id) { + _patch (id: NullableId, _data: any, _params?: Params) { return Promise.resolve({ id }); } - _remove (id) { + _remove (id: NullableId, _params?: Params) { return Promise.resolve({ id }); } } METHODS.forEach(method => { it(`${method}`, () => { - const service = new MethodService(); + const service = new MethodService({}); const args = []; if (method !== 'find') { @@ -62,12 +65,13 @@ describe('@feathersjs/adapter-commons/service', () => { args.push({}); } + // @ts-ignore return service[method](...args); }); }); it('does not allow multi patch', () => { - const service = new MethodService(); + const service = new MethodService({}); return service.patch(null, {}) .then(() => assert.ok(false)) @@ -78,7 +82,7 @@ describe('@feathersjs/adapter-commons/service', () => { }); it('does not allow multi remove', () => { - const service = new MethodService(); + const service = new MethodService({}); return service.remove(null, {}) .then(() => assert.ok(false)) @@ -89,7 +93,7 @@ describe('@feathersjs/adapter-commons/service', () => { }); it('does not allow multi create', () => { - const service = new MethodService(); + const service = new MethodService({}); return service.create([]) .then(() => assert.ok(false)) @@ -100,7 +104,7 @@ describe('@feathersjs/adapter-commons/service', () => { }); it('multi can be set to true', () => { - const service = new MethodService(); + const service = new MethodService({}); service.options.multi = true; diff --git a/packages/adapter-commons/test/sort.test.js b/packages/adapter-commons/test/sort.test.ts similarity index 96% rename from packages/adapter-commons/test/sort.test.js rename to packages/adapter-commons/test/sort.test.ts index 8f8221dbee..88e144b56c 100644 --- a/packages/adapter-commons/test/sort.test.js +++ b/packages/adapter-commons/test/sort.test.ts @@ -1,6 +1,5 @@ -/* eslint-disable no-unused-expressions */ -const assert = require('assert'); -const { sorter } = require('../lib'); +import assert from 'assert'; +import { sorter } from '../src'; describe('@feathersjs/adapter-commons', () => { describe('sorter', () => { diff --git a/packages/adapter-commons/tsconfig.json b/packages/adapter-commons/tsconfig.json new file mode 100644 index 0000000000..316fd41336 --- /dev/null +++ b/packages/adapter-commons/tsconfig.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig", + "include": [ + "src/**/*.ts" + ], + "compilerOptions": { + "outDir": "lib" + } +} diff --git a/tslint.json b/tslint.json index 462ca5bfbf..e90a2b22fa 100644 --- a/tslint.json +++ b/tslint.json @@ -5,6 +5,7 @@ ], "linterOptions": { "exclude": [ + "packages/adapter-commons/lib/**", "packages/authentication/lib/**", "packages/authentication-local/lib/**", "packages/authentication-client/lib/**",