diff --git a/package.json b/package.json index e8d8ecc3..563ea6a8 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "test-watch": "jest --watch", "lint": "eslint src/*", "lint-fix": "eslint src/* --fix", + "lint:fix": "tsc --noEmit && eslint '*/**/*.{js,ts,tsx}' --quiet --fix", "lint-staged": "lint-staged", "coverage": "cross-env NODE_ENV=test jest --coverage", "coverage-ci": "npm run coverage && cat ./coverage/lcov.info | codecov", diff --git a/src/engine/CustomError.js b/src/engine/CustomError.js deleted file mode 100644 index 03febaa0..00000000 --- a/src/engine/CustomError.js +++ /dev/null @@ -1,10 +0,0 @@ -export class CustomError extends Error { - constructor(message, code, status, meta) { - super(message); - Error.captureStackTrace(this, this.constructor); - this.name = this.constructor.name; - this.code = code; - this.status = status; - this.meta = meta; - } -} diff --git a/src/engine/CustomError.ts b/src/engine/CustomError.ts new file mode 100644 index 00000000..ca223a66 --- /dev/null +++ b/src/engine/CustomError.ts @@ -0,0 +1,26 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ + +declare type ErrorInterface = Error; + +declare class Error implements ErrorInterface { + name: string; + message: string; + static captureStackTrace(object: any, objectConstructor?: any): any; +} + +export class CustomError extends Error { + code: any; + status?: any; + meta?: any; + + constructor(message?: string, code?: any, status?: any, meta?: any) { + // super(message); + super(); + Error.captureStackTrace(this, this.constructor); + this.message = message; + this.name = this.constructor.name; + this.code = code; + this.status = status; + this.meta = meta; + } +} diff --git a/src/engine/action/Action.spec.js b/src/engine/action/Action.spec.ts similarity index 96% rename from src/engine/action/Action.spec.js rename to src/engine/action/Action.spec.ts index fcaf0648..c044e9d9 100644 --- a/src/engine/action/Action.spec.js +++ b/src/engine/action/Action.spec.ts @@ -1,4 +1,5 @@ /* eslint-disable @typescript-eslint/no-empty-function */ +/* eslint-disable @typescript-eslint/explicit-function-return-type */ import { Action, isAction } from './Action'; import { Permission, isPermission } from '../permission/Permission'; @@ -327,7 +328,7 @@ describe('Action', () => { input: {}, output: {}, resolve() {}, - permissions: [ new Permission().authenticated(), new Permission() ], + permissions: [new Permission().authenticated(), new Permission()], }); function fn() { @@ -338,3 +339,6 @@ describe('Action', () => { }); }); }); + +/* eslint-enable @typescript-eslint/no-empty-function */ +/* eslint-enable @typescript-eslint/explicit-function-return-type */ diff --git a/src/engine/action/Action.js b/src/engine/action/Action.ts similarity index 55% rename from src/engine/action/Action.js rename to src/engine/action/Action.ts index 6242146e..84e57ae5 100644 --- a/src/engine/action/Action.js +++ b/src/engine/action/Action.ts @@ -1,16 +1,51 @@ -import { passOrThrow, isMap, isFunction } from '../util'; +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { passOrThrow, isMap, isFunction } from '../util'; +import { AttributeBase } from '../attribute/Attribute'; +import { DataTypeFunction } from '../datatype/DataType'; import { generatePermissionDescription, processActionPermissions, + Permission, } from '../permission/Permission'; export const ACTION_TYPE_MUTATION = 'mutation'; export const ACTION_TYPE_QUERY = 'query'; -export const actionTypes = [ ACTION_TYPE_MUTATION, ACTION_TYPE_QUERY ]; +export const actionTypes = [ACTION_TYPE_MUTATION, ACTION_TYPE_QUERY]; + +export type ActionSetup = { + name?: string; + description?: string; + // input?: AttributeBase | Function | { [type: string]: Function }; + input?: any; + // output?: AttributeBase | Function | { [type: string]: Function }; + output?: any; + resolve?: Function; + type?: string; + permissions?: Function | Permission | Permission[]; + postProcessor?: Function; +}; export class Action { - constructor(setup = {}) { + name: string; + description: string; + // input: AttributeBase | Function | { [type: string]: Function }; + // private _input: AttributeBase | Function | { [type: string]: Function }; + input: any; + private _input: any; + // output: AttributeBase | Function | { [type: string]: Function }; + // private _output: AttributeBase | Function | { [type: string]: Function }; + output: any; + private _output: any; + resolve: Function; + type: string; + permissions: Function | Permission | Permission[]; + private _permissions: Function | Permission | Permission[]; + private _defaultPermissions: Function | Permission | Permission[]; + descriptionPermissions: string | false; + postProcessor: Function; + + constructor(setup: ActionSetup = {} as ActionSetup) { const { name, description, @@ -77,24 +112,26 @@ export class Action { } if (isFunction(this.input)) { - this.input = this.input(); + const inputFn = this.input as Function; + this.input = inputFn(); passOrThrow( isMap(this.input), () => - `Input definition function for action '${ - this.name - }' does not return a map`, + `Input definition function for action '${this.name}' does not return a map`, ); } + const inputAttr = this.input as AttributeBase; passOrThrow( - this.input.type, + inputAttr.type, () => `Missing input type for action '${this.name}'`, ); - if (isFunction(this.input.type)) { - this.input.type = this.input.type({ + if (isFunction(inputAttr.type)) { + const inputAttrType = inputAttr.type as DataTypeFunction; + this.input = this.input as AttributeBase; + this.input.type = inputAttrType({ name: 'input', description: this.input.description || this.description, }); @@ -105,7 +142,7 @@ export class Action { return this._input; } - hasInput() { + hasInput(): boolean { return !!this.input; } @@ -119,24 +156,26 @@ export class Action { } if (isFunction(this.output)) { - this.output = this.output(); + const outputFn = this.output as Function; + this.output = outputFn(); passOrThrow( isMap(this.output), () => - `Output definition function for action '${ - this.name - }' does not return a map`, + `Output definition function for action '${this.name}' does not return a map`, ); } + const outputAttr = this.output as AttributeBase; passOrThrow( - this.output.type, + outputAttr.type, () => `Missing output type for action '${this.name}'`, ); - if (isFunction(this.output.type)) { - this.output.type = this.output.type({ + if (isFunction(outputAttr.type)) { + const outputAttrType = outputAttr.type as DataTypeFunction; + this.output = this.output as AttributeBase; + this.output.type = outputAttrType({ name: 'output', description: this.output.description || this.description, }); @@ -147,19 +186,23 @@ export class Action { return this._output; } - hasOutput() { + hasOutput(): boolean { return !!this.output; } _processPermissions() { if (this._permissions) { - const permissions = isFunction(this._permissions) - ? this._permissions() - : this._permissions; - - return processActionPermissions(this, permissions); - } - else if (this._defaultPermissions) { + if (isFunction(this._permissions)) { + const permissionsFn = this._permissions as Function; + return processActionPermissions(this, permissionsFn); + } + return processActionPermissions(this, this._permissions); + + // const permissions = isFunction(this._permissions) + // ? this._permissions() + // : this._permissions; + // return processActionPermissions(this, permissions); + } else if (this._defaultPermissions) { return processActionPermissions(this, this._defaultPermissions); } @@ -193,6 +236,6 @@ export class Action { } } -export const isAction = obj => { +export const isAction = (obj: any) => { return obj instanceof Action; }; diff --git a/src/engine/attribute/Attribute.ts b/src/engine/attribute/Attribute.ts index 19f617e3..2d81debf 100644 --- a/src/engine/attribute/Attribute.ts +++ b/src/engine/attribute/Attribute.ts @@ -1,6 +1,7 @@ import { ComplexDataType } from '../datatype/ComplexDataType'; import { DataType, DataTypeFunction } from '../datatype/DataType'; import { Entity } from '../entity/Entity'; +import { Mutation } from '../mutation/Mutation'; /** * base of a model attribute @@ -14,7 +15,7 @@ export type AttributeBase = { /** * data type of the attribute */ - type: DataType | ComplexDataType | Entity; + type: DataType | ComplexDataType | Entity | DataTypeFunction; /** * make attribute non-nullable on the storage level @@ -46,17 +47,38 @@ export type AttributeBase = { /** * default value generator for create type mutations */ - defaultValue?: () => any; + + defaultValue?: ( + payload?: any, + entityMutation?: Mutation, + entity?: Entity, + context?: Record, + ) => any; /** * custom data serializer function */ - serialize?: () => any; + + serialize?: ( + field?: any, + payload?: any, + entityMutation?: Mutation, + entity?: Entity, + model?: any, + context?: Record, + language?: any, + ) => any; /** * custom validation function */ - validate?: () => any; + validate?: ( + value?: any, + attributeName?: string, + row?: any, + source?: any, + context?: Record, + ) => any; /** * hide the attribute in the protocol (graphql) layer @@ -82,6 +104,8 @@ export type AttributeBase = { * place to store custom (project-related) meta data */ meta?: any; + + gqlFieldNameI18n?: any; }; /** diff --git a/src/engine/configuration/Configuration.js b/src/engine/configuration/Configuration.ts similarity index 79% rename from src/engine/configuration/Configuration.js rename to src/engine/configuration/Configuration.ts index f33cb736..b40dfcfb 100644 --- a/src/engine/configuration/Configuration.js +++ b/src/engine/configuration/Configuration.ts @@ -1,16 +1,32 @@ import { passOrThrow, isArray } from '../util'; -import { isSchema } from '../schema/Schema'; - +import { Schema, isSchema } from '../schema/Schema'; import { languageIsoCodeRegex, LANGUAGE_ISO_CODE_PATTERN } from '../constants'; - -import { isProtocolConfiguration } from '../protocol/ProtocolConfiguration'; -import { isStorageConfiguration } from '../storage/StorageConfiguration'; +import { + ProtocolConfiguration, + isProtocolConfiguration, +} from '../protocol/ProtocolConfiguration'; +import { + StorageConfiguration, + isStorageConfiguration, +} from '../storage/StorageConfiguration'; import * as _ from 'lodash'; +export type ConfigurationSetup = { + languages?: string[]; + schema?: Schema; + protocolConfiguration?: ProtocolConfiguration; + storageConfiguration?: StorageConfiguration; +}; + export class Configuration { - constructor(setup = {}) { + languages: string[]; + schema: Schema; + protocolConfiguration: ProtocolConfiguration; + storageConfiguration: StorageConfiguration; + + constructor(setup: ConfigurationSetup = {} as ConfigurationSetup) { const { languages, schema, @@ -18,7 +34,7 @@ export class Configuration { storageConfiguration, } = setup; - this.setLanguages(languages || [ 'en' ]); + this.setLanguages(languages || ['en']); if (schema) { this.setSchema(schema); diff --git a/src/engine/entity/Entity.ts b/src/engine/entity/Entity.ts index f7043f3e..6b3bef49 100644 --- a/src/engine/entity/Entity.ts +++ b/src/engine/entity/Entity.ts @@ -63,6 +63,7 @@ export type EntitySetup = { includeTimeTracking?: boolean; includeUserTracking?: boolean; indexes?: any; + // improve typings ? mutations?: any; permissions?: any; states?: any; diff --git a/src/engine/entity/ShadowEntity.spec.ts b/src/engine/entity/ShadowEntity.spec.ts index eb34d3fa..1224cea9 100644 --- a/src/engine/entity/ShadowEntity.spec.ts +++ b/src/engine/entity/ShadowEntity.spec.ts @@ -1,3 +1,5 @@ +/* eslint-disable @typescript-eslint/explicit-function-return-type */ + import { Entity } from './Entity'; import { ShadowEntity, isShadowEntity } from './ShadowEntity'; import { passOrThrow } from '../util'; diff --git a/src/engine/entity/systemAttributes.js b/src/engine/entity/systemAttributes.ts similarity index 79% rename from src/engine/entity/systemAttributes.js rename to src/engine/entity/systemAttributes.ts index 6b54d7b2..a80e5bcf 100644 --- a/src/engine/entity/systemAttributes.js +++ b/src/engine/entity/systemAttributes.ts @@ -1,3 +1,6 @@ +import { camelCase, isFunction } from 'lodash'; +import * as casual from 'casual'; + import { DataTypeID, DataTypeUserID, @@ -7,11 +10,10 @@ import { import { CustomError } from '../CustomError'; import { DataTypeState } from '../datatype/DataTypeState'; +import { Entity } from '../entity/Entity'; +import { Mutation } from '../mutation/Mutation'; import { i18nMockGenerator } from '../i18n'; -import * as _ from 'lodash'; -import * as casual from 'casual'; - export const systemAttributePrimary = { name: 'id', description: 'Unique row identifier', @@ -26,16 +28,14 @@ export const systemAttributesTimeTracking = [ description: 'Record was created at this time', type: DataTypeTimestampTz, required: true, - defaultValue: (data, mutation) => { + defaultValue: (_: any, mutation: Mutation) => { return mutation.isTypeCreate ? new Date() : undefined; }, - mock: entity => { + mock: (entity: Entity) => { if (entity.meta && entity.meta.mockCreatedAtGenerator) { - if (!_.isFunction(entity.meta.mockCreatedAtGenerator)) { + if (!isFunction(entity.meta.mockCreatedAtGenerator)) { throw new CustomError( - `meta.mockCreatedAtGenerator needs to be a function in entity '${ - entity.name - }'`, + `meta.mockCreatedAtGenerator needs to be a function in entity '${entity.name}'`, 'InvalidMetaDataError', ); } @@ -50,18 +50,16 @@ export const systemAttributesTimeTracking = [ description: 'Record was updated at this time', type: DataTypeTimestampTz, required: true, - defaultValue: (data, mutation) => { + defaultValue: (_: any, mutation: Mutation) => { return mutation.isTypeCreate || mutation.isTypeUpdate ? new Date() : undefined; }, - mock: entity => { + mock: (entity: Entity) => { if (entity.meta && entity.meta.mockUpdatedAtGenerator) { - if (!_.isFunction(entity.meta.mockUpdatedAtGenerator)) { + if (!isFunction(entity.meta.mockUpdatedAtGenerator)) { throw new CustomError( - `meta.mockUpdatedAtGenerator needs to be a function in entity '${ - entity.name - }'`, + `meta.mockUpdatedAtGenerator needs to be a function in entity '${entity.name}'`, 'InvalidMetaDataError', ); } @@ -79,7 +77,7 @@ export const systemAttributesUserTracking = [ description: 'Record was created by this time', type: DataTypeUserID, required: true, - defaultValue: (data, mutation, entity, { userId }) => { + defaultValue: (_: any, mutation: Mutation, __: Entity, { userId }) => { // TODO: make overridable return mutation.isTypeCreate && userId ? userId : undefined; }, @@ -89,7 +87,7 @@ export const systemAttributesUserTracking = [ description: 'Record was updated by this user', type: DataTypeUserID, required: true, - defaultValue: (data, mutation, entity, { userId }) => { + defaultValue: (_: any, mutation: Mutation, __: Entity, { userId }) => { // TODO: make overridable return (mutation.isTypeCreate || mutation.isTypeUpdate) && userId ? userId @@ -101,15 +99,15 @@ export const systemAttributesUserTracking = [ export const systemAttributeState = { name: 'state', description: 'State of record', - type: (attribute, entity) => + type: (attribute: any, entity: Entity) => new DataTypeState({ ...attribute, validate: undefined, // delete from props as it would be handled as a data type validator - name: _.camelCase(`${entity.name}-instance-state`), + name: camelCase(`${entity.name}-instance-state`), states: entity.states, }), required: true, - defaultValue: (data, mutation) => { + defaultValue: (_: any, mutation: Mutation) => { if (mutation.isTypeCreate || mutation.isTypeUpdate) { if (typeof mutation.toState === 'string') { return mutation.toState; @@ -117,7 +115,7 @@ export const systemAttributeState = { } return undefined; }, - serialize: (value, data, mutation, entity) => { + serialize: (value, _: any, __: Mutation, entity: Entity) => { const states = entity.getStates(); const state = states[value]; @@ -127,7 +125,7 @@ export const systemAttributeState = { return state; }, - validate: (value, attributeName, data, { mutation }) => { + validate: (value: string, __: string, _: any, { mutation }) => { if (mutation.isTypeCreate || mutation.isTypeUpdate) { if (typeof mutation.toState !== 'string') { if (!mutation.toState) { diff --git a/src/engine/filter.spec.js b/src/engine/filter.spec.ts similarity index 92% rename from src/engine/filter.spec.js rename to src/engine/filter.spec.ts index dc768a7e..63384c76 100644 --- a/src/engine/filter.spec.js +++ b/src/engine/filter.spec.ts @@ -1,5 +1,6 @@ /* eslint-disable @typescript-eslint/camelcase */ /* eslint-disable @typescript-eslint/no-empty-function */ +/* eslint-disable @typescript-eslint/explicit-function-return-type */ import { validateFilterLevel } from './filter'; import { Entity } from './entity/Entity'; @@ -54,7 +55,7 @@ describe('filter', () => { description: 'Just some description', nativeDataType: 'text', serialize() {}, - capabilities: [ 'lt', 'lte', 'gt', 'gte' ], + capabilities: ['lt', 'lte', 'gt', 'gte'], }); const StorageDataTypeText = new StorageDataType({ @@ -62,7 +63,7 @@ describe('filter', () => { description: 'Just some description', nativeDataType: 'text', serialize() {}, - capabilities: [ 'in', 'lt', 'lte', 'gt', 'gte', 'starts_with', 'ends_with' ], + capabilities: ['in', 'lt', 'lte', 'gt', 'gte', 'starts_with', 'ends_with'], }); SomeStorageType.addDataTypeMap(DataTypeInteger, StorageDataTypeAny); @@ -80,7 +81,7 @@ describe('filter', () => { const goodFilter2 = { lastName: { - $in: [ 'Doe', 'Smith' ], + $in: ['Doe', 'Smith'], }, firstName: { $starts_with: 'Joh', @@ -93,7 +94,7 @@ describe('filter', () => { validateFilterLevel( goodFilter1, filteredEntity.getAttributes(), - [ 'somewhere' ], + ['somewhere'], SomeStorageType, ), ).not.toThrow(); @@ -102,7 +103,7 @@ describe('filter', () => { validateFilterLevel( goodFilter2, filteredEntity.getAttributes(), - [ 'somewhere' ], + ['somewhere'], SomeStorageType, ), ).not.toThrow(); @@ -147,7 +148,7 @@ describe('filter', () => { validateFilterLevel( goodFilter1, filteredEntity.getAttributes(), - [ 'somewhere' ], + ['somewhere'], SomeStorageType, ), ).not.toThrow(); @@ -156,7 +157,7 @@ describe('filter', () => { validateFilterLevel( goodFilter2, filteredEntity.getAttributes(), - [ 'somewhere' ], + ['somewhere'], SomeStorageType, ), ).not.toThrow(); @@ -172,14 +173,14 @@ describe('filter', () => { } function fn3() { - validateFilterLevel([], null, [ 'somewhere' ], SomeStorageType); + validateFilterLevel([], null, ['somewhere'], SomeStorageType); } function fn4() { validateFilterLevel( [], null, - [ 'somewhere', 'deeply', 'nested' ], + ['somewhere', 'deeply', 'nested'], SomeStorageType, ); } @@ -188,7 +189,7 @@ describe('filter', () => { validateFilterLevel( {}, null, - [ 'somewhere', 'deeply', 'nested' ], + ['somewhere', 'deeply', 'nested'], SomeStorageType, ); } @@ -241,7 +242,7 @@ describe('filter', () => { validateFilterLevel( badFilter2, filteredEntity.getAttributes(), - [ 'just', 'here' ], + ['just', 'here'], SomeStorageType, ); } diff --git a/src/engine/filter.js b/src/engine/filter.ts similarity index 100% rename from src/engine/filter.js rename to src/engine/filter.ts diff --git a/src/engine/helpers.js b/src/engine/helpers.ts similarity index 80% rename from src/engine/helpers.js rename to src/engine/helpers.ts index b3a863f5..2d110bf6 100644 --- a/src/engine/helpers.js +++ b/src/engine/helpers.ts @@ -1,16 +1,18 @@ import * as _ from 'lodash'; +import { Entity } from '..'; +import { Mutation } from './mutation/Mutation'; export const fillSystemAttributesDefaultValues = ( - entity, - entityMutation, - payload, - context, -) => { + entity: Entity, + entityMutation: Mutation, + payload: any, + context: Record, +): any => { const ret = { ...payload, }; - const entityAttributes = entity.getAttributes(); + const entityAttributes: any = entity.getAttributes(); const systemAttributes = _.filter( entityAttributes, attribute => attribute.isSystemAttribute && attribute.defaultValue, @@ -30,11 +32,11 @@ export const fillSystemAttributesDefaultValues = ( }; export const fillDefaultValues = async ( - entity, - entityMutation, - payload, - context, -) => { + entity: Entity, + entityMutation: Mutation, + payload: any, + context: Record, +): Promise => { const ret = { ...payload, }; @@ -66,12 +68,12 @@ export const fillDefaultValues = async ( }; export const serializeValues = ( - entity, - entityMutation, - payload, - model, - context, -) => { + entity: Entity, + entityMutation: Mutation, + payload: any, + model: string, + context: Record, +): any => { const ret = { ...payload, }; @@ -95,8 +97,7 @@ export const serializeValues = ( language, ); }); - } - else if (typeof ret[attributeName] !== 'undefined') { + } else if (typeof ret[attributeName] !== 'undefined') { ret[attributeName] = attribute.serialize( ret[attributeName], ret, diff --git a/src/engine/i18n.js b/src/engine/i18n.ts similarity index 78% rename from src/engine/i18n.js rename to src/engine/i18n.ts index 6f2c33aa..a0c65ee9 100644 --- a/src/engine/i18n.js +++ b/src/engine/i18n.ts @@ -1,16 +1,17 @@ +import { map } from 'lodash'; import { randomJson } from './util'; -import * as _ from 'lodash'; +// import { Entity } from '..'; export const i18nMockGenerator = ( - entity, - name, + entity: any, + _: string, { dataShaperMap }, languages = [], -) => { +): object => { if (entity) { const content = {}; - _.map(entity.getAttributes(), ({ type, i18n, mock }, attributeName) => { + map(entity.getAttributes(), ({ type, i18n, mock }, attributeName) => { const storageAttributeName = dataShaperMap[attributeName]; if (i18n) { diff --git a/src/engine/index/Index.spec.js b/src/engine/index/Index.spec.ts similarity index 88% rename from src/engine/index/Index.spec.js rename to src/engine/index/Index.spec.ts index 815ebd0f..53d52793 100644 --- a/src/engine/index/Index.spec.js +++ b/src/engine/index/Index.spec.ts @@ -1,4 +1,5 @@ /* eslint-disable @typescript-eslint/no-empty-function */ +/* eslint-disable @typescript-eslint/explicit-function-return-type */ import { Index, isIndex, INDEX_UNIQUE, processEntityIndexes } from './Index'; import { Entity } from '../entity/Entity'; @@ -52,7 +53,7 @@ describe('Index', () => { // eslint-disable-next-line no-new new Index({ type: INDEX_UNIQUE, - attributes: [ null ], + attributes: [null], }); } @@ -62,7 +63,7 @@ describe('Index', () => { // eslint-disable-next-line no-new new Index({ type: INDEX_UNIQUE, - attributes: [ 123 ], + attributes: [123], }); } @@ -74,7 +75,7 @@ describe('Index', () => { // eslint-disable-next-line no-new new Index({ type: INDEX_UNIQUE, - attributes: [ 'a', 'b', 'a' ], + attributes: ['a', 'b', 'a'], }); } @@ -84,16 +85,16 @@ describe('Index', () => { it('should accept a correct definition', () => { const index1 = new Index({ type: INDEX_UNIQUE, - attributes: [ 'a' ], + attributes: ['a'], }); const index2 = new Index({ type: INDEX_UNIQUE, - attributes: [ 'a', 'b', 'c' ], + attributes: ['a', 'b', 'c'], }); - expect(index1.attributes).toEqual([ 'a' ]); - expect(index2.attributes).toEqual([ 'a', 'b', 'c' ]); + expect(index1.attributes).toEqual(['a']); + expect(index2.attributes).toEqual(['a', 'b', 'c']); expect(String(index1)).toEqual('unique'); }); @@ -102,7 +103,7 @@ describe('Index', () => { it('should recognize objects of type Index', () => { const index = new Index({ type: INDEX_UNIQUE, - attributes: [ 'a' ], + attributes: ['a'], }); function fn() { @@ -141,7 +142,7 @@ describe('Index', () => { it('should throw if provided with an invalid list of indexes', () => { const indexes = { - unique: [ {} ], + unique: [{}], }; function fn() { @@ -152,7 +153,7 @@ describe('Index', () => { }); it('should throw if provided with an invalid index', () => { - const indexes = [ { foo: 'bar' } ]; + const indexes = [{ foo: 'bar' }]; function fn() { processEntityIndexes(entity, indexes); @@ -165,7 +166,7 @@ describe('Index', () => { const indexes = [ new Index({ type: INDEX_UNIQUE, - attributes: [ 'someAttribute', 'notHere' ], + attributes: ['someAttribute', 'notHere'], }), ]; diff --git a/src/engine/index/Index.js b/src/engine/index/Index.ts similarity index 70% rename from src/engine/index/Index.js rename to src/engine/index/Index.ts index 2cf217ef..fa445d7f 100644 --- a/src/engine/index/Index.js +++ b/src/engine/index/Index.ts @@ -1,13 +1,22 @@ +import { uniq } from 'lodash'; import { passOrThrow, isArray } from '../util'; -import * as _ from 'lodash'; +import { Entity } from '../entity/Entity'; export const INDEX_UNIQUE = 'unique'; export const INDEX_GENERIC = 'generic'; -export const indexTypes = [ INDEX_UNIQUE, INDEX_GENERIC ]; +export const indexTypes = [INDEX_UNIQUE, INDEX_GENERIC]; + +export type IndexSetup = { + type?: string; + attributes?: string[]; +}; export class Index { - constructor(setup = {}) { + type: string; + attributes: string[]; + + constructor(setup: IndexSetup = {} as IndexSetup) { const { type, attributes } = setup; passOrThrow(type, () => 'Missing index type'); @@ -34,7 +43,7 @@ export class Index { }); passOrThrow( - attributes.length === _.uniq(attributes).length, + attributes.length === uniq(attributes).length, () => `Index definition of type '${type}' needs to have a list of unique attribute names`, ); @@ -48,17 +57,15 @@ export class Index { } } -export const isIndex = obj => { +export const isIndex = (obj: any) => { return obj instanceof Index; }; -export const processEntityIndexes = (entity, indexes) => { +export const processEntityIndexes = (entity: Entity, indexes: Index[]) => { passOrThrow( isArray(indexes), () => - `Entity '${ - entity.name - }' indexes definition needs to be an array of indexes`, + `Entity '${entity.name}' indexes definition needs to be an array of indexes`, ); const singleAttributeIndexes = []; @@ -69,35 +76,27 @@ export const processEntityIndexes = (entity, indexes) => { passOrThrow( isIndex(index), () => - `Invalid index definition for entity '${ - entity.name - }' at position '${idx}'`, + `Invalid index definition for entity '${entity.name}' at position '${idx}'`, ); index.attributes.map(attributeName => { passOrThrow( entityAttributes[attributeName], () => - `Cannot use attribute '${ - entity.name - }.${attributeName}' in index as it does not exist`, + `Cannot use attribute '${entity.name}.${attributeName}' in index as it does not exist`, ); if (index.type === INDEX_UNIQUE) { passOrThrow( entityAttributes[attributeName].required, () => - `Cannot use attribute '${ - entity.name - }.${attributeName}' in uniqueness index as it is nullable`, + `Cannot use attribute '${entity.name}.${attributeName}' in uniqueness index as it is nullable`, ); passOrThrow( !entityAttributes[attributeName].i18n, () => - `Cannot use attribute '${ - entity.name - }.${attributeName}' in uniqueness index as it is translatable`, + `Cannot use attribute '${entity.name}.${attributeName}' in uniqueness index as it is translatable`, ); if (index.attributes.length === 1) { @@ -118,7 +117,7 @@ export const processEntityIndexes = (entity, indexes) => { indexes.push( new Index({ type: INDEX_GENERIC, - attributes: [ attributeName ], + attributes: [attributeName], }), ); } diff --git a/src/engine/models/Language.js b/src/engine/models/Language.ts similarity index 100% rename from src/engine/models/Language.js rename to src/engine/models/Language.ts diff --git a/src/engine/models/User.js b/src/engine/models/User.ts similarity index 100% rename from src/engine/models/User.js rename to src/engine/models/User.ts diff --git a/src/engine/mutation/Mutation.spec.js b/src/engine/mutation/Mutation.spec.ts similarity index 90% rename from src/engine/mutation/Mutation.spec.js rename to src/engine/mutation/Mutation.spec.ts index 347c8d6f..fe927896 100644 --- a/src/engine/mutation/Mutation.spec.js +++ b/src/engine/mutation/Mutation.spec.ts @@ -1,4 +1,5 @@ /* eslint-disable @typescript-eslint/no-empty-function */ +/* eslint-disable @typescript-eslint/explicit-function-return-type */ import { Mutation, @@ -79,10 +80,10 @@ describe('Mutation', () => { description: 'mutate the world', }); - processEntityMutations(entity, [ mutation ]); + processEntityMutations(entity, [mutation]); const defaultAttributes = mutation.attributes; - const expectedAttributes = [ 'someAttribute', 'anotherAttribute' ]; + const expectedAttributes = ['someAttribute', 'anotherAttribute']; expect(defaultAttributes).toEqual(expectedAttributes); }); @@ -92,11 +93,11 @@ describe('Mutation', () => { name: 'example', type: MUTATION_TYPE_CREATE, description: 'mutate the world', - attributes: [ 'anything', { foo: 'bar' } ], + attributes: ['anything', { foo: 'bar' }], }); function fn() { - processEntityMutations(entity, [ mutation ]); + processEntityMutations(entity, [mutation]); } expect(fn).toThrowErrorMatchingSnapshot(); @@ -110,7 +111,7 @@ describe('Mutation', () => { attributes: [], }); - processEntityMutations(entity, [ mutation ]); + processEntityMutations(entity, [mutation]); expect(mutation.attributes).toEqual([]); }); @@ -122,7 +123,7 @@ describe('Mutation', () => { attributes: [], }); - processEntityMutations(entity, [ mutation ]); + processEntityMutations(entity, [mutation]); expect(mutation.attributes).not.toBeDefined(); }); @@ -131,11 +132,11 @@ describe('Mutation', () => { name: 'example', type: MUTATION_TYPE_CREATE, description: 'mutate the world', - attributes: [ 'anything', 'anything' ], + attributes: ['anything', 'anything'], }); function fn() { - processEntityMutations(entity, [ mutation ]); + processEntityMutations(entity, [mutation]); } expect(fn).toThrowErrorMatchingSnapshot(); @@ -146,7 +147,7 @@ describe('Mutation', () => { name: 'example', type: MUTATION_TYPE_UPDATE, description: 'mutate the world', - attributes: [ 'anything' ], + attributes: ['anything'], }); expect(mutation.name).toBe('example'); @@ -160,7 +161,7 @@ describe('Mutation', () => { name: 'example', type: MUTATION_TYPE_CREATE, description: 'mutate the world', - attributes: [ 'anything' ], + attributes: ['anything'], preProcessor: 'not-a-function', }); } @@ -175,7 +176,7 @@ describe('Mutation', () => { name: 'example', type: MUTATION_TYPE_CREATE, description: 'mutate the world', - attributes: [ 'anything' ], + attributes: ['anything'], postProcessor: 'not-a-function', }); } @@ -190,7 +191,7 @@ describe('Mutation', () => { name: 'example', type: MUTATION_TYPE_UPDATE, description: 'mutate the world', - attributes: [ 'anything' ], + attributes: ['anything'], fromState: { foo: 'bar' }, }); } @@ -205,7 +206,7 @@ describe('Mutation', () => { name: 'example', type: MUTATION_TYPE_UPDATE, description: 'mutate the world', - attributes: [ 'anything' ], + attributes: ['anything'], toState: 123, }); } @@ -220,7 +221,7 @@ describe('Mutation', () => { name: 'example', type: MUTATION_TYPE_CREATE, description: 'mutate the world', - attributes: [ 'anything' ], + attributes: ['anything'], fromState: 'open', }); } @@ -235,7 +236,7 @@ describe('Mutation', () => { name: 'example', type: MUTATION_TYPE_DELETE, description: 'mutate the world', - attributes: [ 'anything' ], + attributes: ['anything'], toState: 'closed', }); } @@ -250,7 +251,7 @@ describe('Mutation', () => { name: 'example', type: MUTATION_TYPE_UPDATE, description: 'mutate the world', - attributes: [ 'anything' ], + attributes: ['anything'], fromState: 'open', }); } @@ -265,7 +266,7 @@ describe('Mutation', () => { name: 'example', type: MUTATION_TYPE_UPDATE, description: 'mutate the world', - attributes: [ 'anything' ], + attributes: ['anything'], toState: 'close', }); } @@ -278,7 +279,7 @@ describe('Mutation', () => { name: 'example', type: MUTATION_TYPE_UPDATE, description: 'mutate the world', - attributes: [ 'anything' ], + attributes: ['anything'], preProcessor() {}, postProcessor() {}, }); @@ -308,26 +309,26 @@ describe('Mutation', () => { type: MUTATION_TYPE_CREATE, name: 'build', description: 'build item', - attributes: [ 'someAttribute' ], + attributes: ['someAttribute'], }; const mutationTypeUpdateDefinition = { type: MUTATION_TYPE_UPDATE, name: 'change', description: 'change item', - attributes: [ 'id', 'someAttribute' ], + attributes: ['id', 'someAttribute'], }; const mutationTypeDeleteDefinition = { type: MUTATION_TYPE_DELETE, name: 'drop', description: 'drop item', - attributes: [ 'id' ], + attributes: ['id'], }; it('should throw if provided with an invalid list of mutations', () => { const mutations = { - foo: [ {} ], + foo: [{}], }; function fn() { @@ -338,7 +339,7 @@ describe('Mutation', () => { }); it('should throw if provided with an invalid mutation', () => { - const mutations = [ { foo: 'bar' } ]; + const mutations = [{ foo: 'bar' }]; function fn() { processEntityMutations(entity, mutations); @@ -368,7 +369,7 @@ describe('Mutation', () => { type: MUTATION_TYPE_CREATE, name: 'build', description: 'build item', - attributes: [ 'someAttribute' ], + attributes: ['someAttribute'], }), ], }); @@ -385,13 +386,13 @@ describe('Mutation', () => { type: MUTATION_TYPE_CREATE, name: 'build', description: 'build item', - attributes: [ 'someAttribute' ], + attributes: ['someAttribute'], }), new Mutation({ type: MUTATION_TYPE_CREATE, name: 'build', description: 'build item', - attributes: [ 'someAttribute' ], + attributes: ['someAttribute'], }), ]; @@ -408,7 +409,7 @@ describe('Mutation', () => { type: MUTATION_TYPE_CREATE, name: 'build', description: 'build item', - attributes: [ 'doesNotExist' ], + attributes: ['doesNotExist'], }), ]; @@ -496,7 +497,7 @@ describe('Mutation', () => { const mutations2 = [ new Mutation({ ...mutationTypeUpdateDefinition, - fromState: [ 'open', 'whatever', 'close' ], + fromState: ['open', 'whatever', 'close'], toState: 'close', }), ]; @@ -529,7 +530,7 @@ describe('Mutation', () => { new Mutation({ ...mutationTypeUpdateDefinition, fromState: 'open', - toState: [ 'closed', 'randomState', 'open' ], + toState: ['closed', 'randomState', 'open'], }), ]; @@ -541,7 +542,7 @@ describe('Mutation', () => { const mutations4 = [ new Mutation({ ...mutationTypeDeleteDefinition, - fromState: [ 'open', 'notHere', 'open' ], + fromState: ['open', 'notHere', 'open'], }), ]; diff --git a/src/engine/mutation/Mutation.js b/src/engine/mutation/Mutation.ts similarity index 72% rename from src/engine/mutation/Mutation.js rename to src/engine/mutation/Mutation.ts index 930a4329..875f6070 100644 --- a/src/engine/mutation/Mutation.js +++ b/src/engine/mutation/Mutation.ts @@ -1,6 +1,7 @@ +import { uniq } from 'lodash'; import { passOrThrow, isArray, isFunction, mapOverProperties } from '../util'; -import * as _ from 'lodash'; +import { Entity } from '../entity/Entity'; export const MUTATION_TYPE_CREATE = 'create'; export const MUTATION_TYPE_UPDATE = 'update'; @@ -16,26 +17,53 @@ export const defaultEntityMutations = [ { name: 'create', type: MUTATION_TYPE_CREATE, - description: typeName => `Create a new **\`${typeName}\`**`, + description: (typeName: string) => `Create a new **\`${typeName}\`**`, hasAttributes: true, }, { name: 'update', type: MUTATION_TYPE_UPDATE, - description: typeName => + description: (typeName: string) => `Update a single **\`${typeName}\`** using its node ID and a data patch`, hasAttributes: true, }, { name: 'delete', - description: typeName => + description: (typeName: string) => `Delete a single **\`${typeName}\`** using its node ID`, type: MUTATION_TYPE_DELETE, }, ]; +export type MutationSetup = { + name?: string; + type?: string; + description?: string; + attributes?: string[]; + preProcessor?: Function; + postProcessor?: Function; + fromState?: string | string[]; + toState?: string | string[]; +}; + export class Mutation { - constructor(setup = {}) { + name: string; + type: string; + description: string; + attributes: string[]; + fromState: string | string[]; + toState: string | string[]; + + preProcessor: Function; + postProcessor: Function; + + isTypeCreate?: boolean; + isTypeDelete?: boolean; + needsInstance?: boolean; + ignoreRequired?: boolean; + isTypeUpdate?: boolean; + + constructor(setup: MutationSetup = {} as MutationSetup) { const { name, type, @@ -111,9 +139,7 @@ export class Mutation { passOrThrow( this.type !== MUTATION_TYPE_CREATE, () => - `Mutation '${ - this.name - }' cannot define fromState as it is a 'create' type mutation`, + `Mutation '${this.name}' cannot define fromState as it is a 'create' type mutation`, ); passOrThrow( @@ -126,9 +152,7 @@ export class Mutation { passOrThrow( toState, () => - `Mutation '${ - this.name - }' has a fromState defined but misses a toState definition`, + `Mutation '${this.name}' has a fromState defined but misses a toState definition`, ); } @@ -139,26 +163,20 @@ export class Mutation { passOrThrow( this.type !== MUTATION_TYPE_DELETE, () => - `Mutation '${ - this.name - }' cannot define toState as it is a 'delete' type mutation`, + `Mutation '${this.name}' cannot define toState as it is a 'delete' type mutation`, ); passOrThrow( typeof toState === 'string' || isArray(toState), () => - `toState in mutation '${ - this.name - }' needs to be the name of a state or a list of state names the mutation can transition to`, + `toState in mutation '${this.name}' needs to be the name of a state or a list of state names the mutation can transition to`, ); if (this.type !== MUTATION_TYPE_CREATE) { passOrThrow( fromState, () => - `Mutation '${ - this.name - }' has a toState defined but misses a fromState definition`, + `Mutation '${this.name}' has a toState defined but misses a fromState definition`, ); } @@ -171,26 +189,25 @@ export class Mutation { } } -export const isMutation = obj => { +export const isMutation = (obj: any) => { return obj instanceof Mutation; }; -export const processEntityMutations = (entity, mutations) => { +export const processEntityMutations = ( + entity: Entity, + mutations: Mutation[], +) => { passOrThrow( isArray(mutations), () => - `Entity '${ - entity.name - }' mutations definition needs to be an array of mutations`, + `Entity '${entity.name}' mutations definition needs to be an array of mutations`, ); mutations.map((mutation, idx) => { passOrThrow( isMutation(mutation), () => - `Invalid mutation definition for entity '${ - entity.name - }' at position '${idx}'`, + `Invalid mutation definition for entity '${entity.name}' at position '${idx}'`, ); }); @@ -224,38 +241,28 @@ export const processEntityMutations = (entity, mutations) => { mutation.type === MUTATION_TYPE_CREATE) || isArray(mutation.attributes, false), () => - `Mutation '${entity.name}.${ - mutation.name - }' needs to have a list of attributes`, + `Mutation '${entity.name}.${mutation.name}' needs to have a list of attributes`, ); mutation.attributes.map(attribute => { passOrThrow( typeof attribute === 'string', () => - `Mutation '${entity.name}.${ - mutation.name - }' needs to have a list of attribute names`, + `Mutation '${entity.name}.${mutation.name}' needs to have a list of attribute names`, ); }); passOrThrow( - mutation.attributes.length === _.uniq(mutation.attributes).length, + mutation.attributes.length === uniq(mutation.attributes).length, () => - `Mutation '${entity.name}.${ - mutation.name - }' needs to have a list of unique attribute names`, + `Mutation '${entity.name}.${mutation.name}' needs to have a list of unique attribute names`, ); mutation.attributes.map(attributeName => { passOrThrow( entityAttributes[attributeName], () => - `Cannot use attribute '${ - entity.name - }.${attributeName}' in mutation '${entity.name}.${ - mutation.name - }' as it does not exist`, + `Cannot use attribute '${entity.name}.${attributeName}' in mutation '${entity.name}.${mutation.name}' as it does not exist`, ); }); @@ -276,8 +283,7 @@ export const processEntityMutations = (entity, mutations) => { )} ]`, ); } - } - else if ( + } else if ( mutation.type === MUTATION_TYPE_CREATE || mutation.type === MUTATION_TYPE_UPDATE ) { @@ -295,15 +301,13 @@ export const processEntityMutations = (entity, mutations) => { const checkMutationStates = stateStringOrArray => { const stateNames = isArray(stateStringOrArray) ? stateStringOrArray - : [ stateStringOrArray ]; + : [stateStringOrArray]; stateNames.map(stateName => { passOrThrow( entityStates[stateName], () => - `Unknown state '${stateName}' used in mutation '${entity.name}.${ - mutation.name - }'`, + `Unknown state '${stateName}' used in mutation '${entity.name}.${mutation.name}'`, ); }); }; @@ -312,9 +316,7 @@ export const processEntityMutations = (entity, mutations) => { passOrThrow( entity.hasStates(), () => - `Mutation '${entity.name}.${ - mutation.name - }' cannot define fromState as the entity is stateless`, + `Mutation '${entity.name}.${mutation.name}' cannot define fromState as the entity is stateless`, ); checkMutationStates(mutation.fromState); @@ -324,9 +326,7 @@ export const processEntityMutations = (entity, mutations) => { passOrThrow( entity.hasStates(), () => - `Mutation '${entity.name}.${ - mutation.name - }' cannot define toState as the entity is stateless`, + `Mutation '${entity.name}.${mutation.name}' cannot define toState as the entity is stateless`, ); checkMutationStates(mutation.toState); diff --git a/src/engine/permission/Permission.spec.js b/src/engine/permission/Permission.spec.ts similarity index 95% rename from src/engine/permission/Permission.spec.js rename to src/engine/permission/Permission.spec.ts index 2816e085..5296d250 100644 --- a/src/engine/permission/Permission.spec.js +++ b/src/engine/permission/Permission.spec.ts @@ -1,4 +1,5 @@ /* eslint-disable @typescript-eslint/no-empty-function */ +/* eslint-disable @typescript-eslint/explicit-function-return-type */ import { Permission, @@ -49,7 +50,7 @@ describe('Permission', () => { }) .value('someAttribute', 987); - expect(permission.roles).toEqual([ 'manager', 'admin' ]); + expect(permission.roles).toEqual(['manager', 'admin']); expect(permission.lookups).toEqual([ { entity: Language, @@ -202,9 +203,9 @@ describe('Permission', () => { function fn() { passOrThrow( - isPermissionsArray([ permission ]) && - isPermissionsArray([ permission, permission ]) && - isPermissionsArray([ permission, permission, permission ]), + isPermissionsArray([permission]) && + isPermissionsArray([permission, permission]) && + isPermissionsArray([permission, permission, permission]), () => 'This error will never happen', ); } @@ -223,11 +224,11 @@ describe('Permission', () => { isPermissionsArray({}) || isPermissionsArray(function test() {}) || isPermissionsArray(Error) || - isPermissionsArray([ {} ]) || - isPermissionsArray([ function test() {} ]) || - isPermissionsArray([ Error ]) || - isPermissionsArray([ permission, null ]) || - isPermissionsArray([ permission, {}, permission ]), + isPermissionsArray([{}]) || + isPermissionsArray([function test() {}]) || + isPermissionsArray([Error]) || + isPermissionsArray([permission, null]) || + isPermissionsArray([permission, {}, permission]), () => 'Not a Permissions array', ); } @@ -369,10 +370,12 @@ describe('Permission', () => { }); it('should generate a description based on defined permissions', () => { - const tests = [ - [ new Permission().everyone(), 'everyone' ], - [ new Permission().authenticated(), 'authenticated' ], - [ new Permission().role('manager'), 'role manager' ], + type Test = [Permission | Permission[], string]; + + const tests: Test[] = [ + [new Permission().everyone(), 'everyone'], + [new Permission().authenticated(), 'authenticated'], + [new Permission().role('manager'), 'role manager'], [ new Permission().userAttribute('publisher'), 'userAttributes publisher', @@ -381,7 +384,7 @@ describe('Permission', () => { new Permission().lookup(Language, { createdBy: 'someAttribute' }), 'lookup Language someAttribute', ], - [ new Permission().value('someAttribute', 123), 'value someAttribute' ], + [new Permission().value('someAttribute', 123), 'value someAttribute'], [ new Permission() .role('manager') @@ -405,7 +408,7 @@ describe('Permission', () => { ], ]; - tests.map(([ permission, testName ]) => { + tests.map(([permission, testName]) => { expect(generatePermissionDescription(permission)).toMatchSnapshot( testName, ); @@ -423,7 +426,7 @@ describe('Permission', () => { describe('permissions check simple', () => { const userId = 123; - const userRoles = [ 'manager', 'reviewer' ]; + const userRoles = ['manager', 'reviewer']; it('should reject if non-Permission object is provided', () => { function fn() { @@ -494,7 +497,7 @@ describe('Permission', () => { describe('build permission filter', () => { const userId = 123; - const userRoles = [ 'manager', 'reviewer' ]; + const userRoles = ['manager', 'reviewer']; const someEntity = new Entity({ name: 'SomeEntityName', @@ -692,7 +695,7 @@ describe('Permission', () => { district: ({ input }) => input.district, open: () => true, owner: ({ userId }) => userId, // eslint-disable-line no-shadow - state: () => [ 'defined', 'approved' ], + state: () => ['defined', 'approved'], }); const input = { @@ -909,7 +912,7 @@ describe('Permission', () => { }); it('should throw if provided with an invalid map of permissions', () => { - const permissions = [ 'bad' ]; + const permissions = ['bad']; function fn() { processEntityPermissions(entity, permissions); @@ -920,7 +923,7 @@ describe('Permission', () => { it('should throw if provided with an invalid map of mutation permissions', () => { const permissions = { - mutations: [ 'bad' ], + mutations: ['bad'], }; function fn() { @@ -932,7 +935,7 @@ describe('Permission', () => { it('should throw if provided with an invalid permissions', () => { const permissions1 = { - read: [ 'bad' ], + read: ['bad'], }; function fn1() { @@ -942,7 +945,7 @@ describe('Permission', () => { expect(fn1).toThrowErrorMatchingSnapshot(); const permissions2 = { - find: [ 'bad' ], + find: ['bad'], }; function fn2() { @@ -954,7 +957,7 @@ describe('Permission', () => { const permissions3 = { mutations: { mutations: { - create: [ 'bad' ], + create: ['bad'], }, }, }; @@ -1079,7 +1082,7 @@ describe('Permission', () => { }); it('should throw if provided with invalid permissions', () => { - const permissions1 = [ 'bad' ]; + const permissions1 = ['bad']; function fn1() { processActionPermissions(action, permissions1); @@ -1088,7 +1091,7 @@ describe('Permission', () => { expect(fn1).toThrowErrorMatchingSnapshot(); const permissions2 = { - find: [ 'bad' ], + find: ['bad'], }; function fn2() { diff --git a/src/engine/permission/Permission.ts b/src/engine/permission/Permission.ts index 7e078e84..9f872a24 100644 --- a/src/engine/permission/Permission.ts +++ b/src/engine/permission/Permission.ts @@ -1,11 +1,16 @@ +import * as _ from 'lodash'; import { passOrThrow, isMap, isArray, isFunction, asyncForEach } from '../util'; +import { Action } from '../action/Action'; import { Entity, isEntity } from '../entity/Entity'; import { ViewEntity } from '../entity/ViewEntity'; import { isDataTypeUser } from '../datatype/DataTypeUser'; -import { MUTATION_TYPE_CREATE, isMutation } from '../mutation/Mutation'; +import { + MUTATION_TYPE_CREATE, + isMutation, + Mutation, +} from '../mutation/Mutation'; import { isDataTypeState } from '../datatype/DataTypeState'; -import * as _ from 'lodash'; /* all permission rules are ... @@ -15,9 +20,9 @@ import * as _ from 'lodash'; */ const compatibilityList = [ - [ 'everyone', 'lookup', 'value', 'state' ], - [ 'authenticated', 'role', 'userAttribute', 'lookup', 'value', 'state' ], - [ 'role', 'userAttribute', 'lookup', 'value', 'state' ], + ['everyone', 'lookup', 'value', 'state'], + ['authenticated', 'role', 'userAttribute', 'lookup', 'value', 'state'], + ['role', 'userAttribute', 'lookup', 'value', 'state'], ]; export class Permission { @@ -174,11 +179,11 @@ export class Permission { } } -export const isPermission = obj => { +export const isPermission = (obj: any) => { return obj instanceof Permission; }; -export const isPermissionsArray = obj => { +export const isPermissionsArray = (obj?: any) => { if (isArray(obj, true)) { return obj.reduce( (prev, permission) => prev && isPermission(permission), @@ -188,29 +193,31 @@ export const isPermissionsArray = obj => { return false; }; -export const findInvalidPermissionAttributes = (permission, entity) => { +export const findInvalidPermissionAttributes = ( + permission: Permission, + entity: Entity, +) => { const attributes = entity.getAttributes(); permission.userAttributes.map(userAttribute => { const attribute = attributes[userAttribute]; + const attributeTypeAsEntity = attribute.type as Entity; passOrThrow( attribute && (isDataTypeUser(attribute.type) || (entity.isUserEntity && entity.getPrimaryAttribute() === attribute) || - (isEntity(attribute.type) && attribute.type.isUserEntity)), + (isEntity(attribute.type) && attributeTypeAsEntity.isUserEntity)), () => - `Cannot use attribute '${userAttribute}' in '${ - entity.name - }.permissions' as 'userAttribute' as it is not a reference to the User entity`, + `Cannot use attribute '${userAttribute}' in '${entity.name}.permissions' as 'userAttribute' as it is not a reference to the User entity`, ); }); }; export const findMissingPermissionAttributes = ( - permission, - permissionEntity, - mutation, + permission: Permission, + permissionEntity: Entity, + mutation?: Mutation, ) => { const entityAttributeNames = Object.keys(permissionEntity.getAttributes()); @@ -240,8 +247,7 @@ export const findMissingPermissionAttributes = ( ) { missingLookupAttribute = sourceAttribute; return; - } - else if (!lookupEntityAttributeNames.includes(targetAttribute)) { + } else if (!lookupEntityAttributeNames.includes(targetAttribute)) { missingLookupAttribute = `${entity.name}.${targetAttribute}`; return; } @@ -252,9 +258,7 @@ export const findMissingPermissionAttributes = ( !isFunction(sourceAttribute) ) { throw new Error( - `'lookup' type permission used in 'create' type mutation '${ - mutation.name - }' can only have mappings to value functions`, + `'lookup' type permission used in 'create' type mutation '${mutation.name}' can only have mappings to value functions`, ); } }); @@ -305,16 +309,12 @@ export const validateActionLookupPermission = ( passOrThrow( isFunction(sourceAttribute), () => - `Only functions are allowed in '${ - permissionAction.name - }.permissions' for value lookups`, + `Only functions are allowed in '${permissionAction.name}.permissions' for value lookups`, ); passOrThrow( lookupEntityAttributeNames.includes(targetAttribute), () => - `Cannot use attribute '${targetAttribute}' in '${ - permissionAction.name - }.permissions' as it does not exist`, + `Cannot use attribute '${targetAttribute}' in '${permissionAction.name}.permissions' as it does not exist`, ); }); }); @@ -323,7 +323,7 @@ export const validateActionLookupPermission = ( export const generatePermissionDescription = permissions => { const descriptions = []; - const permissionsArray = isArray(permissions) ? permissions : [ permissions ]; + const permissionsArray = isArray(permissions) ? permissions : [permissions]; permissionsArray.map(permission => { const lines = []; @@ -462,7 +462,13 @@ export const isPermissionSimple = permission => { ); }; -export const buildUserAttributesPermissionFilter = ({ permission, userId }) => { +export const buildUserAttributesPermissionFilter = ({ + permission, + userId, +}: { + permission: Permission; + userId: string | number; +}) => { let where; if (permission.userAttributes.length > 0) { @@ -481,7 +487,13 @@ export const buildUserAttributesPermissionFilter = ({ permission, userId }) => { return where; }; -export const buildStatesPermissionFilter = ({ permission, entity }) => { +export const buildStatesPermissionFilter = ({ + permission, + entity, +}: { + permission: Permission; + entity: Entity; +}) => { let where; if (permission.states.length > 0) { @@ -512,7 +524,11 @@ export const buildStatesPermissionFilter = ({ permission, entity }) => { return where; }; -export const buildValuesPermissionFilter = ({ permission }) => { +export const buildValuesPermissionFilter = ({ + permission, +}: { + permission: Permission; +}) => { let where; if (permission.values.length > 0) { @@ -534,7 +550,13 @@ export const buildLookupsPermissionFilter = async ({ userId, userRoles, input, - context + context, +}: { + permission: Permission; + userId?: string | number; + userRoles?: any; + input?: any; + context?: any; }) => { let where; @@ -553,7 +575,12 @@ export const buildLookupsPermissionFilter = async ({ let operator = '$eq'; if (isFunction(sourceAttribute)) { - let value = await sourceAttribute({ userId, userRoles, input, context }); + let value = await sourceAttribute({ + userId, + userRoles, + input, + context, + }); const attr = entity.getAttributes(); @@ -578,8 +605,7 @@ export const buildLookupsPermissionFilter = async ({ operator, value, }); - } - else { + } else { condition.push({ targetAttribute, operator, @@ -603,12 +629,12 @@ export const buildLookupsPermissionFilter = async ({ }; export const buildPermissionFilterSingle = async ( - permission, - userId, - userRoles, - entity, - input, - context + permission: Permission, + userId?: string | number, + userRoles?: any, + entity?: Entity, + input?: any, + context?: any, ) => { let where; @@ -633,12 +659,12 @@ export const buildPermissionFilterSingle = async ( }; export const buildPermissionFilter = async ( - _permissions, - userId, - userRoles, - entity, - input, - context + _permissions: Permission | Permission[], + userId?: string | number, + userRoles?: any, + entity?: Entity, + input?: any, + context?: any, ) => { let where; @@ -646,11 +672,13 @@ export const buildPermissionFilter = async ( return where; } - const permissions = isArray(_permissions) ? _permissions : [ _permissions ]; + const permissions = isArray(_permissions as any[]) + ? _permissions + : [_permissions]; let foundSimplePermission = false; - await asyncForEach(permissions, async permission => { + await asyncForEach(permissions as any[], async permission => { if (foundSimplePermission) { return; } @@ -671,7 +699,7 @@ export const buildPermissionFilter = async ( userRoles, entity, input, - context + context, ); if (permissionFilter) { @@ -691,7 +719,7 @@ export const buildActionPermissionFilter = async ( userRoles, action, input, - context + context, ) => { let where; let lookupPermissionEntity; @@ -700,7 +728,7 @@ export const buildActionPermissionFilter = async ( return where; } - const permissions = isArray(_permissions) ? _permissions : [ _permissions ]; + const permissions = isArray(_permissions) ? _permissions : [_permissions]; let foundSimplePermission = false; @@ -720,7 +748,14 @@ export const buildActionPermissionFilter = async ( return; } - const params = { permission, userId, userRoles, action, input, context }; + const params = { + permission, + userId, + userRoles, + action, + input, + context, + }; const permissionFilter = await buildLookupsPermissionFilter(params); if (permissionFilter) { @@ -766,7 +801,7 @@ const validatePermissionAttributesAndStates = ( permissions, _mutation, ) => { - const permissionsArray = isArray(permissions) ? permissions : [ permissions ]; + const permissionsArray = isArray(permissions) ? permissions : [permissions]; const mutation = isMutation(_mutation) ? _mutation : null; @@ -784,9 +819,7 @@ const validatePermissionAttributesAndStates = ( passOrThrow( !invalidAttribute, () => - `Cannot use attribute '${invalidAttribute}' in '${ - entity.name - }.permissions' for '${mutationName}' as it does not exist`, + `Cannot use attribute '${invalidAttribute}' in '${entity.name}.permissions' for '${mutationName}' as it does not exist`, ); findInvalidPermissionAttributes(permission, entity); @@ -797,9 +830,7 @@ const validatePermissionAttributesAndStates = ( passOrThrow( !invalidState, () => - `Cannot use state '${invalidState}' in '${ - entity.name - }.permissions' for '${mutationName}' as it does not exist`, + `Cannot use state '${invalidState}' in '${entity.name}.permissions' for '${mutationName}' as it does not exist`, ); } }); @@ -807,7 +838,7 @@ const validatePermissionAttributesAndStates = ( const validatePermissionMutationTypes = (entity, permissions, mutation) => { if (mutation.type === MUTATION_TYPE_CREATE) { - const permissionsArray = isArray(permissions) ? permissions : [ permissions ]; + const permissionsArray = isArray(permissions) ? permissions : [permissions]; permissionsArray.map(permission => { passOrThrow( @@ -815,9 +846,7 @@ const validatePermissionMutationTypes = (entity, permissions, mutation) => { !permission.states.length && !permission.values.length, () => - `Create type mutation permission '${mutation.name}' in '${ - entity.name - }.permissions' can only be of type 'authenticated', 'everyone', 'role' or 'lookup'`, + `Create type mutation permission '${mutation.name}' in '${entity.name}.permissions' can only be of type 'authenticated', 'everyone', 'role' or 'lookup'`, ); }); } @@ -874,8 +903,7 @@ export const processEntityPermissions = ( isPermission(permissions.read) || isPermissionsArray(permissions.read), () => `Invalid 'read' permission definition for entity '${entity.name}'`, ); - } - else if (defaultPermissions) { + } else if (defaultPermissions) { permissions.read = defaultPermissions.read; } @@ -884,8 +912,7 @@ export const processEntityPermissions = ( isPermission(permissions.find) || isPermissionsArray(permissions.find), () => `Invalid 'find' permission definition for entity '${entity.name}'`, ); - } - else if (defaultPermissions) { + } else if (defaultPermissions) { permissions.find = defaultPermissions.find; } @@ -899,9 +926,7 @@ export const processEntityPermissions = ( passOrThrow( isMap(permissions.mutations), () => - `Entity '${ - entity.name - }' permissions definition for mutations needs to be a map of mutations and permissions`, + `Entity '${entity.name}' permissions definition for mutations needs to be a map of mutations and permissions`, ); const mutationNames = Object.keys(permissions.mutations); @@ -910,9 +935,7 @@ export const processEntityPermissions = ( isPermission(permissions.mutations[mutationName]) || isPermissionsArray(permissions.mutations[mutationName]), () => - `Invalid mutation permission definition for entity '${ - entity.name - }' at position '${idx}'`, + `Invalid mutation permission definition for entity '${entity.name}' at position '${idx}'`, ); }); @@ -945,9 +968,7 @@ export const processEntityPermissions = ( passOrThrow( mutationNames.includes(permissionMutationName), () => - `Unknown mutation '${permissionMutationName}' used for permissions in entity '${ - entity.name - }'`, + `Unknown mutation '${permissionMutationName}' used for permissions in entity '${entity.name}'`, ); }); @@ -993,8 +1014,7 @@ export const processViewEntityPermissions = ( isPermission(permissions.read) || isPermissionsArray(permissions.read), () => `Invalid 'read' permission definition for entity '${entity.name}'`, ); - } - else if (defaultPermissions) { + } else if (defaultPermissions) { permissions.read = defaultPermissions.read; } @@ -1003,8 +1023,7 @@ export const processViewEntityPermissions = ( isPermission(permissions.find) || isPermissionsArray(permissions.find), () => `Invalid 'find' permission definition for entity '${entity.name}'`, ); - } - else if (defaultPermissions) { + } else if (defaultPermissions) { permissions.find = defaultPermissions.find; } @@ -1031,13 +1050,13 @@ export const processViewEntityPermissions = ( return permissions; }; -export const processActionPermissions = (action, permissions) => { +export const processActionPermissions = (action: Action, permissions) => { passOrThrow( isPermission(permissions) || isPermissionsArray(permissions), () => `Invalid permission definition for action '${action.name}'`, ); - const permissionsArray = isArray(permissions) ? permissions : [ permissions ]; + const permissionsArray = isArray(permissions) ? permissions : [permissions]; permissionsArray.map(permission => { passOrThrow( diff --git a/src/engine/protocol/ProtocolConfiguration.js b/src/engine/protocol/ProtocolConfiguration.ts similarity index 52% rename from src/engine/protocol/ProtocolConfiguration.js rename to src/engine/protocol/ProtocolConfiguration.ts index 0bf50c58..f71153c1 100644 --- a/src/engine/protocol/ProtocolConfiguration.js +++ b/src/engine/protocol/ProtocolConfiguration.ts @@ -1,8 +1,17 @@ import { passOrThrow, isString, isArray } from '../util'; +export type ProtocolConfigurationSetup = { + features?: string[]; +}; + export class ProtocolConfiguration { - constructor(setup = {}) { - this.features = []; + features: { [key: string]: boolean }; + + constructor( + setup: ProtocolConfigurationSetup = {} as ProtocolConfigurationSetup, + ) { + // this.features = []; + this.features = {}; const { features } = setup; @@ -11,7 +20,7 @@ export class ProtocolConfiguration { } } - enableFeature(feature, enable = true) { + enableFeature(feature: string, enable = true): void { passOrThrow( isString(feature), () => 'enableFeature() expects a feature name', @@ -20,7 +29,7 @@ export class ProtocolConfiguration { this.features[feature] = !!enable; } - enableFeatures(features, enable = true) { + enableFeatures(features: string[], enable = true): void { passOrThrow( isArray(features), () => 'enableFeatures() expects an array of feature names', @@ -29,11 +38,12 @@ export class ProtocolConfiguration { features.map(feature => this.enableFeature(feature, enable)); } - getEnabledFeatures() { + getEnabledFeatures(): { [key: string]: boolean } { return this.features; } } -export const isProtocolConfiguration = obj => { +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export const isProtocolConfiguration = (obj: any): boolean => { return obj instanceof ProtocolConfiguration; }; diff --git a/src/engine/protocol/ProtocolType.spec.js b/src/engine/protocol/ProtocolType.spec.ts similarity index 98% rename from src/engine/protocol/ProtocolType.spec.js rename to src/engine/protocol/ProtocolType.spec.ts index 796718e7..74c46b37 100644 --- a/src/engine/protocol/ProtocolType.spec.js +++ b/src/engine/protocol/ProtocolType.spec.ts @@ -1,3 +1,5 @@ +/* eslint-disable @typescript-eslint/explicit-function-return-type */ + import { ProtocolType, isProtocolType } from './ProtocolType'; import { DataTypeID, diff --git a/src/engine/protocol/ProtocolType.js b/src/engine/protocol/ProtocolType.ts similarity index 64% rename from src/engine/protocol/ProtocolType.js rename to src/engine/protocol/ProtocolType.ts index 78ab7967..b938db4a 100644 --- a/src/engine/protocol/ProtocolType.js +++ b/src/engine/protocol/ProtocolType.ts @@ -1,10 +1,49 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ + import { passOrThrow, isFunction } from '../util'; -import { isDataType } from '../datatype/DataType'; -import { isProtocolConfiguration } from './ProtocolConfiguration'; +import { DataType, DataTypeFunction, isDataType } from '../datatype/DataType'; +import { + ProtocolConfiguration, + isProtocolConfiguration, +} from './ProtocolConfiguration'; + +export type ProtocolTypeSetup = { + name?: string; + description?: string; + // isProtocolDataType?: Function; + isProtocolDataType?: (protocolDataType: any) => boolean; +}; + +// export interface ProtocolDataTypeFunction { +// schemaDataType: DataType; +// attribute: string; +// asInput?: boolean; +// } + +export type ProtocolDataType = { + name: string | Function; +}; + +type DataTypeMap = { + [name: string]: ProtocolDataType; +}; + +type DynamicDataTypeMap = { + schemaDataTypeDetector: Function; + protocolDataType: ProtocolDataType; +}; export class ProtocolType { - constructor(setup = {}) { + name: string; + description: string; + isProtocolDataType: (protocolDataType: any) => boolean; + protocolConfiguration: ProtocolConfiguration; + + private _dataTypeMap: DataTypeMap; + private _dynamicDataTypeMap: DynamicDataTypeMap[]; + + constructor(setup: ProtocolTypeSetup = {} as ProtocolTypeSetup) { const { name, description, isProtocolDataType } = setup; passOrThrow(name, () => 'Missing protocol type name'); @@ -26,7 +65,10 @@ export class ProtocolType { this._dynamicDataTypeMap = []; } - addDataTypeMap(schemaDataType, protocolDataType) { + addDataTypeMap( + schemaDataType: DataType | DataTypeFunction, + protocolDataType: ProtocolDataType, + ): void { passOrThrow( isDataType(schemaDataType), () => @@ -50,7 +92,10 @@ export class ProtocolType { this._dataTypeMap[schemaDataType.name] = protocolDataType; } - addDynamicDataTypeMap(schemaDataTypeDetector, protocolDataType) { + addDynamicDataTypeMap( + schemaDataTypeDetector: Function, + protocolDataType: ProtocolType, + ): void { passOrThrow( isFunction(schemaDataTypeDetector), () => @@ -73,7 +118,11 @@ export class ProtocolType { }); } - convertToProtocolDataType(schemaDataType, sourceName, asInput) { + convertToProtocolDataType( + schemaDataType: DataType | DataTypeFunction, + sourceName?: string, + asInput?: boolean, + ): ProtocolDataType { const foundDynamicDataType = this._dynamicDataTypeMap.find( ({ schemaDataTypeDetector }) => schemaDataTypeDetector(schemaDataType), ); @@ -82,7 +131,8 @@ export class ProtocolType { if (isFunction(protocolDataType)) { const attributeType = schemaDataType; - return protocolDataType(attributeType, sourceName, asInput); + const protocolDataTypeFn = protocolDataType as Function; + return protocolDataTypeFn(attributeType, sourceName, asInput); } return protocolDataType; @@ -103,7 +153,7 @@ export class ProtocolType { return this._dataTypeMap[schemaDataType.name]; } - setProtocolConfiguration(protocolConfiguration) { + setProtocolConfiguration(protocolConfiguration): void { passOrThrow( isProtocolConfiguration(protocolConfiguration), () => 'ProtocolType expects a valid protocolConfiguration', @@ -112,7 +162,7 @@ export class ProtocolType { this.protocolConfiguration = protocolConfiguration; } - getProtocolConfiguration() { + getProtocolConfiguration(): ProtocolConfiguration { passOrThrow( this.protocolConfiguration, () => 'ProtocolType is missing a valid protocolConfiguration', @@ -121,11 +171,11 @@ export class ProtocolType { return this.protocolConfiguration; } - toString() { + toString(): string { return this.name; } } -export const isProtocolType = obj => { +export const isProtocolType = (obj: any): boolean => { return obj instanceof ProtocolType; }; diff --git a/src/engine/schema/Schema.spec.js b/src/engine/schema/Schema.spec.ts similarity index 94% rename from src/engine/schema/Schema.spec.js rename to src/engine/schema/Schema.spec.ts index 69329204..162f3e0d 100644 --- a/src/engine/schema/Schema.spec.js +++ b/src/engine/schema/Schema.spec.ts @@ -1,3 +1,5 @@ +/* eslint-disable @typescript-eslint/explicit-function-return-type */ + import { Entity } from '../entity/Entity'; import { Schema } from './Schema'; @@ -44,7 +46,7 @@ describe('Schema', () => { // eslint-disable-next-line no-new new Schema({ - entities: [ FirstEntity, SecondEntity ], + entities: [FirstEntity, SecondEntity], }); }); @@ -100,7 +102,7 @@ describe('Schema', () => { function fn4() { // eslint-disable-next-line no-new new Schema({ - entities: [ 'so-wrong' ], + entities: ['so-wrong'], }); } diff --git a/src/engine/schema/Schema.js b/src/engine/schema/Schema.ts similarity index 91% rename from src/engine/schema/Schema.js rename to src/engine/schema/Schema.ts index fbf0e990..b6af0292 100644 --- a/src/engine/schema/Schema.js +++ b/src/engine/schema/Schema.ts @@ -1,16 +1,45 @@ import { passOrThrow, isArray, isMap } from '../util'; -import { isEntity } from '../entity/Entity'; -import { isAction } from '../action/Action'; +import { Entity, isEntity } from '../entity/Entity'; +import { Action, isAction } from '../action/Action'; import { isDataTypeUser } from '../datatype/DataTypeUser'; -import { isStorageType } from '../storage/StorageType'; +import { StorageType, isStorageType } from '../storage/StorageType'; import { isPermission, isPermissionsArray } from '../permission/Permission'; import { isViewEntity } from '../entity/ViewEntity'; import { isShadowEntity } from '../entity/ShadowEntity'; +export type SchemaSetup = { + entities?: Entity[] | null; + defaultStorageType?: StorageType | null; + actions?: Action[] | null; + defaultActionPermissions?: null; + permissionsMap?: PermissionsMap | null; +}; + +type EntityPermission = { + // improve typing + [key: string]: any; + // [entityName: string]: Permission; + // _defaultPermissions: Permission | Permission[] | null; +}; + +type PermissionsMap = { + entities: EntityPermission; +}; + export class Schema { + private _entityMap = {}; + private _actionMap = {}; + private _isValidated: boolean; + private _userEntity = null; + + defaultStorageType: StorageType; + permissionsMap: PermissionsMap | null; + + defaultActionPermissions; + constructor( - setup = { + setup: SchemaSetup = { entities: null, defaultStorageType: null, actions: null, @@ -176,7 +205,7 @@ export class Schema { const entityDefaultPermissions = this.permissionsMap.entities[entity.name] || {}; entityDefaultPermissions.mutations = - entityDefaultPermissions.mutations || {}; + entityDefaultPermissions.mutations || ({} as Entity); const defaultPermissions = this.permissionsMap.entities ._defaultPermissions; diff --git a/src/engine/storage/StorageConfiguration.js b/src/engine/storage/StorageConfiguration.ts similarity index 79% rename from src/engine/storage/StorageConfiguration.js rename to src/engine/storage/StorageConfiguration.ts index 687ccda5..af13a6cb 100644 --- a/src/engine/storage/StorageConfiguration.js +++ b/src/engine/storage/StorageConfiguration.ts @@ -1,7 +1,22 @@ import { passOrThrow } from '../util'; +export type StorageConfigurationSetup = { + // todo improve typings ? + name?: string; + storageInstance?: any; + storageModels?: any; + connectionConfig?: any; +}; + export class StorageConfiguration { - constructor(setup = {}) { + name: string; + storageInstance: any; + storageModels: any; + connectionConfig: any; + + constructor( + setup: StorageConfigurationSetup = {} as StorageConfigurationSetup, + ) { const { name, storageInstance, storageModels, connectionConfig } = setup; if (name) { diff --git a/src/engine/storage/StorageDataType.spec.js b/src/engine/storage/StorageDataType.spec.ts similarity index 94% rename from src/engine/storage/StorageDataType.spec.js rename to src/engine/storage/StorageDataType.spec.ts index 6af61738..f67baa4f 100644 --- a/src/engine/storage/StorageDataType.spec.js +++ b/src/engine/storage/StorageDataType.spec.ts @@ -1,4 +1,5 @@ /* eslint-disable @typescript-eslint/no-empty-function */ +/* eslint-disable @typescript-eslint/explicit-function-return-type */ import { StorageDataType, isStorageDataType } from './StorageDataType'; import { passOrThrow } from '../util'; @@ -87,7 +88,7 @@ describe('StorageDataType', () => { description: 'Just some description', nativeDataType: String, serialize() {}, - capabilities: [ 'in', 'ne', 'magic_unicorn_filter' ], + capabilities: ['in', 'ne', 'magic_unicorn_filter'], }); } @@ -100,10 +101,10 @@ describe('StorageDataType', () => { description: 'Just some description', nativeDataType: String, serialize() {}, - capabilities: [ 'in', 'ne', 'contains' ], + capabilities: ['in', 'ne', 'contains'], }); - expect(storageDataType.capabilities).toEqual([ 'in', 'ne', 'contains' ]); + expect(storageDataType.capabilities).toEqual(['in', 'ne', 'contains']); }); it('should fall back to a simple parse function if none provided', () => { diff --git a/src/engine/storage/StorageDataType.js b/src/engine/storage/StorageDataType.ts similarity index 72% rename from src/engine/storage/StorageDataType.js rename to src/engine/storage/StorageDataType.ts index 081bc8a4..a1551524 100644 --- a/src/engine/storage/StorageDataType.js +++ b/src/engine/storage/StorageDataType.ts @@ -2,8 +2,29 @@ import { passOrThrow, isFunction, isArray } from '../util'; import { storageDataTypeCapabilities } from '../constants'; +export type StorageDataTypeSetup = { + name?: string; + description?: string; + // improve nativeDataType typing + nativeDataType?: any; + isSortable?: boolean; + serialize?: Function; + enforceSerialize?: boolean; + parse?: Function; + capabilities?: string[]; +}; + export class StorageDataType { - constructor(setup = {}) { + name: string; + description: string; + nativeDataType: any; + isSortable?: boolean; + serialize: Function; + enforceSerialize?: boolean; + parse?: Function; + capabilities?: string[]; + + constructor(setup: StorageDataTypeSetup = {} as StorageDataTypeSetup) { const { name, description, @@ -56,7 +77,7 @@ export class StorageDataType { this.isSortable = !!isSortable; this.serialize = serialize; this.enforceSerialize = !!enforceSerialize; - this.parse = parse || (value => value); + this.parse = parse || ((value: any) => value); this.capabilities = capabilities || []; } @@ -65,6 +86,6 @@ export class StorageDataType { } } -export const isStorageDataType = obj => { +export const isStorageDataType = (obj: any) => { return obj instanceof StorageDataType; }; diff --git a/src/engine/storage/StorageType.spec.js b/src/engine/storage/StorageType.spec.ts similarity index 98% rename from src/engine/storage/StorageType.spec.js rename to src/engine/storage/StorageType.spec.ts index ec837264..3c6f92db 100644 --- a/src/engine/storage/StorageType.spec.js +++ b/src/engine/storage/StorageType.spec.ts @@ -1,4 +1,5 @@ /* eslint-disable @typescript-eslint/no-empty-function */ +/* eslint-disable @typescript-eslint/explicit-function-return-type */ import { StorageType, isStorageType } from './StorageType'; import { StorageDataType } from './StorageDataType'; diff --git a/src/engine/storage/StorageType.js b/src/engine/storage/StorageType.ts similarity index 65% rename from src/engine/storage/StorageType.js rename to src/engine/storage/StorageType.ts index a1a4c289..c4aa99b5 100644 --- a/src/engine/storage/StorageType.js +++ b/src/engine/storage/StorageType.ts @@ -1,11 +1,69 @@ import { passOrThrow, isFunction } from '../util'; -import { isDataType } from '../datatype/DataType'; +import { isDataType, DataType } from '../datatype/DataType'; import { isStorageDataType } from './StorageDataType'; -import { isStorageConfiguration } from './StorageConfiguration'; +import { + StorageConfiguration, + isStorageConfiguration, +} from './StorageConfiguration'; +import { Entity, StorageDataType } from '../..'; +import { Mutation } from '../mutation/Mutation'; + +export type StorageTypeSetup = { + name?: string; + description?: string; + findOne?: ( + entity?: Entity, + id?: any, + args?: Record, + context?: Record, + ) => any; + findOneByValues?: ( + entity?: Entity, + arg?: any, + context?: Record, + ) => any; + find?: ( + entity?: Entity, + args?: Record, + context?: Record, + parentConnection?: any, + ) => any; + count?: ( + entity?: Entity, + args?: Record, + context?: Record, + parentConnection?: any, + ) => number | any; + mutate?: ( + entity?: Entity, + id?: any, + input?: any, + entityMutation?: Mutation, + context?: Record, + ) => void | any; + checkLookupPermission?: ( + entity?: Entity, + where?: any, + context?: Record, + ) => boolean | any; +}; export class StorageType { - constructor(setup = {}) { + name: string; + description: string; + findOne: Function; + findOneByValues: Function; + find: Function; + count: Function; + mutate: Function; + checkLookupPermission: Function; + + private _dataTypeMap; + private _dynamicDataTypeMap; + storageConfiguration: StorageConfiguration; + + constructor(setup: StorageTypeSetup = {} as StorageTypeSetup) { const { name, description, @@ -66,7 +124,7 @@ export class StorageType { this._dynamicDataTypeMap = []; } - addDataTypeMap(schemaDataType, storageDataType) { + addDataTypeMap(schemaDataType: DataType, storageDataType: StorageDataType) { passOrThrow( isDataType(schemaDataType), () => @@ -86,21 +144,21 @@ export class StorageType { passOrThrow( !this._dataTypeMap[schemaDataType.name], () => - `Data type mapping for '${ - schemaDataType.name - }' already registered with storage type '${this.name}'`, + `Data type mapping for '${schemaDataType.name}' already registered with storage type '${this.name}'`, ); this._dataTypeMap[schemaDataType.name] = storageDataType; } - addDynamicDataTypeMap(schemaDataTypeDetector, storageDataType) { + addDynamicDataTypeMap( + schemaDataTypeDetector: Function, + storageDataType: StorageDataType | Function, + ) { passOrThrow( isFunction(schemaDataTypeDetector), () => - `Provided schemaDataTypeDetector is not a valid function in '${ - this.name - }', ` + `got this instead: ${String(schemaDataTypeDetector)}`, + `Provided schemaDataTypeDetector is not a valid function in '${this.name}', ` + + `got this instead: ${String(schemaDataTypeDetector)}`, ); passOrThrow( @@ -118,7 +176,7 @@ export class StorageType { }); } - convertToStorageDataType(schemaDataType) { + convertToStorageDataType(schemaDataType: DataType) { const foundDynamicDataType = this._dynamicDataTypeMap.find( ({ schemaDataTypeDetector }) => schemaDataTypeDetector(schemaDataType), ); @@ -136,23 +194,20 @@ export class StorageType { passOrThrow( isDataType(schemaDataType), () => - `Provided schemaDataType is not a valid data type in storage type '${ - this.name - }', ` + `got this instead: ${String(schemaDataType)}`, + `Provided schemaDataType is not a valid data type in storage type '${this.name}', ` + + `got this instead: ${String(schemaDataType)}`, ); passOrThrow( this._dataTypeMap[schemaDataType.name], () => - `No data type mapping found for '${ - schemaDataType.name - }' in storage type '${this.name}'`, + `No data type mapping found for '${schemaDataType.name}' in storage type '${this.name}'`, ); return this._dataTypeMap[schemaDataType.name]; } - setStorageConfiguration(storageConfiguration) { + setStorageConfiguration(storageConfiguration: StorageConfiguration) { passOrThrow( isStorageConfiguration(storageConfiguration), () => 'StorageType expects a valid storageConfiguration', diff --git a/src/engine/storage/StorageTypeNull.js b/src/engine/storage/StorageTypeNull.ts similarity index 100% rename from src/engine/storage/StorageTypeNull.js rename to src/engine/storage/StorageTypeNull.ts diff --git a/src/engine/util.spec.js b/src/engine/util.spec.ts similarity index 99% rename from src/engine/util.spec.js rename to src/engine/util.spec.ts index 22f453d9..78e04bc3 100644 --- a/src/engine/util.spec.js +++ b/src/engine/util.spec.ts @@ -1,3 +1,5 @@ +/* eslint-disable @typescript-eslint/explicit-function-return-type */ + import { passOrThrow, resolveFunctionMap, diff --git a/src/engine/validation.spec.js b/src/engine/validation.spec.ts similarity index 98% rename from src/engine/validation.spec.js rename to src/engine/validation.spec.ts index 46527a79..e578dece 100644 --- a/src/engine/validation.spec.js +++ b/src/engine/validation.spec.ts @@ -36,8 +36,7 @@ describe('validation', () => { setTimeout(() => { if (value.length > 2) { resolve(); - } - else { + } else { reject(new Error('Firstname too short')); } }, 1); @@ -118,14 +117,14 @@ describe('validation', () => { type: MUTATION_TYPE_CREATE, name: 'build', description: 'build something', - attributes: [ 'someAttribute', 'team' ], + attributes: ['someAttribute', 'team'], }); const mutationUpdate = new Mutation({ type: MUTATION_TYPE_UPDATE, name: 'change', description: 'update something', - attributes: [ 'someAttribute', 'team' ], + attributes: ['someAttribute', 'team'], }); const action1 = new Action({ @@ -331,7 +330,7 @@ describe('validation', () => { team: { teamName: 'Falcons United Team', players: { - offense: [ 'lorem' ], + offense: ['lorem'], }, }, }; diff --git a/src/engine/validation.js b/src/engine/validation.ts similarity index 85% rename from src/engine/validation.js rename to src/engine/validation.ts index 691bcbbd..b7737de1 100644 --- a/src/engine/validation.js +++ b/src/engine/validation.ts @@ -3,9 +3,16 @@ import { isObjectDataType } from './datatype/ObjectDataType'; import { isListDataType } from './datatype/ListDataType'; import { isComplexDataType } from './datatype/ComplexDataType'; import { isMap, passOrThrow, isDefined, asyncForEach } from './util'; -import { MUTATION_TYPE_CREATE } from './mutation/Mutation'; - -const validateDataTypePayload = async (paramType, payload, context) => { +import { MUTATION_TYPE_CREATE, Mutation } from './mutation/Mutation'; +import { Action } from './action/Action'; +import { Entity } from '..'; +// import { Attribute } from './attribute/Attribute'; + +const validateDataTypePayload = async ( + paramType: any, + payload: any, + context?: Record, +): Promise => { const dataTypeValidator = paramType.validate; if (dataTypeValidator) { @@ -13,7 +20,13 @@ const validateDataTypePayload = async (paramType, payload, context) => { } }; -const validatePayload = async (param, payload, source, context, path = []) => { +const validatePayload = async ( + param: any, + payload: any, + source: any, + context?: Record, + path = [], +): Promise => { if (typeof payload !== 'undefined' && payload !== null) { const paramName = param.name; @@ -108,11 +121,11 @@ const validatePayload = async (param, payload, source, context, path = []) => { }; export const validateActionPayload = async ( - param, - payload, - action, - context, -) => { + param: any, + payload: any, + action: Action, + context?: Record, +): Promise => { const newParam = { ...param, name: 'input', @@ -132,12 +145,12 @@ export const validateActionPayload = async ( }; export const validateMutationPayload = async ( - entity, - mutation, - payload, - context, -) => { - const attributes = entity.getAttributes(); + entity: Entity, + mutation: Mutation, + payload: any, + context?: Record, +): Promise => { + const attributes: any = entity.getAttributes(); const systemAttributes = _.filter( attributes, attribute => attribute.isSystemAttribute && attribute.defaultValue, diff --git a/src/graphqlProtocol/action.spec.js b/src/graphqlProtocol/action.spec.js index f79791a4..09e314ac 100644 --- a/src/graphqlProtocol/action.spec.js +++ b/src/graphqlProtocol/action.spec.js @@ -1,4 +1,5 @@ /* eslint-disable @typescript-eslint/no-empty-function */ +/* eslint-disable @typescript-eslint/explicit-function-return-type */ import { generateActions } from './action'; import { ProtocolGraphQL } from './ProtocolGraphQL'; @@ -174,7 +175,7 @@ describe('action', () => { resolve() {}, }); - const actions = [ simpleAction, noInputAction, noOutputAction, complexAction ]; + const actions = [simpleAction, noInputAction, noOutputAction, complexAction]; const graphRegistry = { types: {}, diff --git a/src/graphqlProtocol/dataTypes.spec.js b/src/graphqlProtocol/dataTypes.spec.js index d6bee945..e82021ff 100644 --- a/src/graphqlProtocol/dataTypes.spec.js +++ b/src/graphqlProtocol/dataTypes.spec.js @@ -1,3 +1,5 @@ +/* eslint-disable @typescript-eslint/explicit-function-return-type */ + import { GraphQLDateTime, GraphQLDate, GraphQLTime } from './dataTypes'; import { parseValue } from 'graphql'; diff --git a/src/graphqlProtocol/filter.spec.js b/src/graphqlProtocol/filter.spec.js index c86ab270..a1f27925 100644 --- a/src/graphqlProtocol/filter.spec.js +++ b/src/graphqlProtocol/filter.spec.js @@ -1,4 +1,5 @@ /* eslint-disable @typescript-eslint/camelcase */ +/* eslint-disable @typescript-eslint/explicit-function-return-type */ import { splitAttributeAndFilterOperator, @@ -44,7 +45,7 @@ describe('filter', () => { }, }); - extendModelsForGql([ filteredEntity ]); + extendModelsForGql([filteredEntity]); describe('splitAttributeAndFilterOperator', () => { it('should split attributes from operators', () => { @@ -130,7 +131,7 @@ describe('filter', () => { }; const goodFilter2 = { - lastName__in: [ 'Doe', 'Smith' ], + lastName__in: ['Doe', 'Smith'], firstName__starts_with: 'Joh', firstName__ends_with: 'an', isActive: true, @@ -138,7 +139,7 @@ describe('filter', () => { const result2 = { lastName: { - $in: [ 'Doe', 'Smith' ], + $in: ['Doe', 'Smith'], }, firstName: { $starts_with: 'Joh', @@ -153,7 +154,7 @@ describe('filter', () => { goodFilter1, filteredEntity.getAttributes(), {}, - [ 'somewhere' ], + ['somewhere'], ), ).toEqual(result1); @@ -163,7 +164,7 @@ describe('filter', () => { goodFilter2, filteredEntity.getAttributes(), {}, - [ 'somewhere' ], + ['somewhere'], ), ).toEqual(result2); }); @@ -235,7 +236,7 @@ describe('filter', () => { goodFilter1, filteredEntity.getAttributes(), {}, - [ 'somewhere' ], + ['somewhere'], ), ).toEqual(result1); @@ -245,7 +246,7 @@ describe('filter', () => { goodFilter2, filteredEntity.getAttributes(), {}, - [ 'somewhere' ], + ['somewhere'], ), ).toEqual(result2); }); @@ -260,7 +261,7 @@ describe('filter', () => { ).rejects.toThrowErrorMatchingSnapshot(); expect( - transformFilterLevel(filteredEntity, [], null, {}, [ 'somewhere' ]), + transformFilterLevel(filteredEntity, [], null, {}, ['somewhere']), ).rejects.toThrowErrorMatchingSnapshot(); expect( @@ -319,14 +320,14 @@ describe('filter', () => { badFilter2, filteredEntity.getAttributes(), {}, - [ 'just', 'here' ], + ['just', 'here'], ), ).rejects.toThrowErrorMatchingSnapshot(); }); it('should throw if exact match operators is used with another operator on the same attribute', async () => { const badFilter1 = { - lastName__in: [ 'Doe', 'Smith' ], + lastName__in: ['Doe', 'Smith'], firstName__starts_with: 'Joh', firstName__ends_with: 'an', firstName: 'Frank', @@ -334,7 +335,7 @@ describe('filter', () => { }; const badFilter2 = { - lastName__in: [ 'Doe', 'Smith' ], + lastName__in: ['Doe', 'Smith'], firstName: 'Frank', firstName__starts_with: 'Joh', firstName__ends_with: 'an', diff --git a/src/graphqlProtocol/generator.spec.js b/src/graphqlProtocol/generator.spec.js index 2c02893f..9d6f312e 100644 --- a/src/graphqlProtocol/generator.spec.js +++ b/src/graphqlProtocol/generator.spec.js @@ -1,3 +1,5 @@ +/* eslint-disable @typescript-eslint/explicit-function-return-type */ + import { generateGraphQLSchema } from './generator'; import { generateTestSchema } from './test-helper'; diff --git a/src/graphqlProtocol/io.spec.js b/src/graphqlProtocol/io.spec.js index 86f4fcd5..bcaa89ad 100644 --- a/src/graphqlProtocol/io.spec.js +++ b/src/graphqlProtocol/io.spec.js @@ -1,4 +1,5 @@ /* eslint-disable @typescript-eslint/no-empty-function */ +/* eslint-disable @typescript-eslint/explicit-function-return-type */ import { Action } from '../engine/action/Action'; import { DataTypeString, DataTypeInteger } from '../engine/datatype/dataTypes'; diff --git a/src/graphqlProtocol/resolver.js b/src/graphqlProtocol/resolver.js index 3da2ea8b..f78ac7fd 100644 --- a/src/graphqlProtocol/resolver.js +++ b/src/graphqlProtocol/resolver.js @@ -49,7 +49,6 @@ export const resolveByFind = (entity, parentConnectionCollector) => { validateConnectionArgs(source, args, context, info); forceSortByUnique(args.orderBy, entity); - // implementing entity.preProcessor here is correct ? if (entity.preProcessor) { const preProcessorResult = await entity.preProcessor( entity, @@ -368,6 +367,8 @@ export const getMutationResolver = ( ); if (entityMutation.type !== MUTATION_TYPE_DELETE) { + // + // this function might be wrong when we look serializeValues args args.input[typeName] = serializeValues( entity, entityMutation, diff --git a/src/graphqlProtocol/test-helper.ts b/src/graphqlProtocol/test-helper.ts index a91ecbfe..d38c9c5a 100644 --- a/src/graphqlProtocol/test-helper.ts +++ b/src/graphqlProtocol/test-helper.ts @@ -92,7 +92,6 @@ export const generateTestSchema = async entities => { serialize: String, }); - /* eslint-disable no-console */ const TestStorage = new StorageType({ name: 'TestStorage', description: 'Just some description', @@ -131,7 +130,6 @@ export const generateTestSchema = async entities => { return true; }, }); - /* eslint-enable no-console */ TestStorage.addDataTypeMap(DataTypeID, StorageDataTypeString); TestStorage.addDataTypeMap(DataTypeInteger, StorageDataTypeAny); diff --git a/src/graphqlProtocol/util.spec.js b/src/graphqlProtocol/util.spec.js index 159e9d0d..244c2117 100644 --- a/src/graphqlProtocol/util.spec.js +++ b/src/graphqlProtocol/util.spec.js @@ -1,3 +1,5 @@ +/* eslint-disable @typescript-eslint/explicit-function-return-type */ + import { generateTypeName, generateTypeNamePascalCase,