├── .eslintrc.js
├── .github
├── ISSUE_TEMPLATE
│ ├── ask-question.md
│ ├── bug_report.md
│ ├── documentation-improvement.md
│ ├── issue-assignment-request.md
│ └── new-feature-enhancement-request.md
└── workflows
│ ├── ci.yaml
│ └── codeql-analysis.yml
├── .gitignore
├── .idea
├── .gitignore
├── backk.iml
├── codeStyles
│ ├── Project.xml
│ └── codeStyleConfig.xml
├── inspectionProfiles
│ └── Project_Default.xml
├── modules.xml
├── scopes
│ └── README_MD.xml
├── sonarlint
│ └── issuestore
│ │ ├── d
│ │ └── 4
│ │ │ └── d48738a44d8def1348b23656bdd8250e5027144a
│ │ └── index.pb
└── vcs.xml
├── .prettierrc
├── LICENSE
├── README.MD
├── TODO.md
├── docs
├── API_DOCUMENTATION.MD
├── Backk.png
├── OWASP_TOP_TEN_CHECKLIST.MD
├── PRODUCTION_READINESS_CHECKLIST.txt
├── SECURITY_FEATURES.MD
├── USAGE_DOCUMENTATION.MD
├── api
│ ├── ABSTRACT_DATA_STORE.MD
│ ├── ADD_SUBENTITIES.MD
│ ├── BASE_SERVICES.MD
│ ├── CREATE_ENTITIES.MD
│ ├── DECORATORS.MD
│ ├── DELETE_ENTITIES.MD
│ ├── ENTITY_ARRAYS.MD
│ ├── ERRORS.MD
│ ├── EXECUTE_CUSTOM_MONGODB.MD
│ ├── EXECUTE_CUSTOM_SQL.MD
│ ├── FILE_READING.MD
│ ├── GET_ENTITIES.MD
│ ├── HOOKS.MD
│ ├── MICROSERVICE_CLASS.MD
│ ├── MICROSERVICE_INITIALIZATION.MD
│ ├── OBSERVABILITY.MD
│ ├── POST_QUERY_OPERATIONS.MD
│ ├── QUERY_FILTERS.MD
│ ├── REMOTE_SERVICE_ACCESS.MD
│ ├── REMOVE_SUBENTITIES.MD
│ └── UPDATE_ENTITIES.MD
└── usage
│ ├── ARGUMENT_RESPONSE_CLASSES.MD
│ ├── CACHING.MD
│ ├── CD.MD
│ ├── CI.MD
│ ├── CLIENT_GENERATION.MD
│ ├── CREATING_ENTITY_CLASSES.MD
│ ├── DATA_STORE.MD
│ ├── DI.MD
│ ├── DIRECTORY_STRUCTURE.MD
│ ├── DOCUMENTING_MICROSERVICE.MD
│ ├── INFRA_PLATFORM.MD
│ ├── INITIALIZATION.MD
│ ├── INTEGRATION_TESTING.MD
│ ├── INTRODUCTION.MD
│ ├── KUBERNETES_DEPLOYMENT.MD
│ ├── MICROSERVICE_METADATA.MD
│ ├── MULTIPLE_SERVICE_FUNCTION_CALLS.MD
│ ├── NPM_SCRIPTS.MD
│ ├── OBSERVABILITY.MD
│ ├── REMOTE_SERVICE_ACCESS.MD
│ ├── RESOURCE_FILES.MD
│ ├── SCHEDULING_SERVICE_FUNCTION_EXECUTION.MD
│ ├── SECURITY.MD
│ ├── SPECIAL_SERVICE_FUNCTION_TYPES.MD
│ ├── UNIT_TESTING.MD
│ └── VERSION_CONTROL.MD
├── package-lock.json
├── package.json
├── src
├── assertions
│ ├── assertIsColumnName.ts
│ ├── assertIsNumber.ts
│ ├── assertIsSortDirection.ts
│ └── assertThatPostQueryOperationsAreValid.ts
├── authorization
│ ├── AuthorizationService.ts
│ ├── JwtAuthorizationServiceImpl.ts
│ └── tryAuthorize.ts
├── cache
│ └── ResponseCacheConfigService.ts
├── captcha
│ ├── CaptchaVerificationService.ts
│ └── tryVerifyCaptchaToken.ts
├── client
│ ├── addAdditionalDecorators.ts
│ ├── createConstructor.ts
│ ├── generateClients.ts
│ ├── getPropertyTypeName.ts
│ ├── getServiceFunctionType.ts
│ └── isClientGenerationNeeded.ts
├── configuration
│ └── reloadLoggingConfigOnChange.ts
├── constants
│ └── constants.ts
├── continuationlocalstorage
│ ├── getClsNamespace.ts
│ └── initializeCls.ts
├── crypt
│ ├── decrypt.spec.ts
│ ├── decrypt.ts
│ ├── decryptEntities.ts
│ ├── encrypt.ts
│ ├── hash.ts
│ ├── hashAndEncryptEntity.ts
│ ├── shouldEncryptValue.ts
│ ├── shouldHashValue.ts
│ └── shouldUseRandomInitializationVector.ts
├── datastore
│ ├── AbstractDataStore.ts
│ ├── AbstractSqlDataStore.ts
│ ├── DataStore.ts
│ ├── MongoDbDataStore.ts
│ ├── MySqlDataStore.ts
│ ├── NullDataStore.ts
│ ├── PostgreSqlDataStore.ts
│ ├── hooks
│ │ ├── EntitiesPostHook.ts
│ │ ├── EntityPreHook.ts
│ │ ├── PostHook.ts
│ │ ├── PreHook.ts
│ │ ├── tryExecuteEntitiesPostHook.ts
│ │ ├── tryExecuteEntityPreHooks.ts
│ │ ├── tryExecutePostHook.ts
│ │ └── tryExecutePreHooks.ts
│ ├── mongodb
│ │ ├── MongoDbFilter.ts
│ │ ├── addSimpleSubEntitiesOrValuesByEntityId.ts
│ │ ├── addSimpleSubEntitiesOrValuesByFilters.ts
│ │ ├── convertFilterObjectToMongoDbQueries.ts
│ │ ├── convertMongoDbQueriesToMatchExpression.ts
│ │ ├── convertUserDefinedFiltersToMatchExpression.ts
│ │ ├── getDefaultIncludeFieldsMap.ts
│ │ ├── getExcludeFieldsMap.ts
│ │ ├── getFieldOrdering.ts
│ │ ├── getIncludeFieldsMap.ts
│ │ ├── getJoinPipelines.ts
│ │ ├── getProjection.ts
│ │ ├── getRootOperations.ts
│ │ ├── getRootProjection.ts
│ │ ├── handleNestedManyToManyRelations.ts
│ │ ├── handleNestedOneToManyRelations.ts
│ │ ├── operations
│ │ │ └── dql
│ │ │ │ ├── getEntitiesByFilters.ts
│ │ │ │ └── getEntityByFilters.ts
│ │ ├── paginateSubEntities.ts
│ │ ├── performPostQueryOperations.ts
│ │ ├── removeFieldValues.ts
│ │ ├── removePrivateProperties.ts
│ │ ├── removeSimpleSubEntityById.ts
│ │ ├── removeSimpleSubEntityByIdFromEntityByFilters.ts
│ │ ├── removeSubEntities.ts
│ │ ├── replaceFieldPathNames.ts
│ │ ├── replaceIdStringsWithObjectIds.ts
│ │ ├── replaceSubEntityPaths.ts
│ │ ├── setJoinSpecs.ts
│ │ ├── transformResponse.ts
│ │ ├── tryCreateMongoDbIndex.ts
│ │ ├── tryCreateMongoDbIndexesForUniqueFields.ts
│ │ └── tryFetchAndAssignSubEntitiesForManyToManyRelationships.ts
│ ├── sql
│ │ ├── filters
│ │ │ ├── SqlEqFilter.ts
│ │ │ ├── SqlFilter.ts
│ │ │ ├── SqlInFilter.ts
│ │ │ └── SqlNotInFilter.ts
│ │ └── operations
│ │ │ ├── ddl
│ │ │ ├── initializeDatabase.ts
│ │ │ ├── removeDbInitialization.ts
│ │ │ ├── removeDbInitializationWhenPendingTooLong.ts
│ │ │ ├── setDbInitialized.ts
│ │ │ ├── shouldInitializeDb.ts
│ │ │ ├── tryAlterOrCreateTable.ts
│ │ │ ├── tryAlterTable.ts
│ │ │ ├── tryCreateIndex.ts
│ │ │ ├── tryCreateTable.ts
│ │ │ ├── tryCreateUniqueIndex.ts
│ │ │ └── utils
│ │ │ │ ├── addArrayValuesTableJoinSpec.ts
│ │ │ │ ├── createArrayValuesTable.ts
│ │ │ │ ├── getEnumSqlColumnType.ts
│ │ │ │ ├── getSqlColumnType.ts
│ │ │ │ └── setSubEntityInfo.ts
│ │ │ ├── dml
│ │ │ ├── addFieldValues.ts
│ │ │ ├── addSubEntities.ts
│ │ │ ├── addSubEntitiesByFilters.ts
│ │ │ ├── createEntity.ts
│ │ │ ├── deleteAllEntities.ts
│ │ │ ├── deleteEntitiesByFilters.ts
│ │ │ ├── deleteEntityByFilters.ts
│ │ │ ├── deleteEntityById.ts
│ │ │ ├── removeFieldValues.ts
│ │ │ ├── removeSubEntities.ts
│ │ │ ├── removeSubEntitiesByFilters.ts
│ │ │ ├── updateEntitiesByFilters.ts
│ │ │ ├── updateEntity.ts
│ │ │ ├── updateEntityByFilters.ts
│ │ │ └── utils
│ │ │ │ ├── getSubEntitiesByAction.ts
│ │ │ │ └── tryUpdateEntityVersionAndLastModifiedTimestampIfNeeded.ts
│ │ │ ├── dql
│ │ │ ├── clauses
│ │ │ │ ├── getJoinClauses.ts
│ │ │ │ ├── getPaginationClause.ts
│ │ │ │ ├── tryGetOrderByClause.ts
│ │ │ │ ├── tryGetProjection.ts
│ │ │ │ └── tryGetWhereClause.ts
│ │ │ ├── doesEntityArrayFieldContainValue.ts
│ │ │ ├── getAllEntities.ts
│ │ │ ├── getEntitiesByFilters.ts
│ │ │ ├── getEntitiesByIds.ts
│ │ │ ├── getEntitiesCount.ts
│ │ │ ├── getEntityByFilters.ts
│ │ │ ├── getEntityById.ts
│ │ │ ├── getSubEntities.ts
│ │ │ ├── transformresults
│ │ │ │ ├── convertTinyIntegersToBooleans.ts
│ │ │ │ ├── createResultMaps.ts
│ │ │ │ ├── removeReadDeniedProperties.ts
│ │ │ │ ├── removeSingleSubEntitiesWithNullProperties.ts
│ │ │ │ ├── transformNonEntityArrays.ts
│ │ │ │ └── transformRowsToObjects.ts
│ │ │ └── utils
│ │ │ │ ├── columns
│ │ │ │ ├── getFieldsForEntity.ts
│ │ │ │ ├── getSqlColumnForFieldName.ts
│ │ │ │ ├── getSqlColumnFromProjection.ts
│ │ │ │ └── shouldIncludeField.ts
│ │ │ │ ├── convertFilterObjectToSqlEquals.ts
│ │ │ │ ├── convertUserDefinedFilterToSqlExpression.ts
│ │ │ │ ├── getFilterValues.ts
│ │ │ │ ├── getSqlSelectStatementParts.ts
│ │ │ │ ├── isUniqueField.ts
│ │ │ │ └── updateDbLocalTransactionCount.ts
│ │ │ └── transaction
│ │ │ ├── cleanupLocalTransactionIfNeeded.ts
│ │ │ ├── tryCommitLocalTransactionIfNeeded.ts
│ │ │ ├── tryRollbackLocalTransactionIfNeeded.ts
│ │ │ └── tryStartLocalTransactionIfNeeded.ts
│ └── utils
│ │ ├── createCurrentPageTokens.ts
│ │ ├── getTableName.ts
│ │ ├── getUserAccountIdFieldNameAndRequiredValue.ts
│ │ ├── recordDbOperationDuration.ts
│ │ ├── startDbOperation.ts
│ │ ├── tryEnsurePreviousOrNextPageIsRequested.ts
│ │ ├── tryEnsureProperPageIsRequested.spec.ts
│ │ └── validateDbPassword.ts
├── decorators
│ ├── entity
│ │ ├── CompositeIndex.ts
│ │ ├── Entity.ts
│ │ ├── UniqueCompositeIndex.ts
│ │ └── entityAnnotationContainer.ts
│ ├── registerCustomDecorator.ts
│ ├── service
│ │ ├── AllowServiceForEveryUser.ts
│ │ ├── AllowServiceForKubeClusterInternalUse.ts
│ │ ├── AllowServiceForUserRoles.ts
│ │ ├── NoServiceAutoTests.ts
│ │ ├── function
│ │ │ ├── AllowForEveryUser.ts
│ │ │ ├── AllowForEveryUserForOwnResources.ts
│ │ │ ├── AllowForKubeClusterInternalUse.ts
│ │ │ ├── AllowForMicroserviceInternalUse.ts
│ │ │ ├── AllowForTests.ts
│ │ │ ├── AllowForUserRoles.ts
│ │ │ ├── AllowHttpGetMethod.ts
│ │ │ ├── AuditLog.ts
│ │ │ ├── Create.ts
│ │ │ ├── CronJob.ts
│ │ │ ├── Delete.ts
│ │ │ ├── ExecuteOnStartUp.ts
│ │ │ ├── NoAutoTest.ts
│ │ │ ├── NoCaptcha.ts
│ │ │ ├── NoDistributedTransactionNeeded.ts
│ │ │ ├── NoTransactionNeeded.ts
│ │ │ ├── PostTests.ts
│ │ │ ├── ResponseHeaders.ts
│ │ │ ├── ResponseStatusCode.ts
│ │ │ ├── Subscription.ts
│ │ │ ├── Test.ts
│ │ │ ├── TestSetup.ts
│ │ │ ├── Update.ts
│ │ │ └── serviceFunctionAnnotationContainer.ts
│ │ └── serviceAnnotationContainer.ts
│ └── typeproperty
│ │ ├── ArrayNotUnique.ts
│ │ ├── BooleanOrTinyInt.ts
│ │ ├── Encrypted.ts
│ │ ├── FetchFromRemoteService.ts
│ │ ├── Hashed.ts
│ │ ├── Indexed.ts
│ │ ├── IsAnyString.ts
│ │ ├── IsBigInt.ts
│ │ ├── IsCreditCardExpiration.ts
│ │ ├── IsCreditCardVerificationCode.ts
│ │ ├── IsDataUri.ts
│ │ ├── IsExternalId.ts
│ │ ├── IsFloat.ts
│ │ ├── IsNoneOf.ts
│ │ ├── IsOneOf.ts
│ │ ├── IsPostalCode.ts
│ │ ├── IsStringOrObjectId.ts
│ │ ├── IsStrongPassword.ts
│ │ ├── IsSubject.ts
│ │ ├── IsUndefined.ts
│ │ ├── LengthAndMatches.ts
│ │ ├── LengthAndMatchesAll.ts
│ │ ├── ManyToMany.ts
│ │ ├── MaxLengthAndMatches.ts
│ │ ├── MaxLengthAndMatchesAll.ts
│ │ ├── MinMax.ts
│ │ ├── NotEncrypted.ts
│ │ ├── NotHashed.ts
│ │ ├── NotUnique.ts
│ │ ├── OneToMany.ts
│ │ ├── ShouldBeTrue.ts
│ │ ├── ShouldBeTrueForObject.ts
│ │ ├── Transient.ts
│ │ ├── UiProperties.ts
│ │ ├── Unique.ts
│ │ ├── acceptfiletypes
│ │ └── AcceptFileTypes.ts
│ │ ├── access
│ │ ├── CreateOnly.ts
│ │ ├── Private.ts
│ │ ├── ReadOnly.ts
│ │ ├── ReadUpdate.ts
│ │ ├── ReadWrite.ts
│ │ ├── UpdateOnly.ts
│ │ └── WriteOnly.ts
│ │ ├── datetime
│ │ ├── IsDateBetween.ts
│ │ ├── IsDateBetweenRelative.ts
│ │ ├── IsDayOfMonthIn.ts
│ │ ├── IsDayOfWeekBetween.ts
│ │ ├── IsHourIn.ts
│ │ ├── IsMinuteIn.ts
│ │ ├── IsMonthIn.ts
│ │ ├── IsTimeBetween.spec.ts
│ │ ├── IsTimeBetween.ts
│ │ ├── IsTimestampBetween.ts
│ │ ├── IsTimestampBetweenRelative.ts
│ │ ├── IsYearAndMonthBetween.ts
│ │ └── IsYearAndMonthBetweenRelative.ts
│ │ ├── testing
│ │ ├── TestValue.ts
│ │ └── testValueContainer.ts
│ │ └── typePropertyAnnotationContainer.ts
├── documentation
│ └── generateServicesDocumentation.ts
├── errors
│ ├── BACKK_ERRORS.ts
│ ├── createBackkErrorFromError.ts
│ ├── createBackkErrorFromErrorCodeMessageAndStatus.ts
│ ├── createBackkErrorFromErrorMessageAndStatusCode.ts
│ ├── createErrorFromErrorCodeMessageAndStatus.ts
│ ├── createErrorFromErrorMessageAndThrowError.ts
│ ├── createErrorMessageWithStatusCode.ts
│ ├── createInternalServerError.ts
│ ├── emptyError.ts
│ └── isBackkError.ts
├── execution
│ ├── BackkResponse.ts
│ ├── ServiceFunctionCall.ts
│ ├── ServiceFunctionCallResponse.ts
│ ├── executeMultipleServiceFunctions.ts
│ ├── fetchFromRemoteServices.ts
│ ├── isExecuteMultipleRequest.ts
│ ├── isValidServiceFunctionName.ts
│ └── tryExecuteServiceMethod.ts
├── file
│ ├── getObjectsFromCsvFileOrThrow.ts
│ ├── getSeparatedIntegerValuesFromTextFileOrThrow.ts
│ ├── getSeparatedValuesFromTextFileOrThrow.ts
│ ├── getValuesByJsonPathFromJsonFileOrThrow.ts
│ └── getValuesByXPathFromXmlFileOrThrow.ts
├── graphql
│ ├── getFieldsFromGraphQlOrJson.spec.ts
│ └── getFieldsFromGraphQlOrJson.ts
├── index.ts
├── initialization
│ └── tryExecuteOnStartupTasks.ts
├── metadata
│ ├── findParentEntityAndPropertyNameForSubEntity.ts
│ ├── findServiceFunctionArgumentType.ts
│ ├── generateServicesMetadata.ts
│ ├── generateTypesForServices.ts
│ ├── getClassPropertyNameToPropertyTypeNameMap.ts
│ ├── getNestedClasses.ts
│ ├── getTypeDocumentation.ts
│ ├── getTypePropertyAccessType.ts
│ ├── getValidationMetadata.ts
│ └── types
│ │ ├── FunctionMetadata.ts
│ │ └── ServiceMetadata.ts
├── microservice
│ ├── Microservice.ts
│ ├── getMicroserviceServiceByServiceClass.ts
│ ├── getMicroserviceServiceNameByServiceClass.ts
│ └── initializeMicroservice.ts
├── observability
│ ├── distributedtracinig
│ │ ├── initializeDefaultJaegerTracing.ts
│ │ └── tracerProvider.ts
│ ├── logging
│ │ ├── LogEntry.ts
│ │ ├── audit
│ │ │ ├── AuditLogEntry.ts
│ │ │ ├── AuditLoggingService.ts
│ │ │ └── createAuditLogEntry.ts
│ │ ├── log.ts
│ │ └── logEnvironment.ts
│ └── metrics
│ │ ├── defaultPrometheusMeter.ts
│ │ ├── defaultServiceMetrics.ts
│ │ └── defaultSystemAndNodeJsMetrics.ts
├── openapi
│ └── writeOpenApiSpecFile.ts
├── postman
│ ├── addCustomTest.ts
│ ├── createPostmanCollectionItem.ts
│ ├── createPostmanCollectionItemFromCustomTest.ts
│ ├── getSampleStringValue.ts
│ ├── getServiceFunctionExampleReturnValue.ts
│ ├── getServiceFunctionReturnValueTests.ts
│ ├── getServiceFunctionTestArgument.ts
│ ├── getServiceFunctionTests.ts
│ ├── getSetCollectionVariableStatements.ts
│ ├── samplevalues
│ │ ├── getMobilePhoneNumberSampleValue.ts
│ │ ├── getPhoneNumberSampleValue.ts
│ │ └── getPostalCodeSampleValue.ts
│ ├── tryValidateIntegrationTests.ts
│ ├── writeApiPostmanCollectionExportFile.ts
│ └── writeTestsPostmanCollectionExportFile.ts
├── remote
│ ├── http
│ │ ├── callRemoteService.ts
│ │ ├── getRemoteResponseTestValue.ts
│ │ └── makeHttpRequest.ts
│ ├── messagequeue
│ │ ├── kafka
│ │ │ ├── consumeFromKafka.ts
│ │ │ ├── getKafkaServerFromEnv.ts
│ │ │ ├── logCreator.ts
│ │ │ ├── minimumLoggingSeverityToKafkaLoggingLevelMap.ts
│ │ │ ├── sendOneOrMoreToKafka.ts
│ │ │ └── sendToKafka.ts
│ │ ├── redis
│ │ │ ├── consumeFromRedis.ts
│ │ │ ├── getRedisServerFromEnv.ts
│ │ │ └── sendOneOrMoreToRedis.ts
│ │ ├── sendToKafka.ts
│ │ ├── sendToRemoteService.ts
│ │ └── sendToRemoteServiceInsideTransaction.ts
│ └── utils
│ │ ├── parseRemoteServiceFunctionCallUrlParts.ts
│ │ └── validateServiceFunctionArguments.ts
├── requestprocessor
│ ├── AbstractAsyncRequestProcessor.ts
│ ├── Http2Response.ts
│ ├── HttpServer.ts
│ ├── KafkaConsumer.ts
│ ├── RedisConsumer.ts
│ └── RequestProcessor.ts
├── scheduling
│ ├── defaultRetryIntervals.ts
│ ├── entities
│ │ ├── JobScheduling.ts
│ │ ├── __Backk__CronJobScheduling.ts
│ │ └── __Backk__JobScheduling.ts
│ ├── scheduleCronJob.ts
│ ├── scheduleCronJobsForExecution.ts
│ ├── scheduleJobsForExecution.ts
│ ├── tryInitializeCronJobSchedulingTable.ts
│ └── tryScheduleJobExecution.ts
├── services
│ ├── BaseService.ts
│ ├── LivenessCheckService.ts
│ ├── Service.ts
│ ├── crudentity
│ │ ├── CrudEntityService.ts
│ │ ├── assertFunctionNamesAreValidForCrudEntityService.ts
│ │ └── utils
│ │ │ ├── isCreateFunction.ts
│ │ │ ├── isDeleteFunction.ts
│ │ │ ├── isReadFunction.ts
│ │ │ └── isUpdateFunction.ts
│ ├── readinesscheck
│ │ ├── DefaultReadinessCheckServiceImpl.ts
│ │ └── ReadinessCheckService.ts
│ ├── startup
│ │ ├── DefaultStartupCheckServiceImpl.ts
│ │ ├── StartupCheckService.ts
│ │ └── types
│ │ │ └── entity
│ │ │ └── SampleEntity.ts
│ ├── tenant
│ │ ├── TenantBaseService.ts
│ │ └── TenantService.ts
│ └── useraccount
│ │ ├── UserBaseService.ts
│ │ └── UserService.ts
├── subscription
│ ├── Subscription.ts
│ └── subscriptionManager.ts
├── types
│ ├── BackkError.ts
│ ├── Captcha.ts
│ ├── DbTableVersion.ts
│ ├── EntityCountRequest.ts
│ ├── ErrorDefinition.ts
│ ├── ErrorOr.ts
│ ├── PromiseErrorOr.ts
│ ├── RecursivePartial.ts
│ ├── Value.ts
│ ├── Version.ts
│ ├── _id
│ │ ├── _Id.ts
│ │ ├── _IdAndCaptcha.ts
│ │ ├── _IdAndCaptchaAndCreatedAtTimestamp.ts
│ │ ├── _IdAndCaptchaAndCreatedAtTimestampAndLastModifiedTimestamp.ts
│ │ ├── _IdAndCaptchaAndLastModifiedTimestamp.ts
│ │ ├── _IdAndCaptchaAndVersion.ts
│ │ ├── _IdAndCaptchaAndVersionAndCreatedAtTimestamp.ts
│ │ ├── _IdAndCaptchaAndVersionAndCreatedAtTimestampAndLastModifiedTimestamp.ts
│ │ ├── _IdAndCaptchaAndVersionAndLastModifiedTimestamp.ts
│ │ ├── _IdAndCreatedAtTimestamp.ts
│ │ ├── _IdAndCreatedAtTimestampAndLastModifiedTimestamp.ts
│ │ ├── _IdAndCreatedAtTimestampAndLastModifiedTimestampAndUserAccountId.ts
│ │ ├── _IdAndCreatedAtTimestampAndUserAccountId.ts
│ │ ├── _IdAndLastModifiedTimestamp.ts
│ │ ├── _IdAndLastModifiedTimestampAndUserAccountId.ts
│ │ ├── _IdAndUserAccountId.ts
│ │ ├── _IdAndVersion.ts
│ │ ├── _IdAndVersionAndCreatedAtTimestamp.ts
│ │ ├── _IdAndVersionAndCreatedAtTimestampAndLastModifiedTimestamp.ts
│ │ ├── _IdAndVersionAndCreatedAtTimestampAndLastModifiedTimestampAndUserAccountId.ts
│ │ ├── _IdAndVersionAndCreatedAtTimestampAndUserAccountId.ts
│ │ ├── _IdAndVersionAndLastModifiedTimestamp.ts
│ │ ├── _IdAndVersionAndLastModifiedTimestampAndUserAccountId.ts
│ │ └── _IdAndVersionAndUserAccountId.ts
│ ├── entities
│ │ ├── BackkEntity.ts
│ │ └── SubEntity.ts
│ ├── id
│ │ └── Id.ts
│ ├── postqueryoperations
│ │ ├── CurrentPageToken.ts
│ │ ├── DefaultPagination.ts
│ │ ├── DefaultPostQueryOperationsImpl.ts
│ │ ├── DefaultSorting.ts
│ │ ├── DefaultSortingAndPagination.ts
│ │ ├── Pagination.ts
│ │ ├── PostQueryOperations.ts
│ │ ├── Projection.ts
│ │ ├── SortBy.ts
│ │ ├── SortBys.ts
│ │ ├── _IdAndDefaultPostQueryOperations.ts
│ │ └── _IdsAndDefaultPostQueryOperations.ts
│ ├── types.ts
│ ├── useraccount
│ │ ├── BaseUserAccount.ts
│ │ ├── Issuer.ts
│ │ ├── Subject.ts
│ │ └── UserAccountId.ts
│ └── userdefinedfilters
│ │ ├── OrFilter.ts
│ │ └── UserDefinedFilter.ts
├── typescript
│ ├── generator
│ │ ├── generateClassFromSrcFile.ts
│ │ └── generateTypescriptFilesFromTypeDefinitionFiles.ts
│ ├── parser
│ │ ├── parseEnumValuesFromSrcFile.ts
│ │ ├── parseServiceFunctionNameToArgAndReturnTypeNameMaps.ts
│ │ └── parseTypescriptLinesForTypeName.ts
│ └── utils
│ │ ├── areTypeDefinitionsUsedInTypeFilesChanged.ts
│ │ └── mergeImports.ts
├── utils
│ ├── array
│ │ └── pushIfNotExists.ts
│ ├── changePackageJsonNameProperty.ts
│ ├── exception
│ │ ├── throwException.ts
│ │ ├── throwIf.ts
│ │ └── throwIfNot.ts
│ ├── executeForAll.ts
│ ├── file
│ │ ├── getSourceFileName.ts
│ │ ├── getSrcFilePathNameForTypeName.ts
│ │ └── getTypeFilePathNameFor.ts
│ ├── findAsyncSequential.ts
│ ├── forEachAsyncParallel.ts
│ ├── forEachAsyncSequential.ts
│ ├── getDbNameFromServiceName.ts
│ ├── getMicroserviceName.ts
│ ├── getNamespacedMicroserviceName.ts
│ ├── getSingularName.ts
│ ├── getTimeZone.ts
│ ├── promiseOf.ts
│ ├── string
│ │ └── decapitalizeFirstLetter.ts
│ ├── type
│ │ ├── findSubEntityClass.ts
│ │ ├── getTypeInfoForTypeName.ts
│ │ ├── isEntityTypeName.ts
│ │ ├── isEnumTypeName.ts
│ │ ├── isPropertyReadDenied.ts
│ │ └── isValidFunctionArgumentTypeName.ts
│ └── wait.ts
└── validation
│ ├── getCustomValidationConstraint.ts
│ ├── getMaxLengthValidationConstraint.ts
│ ├── getValidationConstraint.ts
│ ├── getValidationErrors.ts
│ ├── hasAtMostRepeatingOrConsecutiveCharacters.spec.ts
│ ├── hasAtMostRepeatingOrConsecutiveCharacters.ts
│ ├── setClassPropertyValidationDecorators.ts
│ ├── setNestedTypeValidationDecorators.ts
│ ├── tryValidateServiceFunctionArgument.ts
│ └── tryValidateServiceFunctionReturnValue.ts
└── tsconfig.json
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | parser: '@typescript-eslint/parser',
3 | parserOptions: {
4 | project: 'tsconfig.json',
5 | sourceType: 'module'
6 | },
7 | plugins: ['@typescript-eslint/eslint-plugin'],
8 | extends: [
9 | 'eslint:recommended',
10 | 'plugin:@typescript-eslint/eslint-recommended',
11 | 'plugin:@typescript-eslint/recommended',
12 | 'prettier',
13 | 'prettier/@typescript-eslint'
14 | ],
15 | root: true,
16 | env: {
17 | node: true,
18 | jest: true
19 | },
20 | rules: {
21 | '@typescript-eslint/interface-name-prefix': 'off',
22 | '@typescript-eslint/explicit-function-return-type': 'off',
23 | '@typescript-eslint/no-explicit-any': 'off',
24 | '@typescript-eslint/no-inferrable-types': 'off'
25 | }
26 | };
27 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/ask-question.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Ask question
3 | about: Further information request
4 | title: 'QUESTION: '
5 | labels: question
6 | assignees: ''
7 |
8 | ---
9 |
10 |
11 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: 'BUG: '
5 | labels: bug
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Describe the bug**
11 | A clear and concise description of what the bug is.
12 |
13 | **To Reproduce**
14 | Steps to reproduce the behavior:
15 |
16 | **Expected behavior**
17 | A clear and concise description of what you expected to happen.
18 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/documentation-improvement.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Documentation improvement
3 | about: Help us to improve documentation
4 | title: 'DOC:'
5 | labels: documentation
6 | assignees: ''
7 |
8 | ---
9 |
10 |
11 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/issue-assignment-request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Issue assignment request
3 | about: Request to assign an issue to yourself
4 | title: 'ASSIGN:'
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | Provide the link/number of issue that you want to assign yourself
11 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/new-feature-enhancement-request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: New feature/enhancement request
3 | about: Suggest a new feature or enhancement for this project
4 | title: 'NEW FEATURE: '
5 | labels: enhancement
6 | assignees: ''
7 |
8 | ---
9 |
10 |
11 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yaml:
--------------------------------------------------------------------------------
1 | name: Continuous integration workflow
2 | on:
3 | workflow_dispatch: {}
4 | push:
5 | branches:
6 | - main
7 | jobs:
8 | build:
9 | runs-on: ubuntu-latest
10 | name: Build with Node version 16
11 | steps:
12 | - name: Checkout Git repo
13 | uses: actions/checkout@v2
14 |
15 | - name: Setup Node.js
16 | uses: actions/setup-node@v2
17 | with:
18 | node-version: '16'
19 | cache: 'npm'
20 |
21 | - name: Install NPM dependencies
22 | run: npm ci
23 |
24 | # - name: Lint source code
25 | # run: npm run lint
26 |
27 | - name: Build
28 | run: npm run build
29 |
30 | - name: Run unit tests with coverage
31 | run: npm run test:coverage
32 |
33 | - name: Pump version
34 | run: npm version prerelease
35 |
36 | - name: Git push package.json version change commit
37 | run: git push
38 |
39 | - name: Publish NPM package
40 | uses: JS-DevTools/npm-publish@v1
41 | with:
42 | token: ${{ secrets.NPM_TOKEN }}
43 |
44 |
--------------------------------------------------------------------------------
/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 | # Editor-based HTTP Client requests
5 | /httpRequests/
6 |
--------------------------------------------------------------------------------
/.idea/backk.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/.idea/codeStyles/codeStyleConfig.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/scopes/README_MD.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/.idea/sonarlint/issuestore/d/4/d48738a44d8def1348b23656bdd8250e5027144a:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/backk-node/backk/b8d2e46f88eab818f0b0aa27a9f7346aac74b1b1/.idea/sonarlint/issuestore/d/4/d48738a44d8def1348b23656bdd8250e5027144a
--------------------------------------------------------------------------------
/.idea/sonarlint/issuestore/index.pb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/backk-node/backk/b8d2e46f88eab818f0b0aa27a9f7346aac74b1b1/.idea/sonarlint/issuestore/index.pb
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "arrowParens": "always",
3 | "printWidth": 110,
4 | "tabWidth": 2,
5 | "singleQuote": true,
6 | "vueIndentScriptAndStyle": true
7 | }
8 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 backk-node
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/docs/API_DOCUMENTATION.MD:
--------------------------------------------------------------------------------
1 | ## API Documentation
2 |
3 | - [Microservice](api/MICROSERVICE_CLASS.MD)
4 | - [Request Processors](api/MICROSERVICE_INITIALIZATION.MD)
5 | - [Base Services](api/BASE_SERVICES.MD)
6 | - [Decorators](api/DECORATORS.MD)
7 | - [DataStore](api/ABSTRACT_DATA_STORE.MD)
8 | - [Errors](api/ERRORS.MD)
9 | - [CSV, Text, JSON and XML File Reading](api/FILE_READING.MD)
10 | - [Observability](api/OBSERVABILITY.MD)
11 | - [Remote Service Access](api/REMOTE_SERVICE_ACCESS.MD)
12 |
--------------------------------------------------------------------------------
/docs/Backk.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/backk-node/backk/b8d2e46f88eab818f0b0aa27a9f7346aac74b1b1/docs/Backk.png
--------------------------------------------------------------------------------
/docs/api/ABSTRACT_DATA_STORE.MD:
--------------------------------------------------------------------------------
1 | ## DataStore
2 |
3 | - [Create Entities](CREATE_ENTITIES.MD)
4 | - [Get Entities](GET_ENTITIES.MD)
5 | - [Update Entities](UPDATE_ENTITIES.MD)
6 | - [Delete Entities](DELETE_ENTITIES.MD)
7 | - [Add Sub-Entities](ADD_SUBENTITIES.MD)
8 | - [Remove Sub-Entities](REMOVE_SUBENTITIES.MD)
9 | - [Add/Check/Remove Entity's Primitive Array Field Values](ENTITY_ARRAYS.MD)
10 | - [Execute Custom SQL Operation](EXECUTE_CUSTOM_SQL.MD)
11 | - [Execute Custom MongoDB Operation](EXECUTE_CUSTOM_MONGODB.MD)
12 | - [Hooks](HOOKS.MD)
13 | - [Query Filters](QUERY_FILTERS.MD)
14 | - [Post Query Operations](POST_QUERY_OPERATIONS.MD)
15 | - [executeInsideTransaction](#executeinsidetransaction)
16 |
17 | ### executeInsideTransaction
18 |
19 | ```ts
20 | interface DataStore {
21 | executeInsideTransaction(executable: () => PromiseErrorOr): PromiseErrorOr
22 | }
23 | ```
24 | Executes data store operations in `executable` inside a transaction.
25 |
--------------------------------------------------------------------------------
/docs/api/EXECUTE_CUSTOM_MONGODB.MD:
--------------------------------------------------------------------------------
1 | ## Execute Custom MongoDB Operation
2 |
3 | By default, custom MongoDB operation should be avoided, if possible. They can open up a possibility to accidentally
4 | craft vulnerable code (Injection attacks). Prefer to find a suitable existing data store method or create
5 | a new feature request for Backk, so that we can enhance an existing Backk data store method or create a totally
6 | new method. Also, when using custom MongoDB operations, you make your code non-portable to use another type of data store.
7 |
8 | ### executeMongoDbOperationsOrThrow
9 |
10 | ```ts
11 | interface DataStore {
12 | executeMongoDbOperationsOrThrow(
13 | shouldUseTransaction: boolean,
14 | executeDbOperations: (client: MongoClient) => Promise
15 | ): Promise;
16 | }
17 | ```
18 |
19 | Executes `executeDbOperations` function optionally inside transaction as specified in `shouldUseTransaction`.
20 |
21 | Returns the return value of `executeDbOperations` or on error throws an exception.
22 |
--------------------------------------------------------------------------------
/docs/usage/DIRECTORY_STRUCTURE.MD:
--------------------------------------------------------------------------------
1 | ## Directory Structure
2 |
3 | Here is the suggested directory structure for a Backk microservice:
4 |
5 | ```
6 | - src
7 | |- services
8 | | |- common
9 | | |- service1
10 | | | |- errors
11 | | | |- types
12 | | | | |- args
13 | | | | |- entities
14 | | | | |- enum
15 | | | | |- responses
16 | | | |- Service1.ts
17 | | | |- Service1Impl.ts
18 | | |- service2
19 | | |- .
20 | | |- .
21 | | |- serviceN
22 | |- microservice.ts
23 | |- main.ts
24 | ```
25 |
--------------------------------------------------------------------------------
/docs/usage/DOCUMENTING_MICROSERVICE.MD:
--------------------------------------------------------------------------------
1 | ## Documenting Microservice
2 |
3 | You can generate a documentation website for your Backk microservice with following command:
4 | ```bash
5 | npm run generateDoc
6 | ```
7 |
8 | After generation, the documentation will be available in directory `generated/docs`.
9 |
--------------------------------------------------------------------------------
/docs/usage/RESOURCE_FILES.MD:
--------------------------------------------------------------------------------
1 | ## Resource Files
2 |
3 | If you want to include resource files in your Backk microservice build, create a `resources`
4 | directory in you Backk microservice project. That directory is copied to `build` directory
5 | whenever you build/start your microservice. Resources are included in the final Docker image.
6 |
7 | You can access your resources from your microservice easily using [File Reading](../api/FILE_READING.MD) methods.
8 |
--------------------------------------------------------------------------------
/docs/usage/VERSION_CONTROL.MD:
--------------------------------------------------------------------------------
1 | ## Version Control
2 |
3 | Backk starter project comes with predefined Git hooks:
4 | -pre-commit
5 | -commit-msg
6 | -post-commit
7 |
8 | These hooks are defined in `.husky` directory. You can modify them for your need.
9 |
10 | **IMPORTANT!**:
11 | Commit message hooks requires that the commit message begins with one of following PATCH, MINOR or MAJOR followed by a colon:
12 | - PATCH:
13 | - MINOR:
14 | - MAJOR:
15 |
16 | Commit hook will pump the NPM version in package.json according to SEMVER change as specified in the commit message.
17 | Commit hook will also create Git tag for the version.
18 |
19 |
20 | Pre-commit hook will execute `lint-staged`.
21 | Lint-staged actions are defined in `package.json` and you can modify them for your need:
22 |
23 | package.json
24 | ```json
25 | "lint-staged": {
26 | "*.ts": [
27 | "npm run format",
28 | "npm run lint",
29 | "npm test",
30 | "npm run generateApiSpecs",
31 | "git add generated/openapi",
32 | "npm run generateClientsIfNeeded",
33 | "git add generated/clients"
34 | ]
35 | }
36 | ```
37 |
--------------------------------------------------------------------------------
/src/assertions/assertIsColumnName.ts:
--------------------------------------------------------------------------------
1 | import createBackkErrorFromErrorCodeMessageAndStatus
2 | from "../errors/createBackkErrorFromErrorCodeMessageAndStatus";
3 | import { BACKK_ERRORS } from "../errors/BACKK_ERRORS";
4 |
5 | export default function assertIsColumnName(columnName: string) {
6 | if (columnName.match(/^[a-zA-Z_][a-zA-Z0-9_]*$/) == null) {
7 | throw createBackkErrorFromErrorCodeMessageAndStatus({
8 | ...BACKK_ERRORS.INVALID_ARGUMENT,
9 | message:
10 | BACKK_ERRORS.INVALID_ARGUMENT.message +
11 | `value ${columnName} is not a valid column name`
12 | });
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/assertions/assertIsNumber.ts:
--------------------------------------------------------------------------------
1 | import createBackkErrorFromErrorCodeMessageAndStatus
2 | from "../errors/createBackkErrorFromErrorCodeMessageAndStatus";
3 | import { BACKK_ERRORS } from "../errors/BACKK_ERRORS";
4 |
5 | export default function assertIsNumber(propertyName: string, value: any) {
6 | if (typeof value !== 'number') {
7 | throw createBackkErrorFromErrorCodeMessageAndStatus({
8 | ...BACKK_ERRORS.INVALID_ARGUMENT,
9 | message:
10 | BACKK_ERRORS.INVALID_ARGUMENT.message + `value ${value} in ${propertyName} property must be a number`
11 | });
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/assertions/assertIsSortDirection.ts:
--------------------------------------------------------------------------------
1 | import createBackkErrorFromErrorCodeMessageAndStatus
2 | from "../errors/createBackkErrorFromErrorCodeMessageAndStatus";
3 | import { BACKK_ERRORS } from "../errors/BACKK_ERRORS";
4 |
5 | export default function assertIsSortDirection(value: any) {
6 | if (value.toUpperCase() !== 'ASC' && value.toUpperCase() !== 'DESC') {
7 | throw createBackkErrorFromErrorCodeMessageAndStatus({
8 | ...BACKK_ERRORS.INVALID_ARGUMENT,
9 | message:
10 | BACKK_ERRORS.INVALID_ARGUMENT.message + `${value} in 'sortDirection' property is not a valid sort direction`
11 | });
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/assertions/assertThatPostQueryOperationsAreValid.ts:
--------------------------------------------------------------------------------
1 | import { PostQueryOperations } from "../types/postqueryoperations/PostQueryOperations";
2 | import getValidationConstraint from "../validation/getValidationConstraint";
3 |
4 | export default function assertThatPostQueryOperationsAreValid(postQueryOperations?: PostQueryOperations) {
5 | postQueryOperations?.sortBys?.forEach(sortBy => {
6 | if (sortBy.constructor !== Object) {
7 | const maxLengthValidationConstraint = getValidationConstraint(sortBy.constructor, 'sortExpression', 'maxLength');
8 | if (maxLengthValidationConstraint !== 0) {
9 | throw new Error("In postQueryOperations 'sortBy' objects' property 'sortExpression' must have 'MaxLength(0)' validation")
10 | }
11 | }
12 | })
13 | }
14 |
--------------------------------------------------------------------------------
/src/authorization/AuthorizationService.ts:
--------------------------------------------------------------------------------
1 | export default abstract class AuthorizationService {
2 | abstract hasUserRoleIn(roles: string[], authHeader: string | string[] | undefined): Promise;
3 | abstract getSubjectAndIssuer(
4 | authHeader: string | string[] | undefined
5 | ): Promise<[string | undefined, string | undefined]>;
6 | }
7 |
--------------------------------------------------------------------------------
/src/cache/ResponseCacheConfigService.ts:
--------------------------------------------------------------------------------
1 | export default abstract class ResponseCacheConfigService {
2 | abstract shouldCacheServiceFunctionCallResponse(
3 | serviceFunctionName: string,
4 | serviceFunctionArgument: object
5 | ): boolean;
6 |
7 | abstract getCachingDurationInSecs(serviceFunctionName: string, serviceFunctionArgument: object): number;
8 | }
9 |
--------------------------------------------------------------------------------
/src/captcha/CaptchaVerificationService.ts:
--------------------------------------------------------------------------------
1 | export default abstract class CaptchaVerificationService {
2 | abstract verifyCaptcha(captchaToken: string): Promise;
3 | }
4 |
--------------------------------------------------------------------------------
/src/captcha/tryVerifyCaptchaToken.ts:
--------------------------------------------------------------------------------
1 | import createErrorFromErrorMessageAndThrowError from '../errors/createErrorFromErrorMessageAndThrowError';
2 | import createErrorMessageWithStatusCode from '../errors/createErrorMessageWithStatusCode';
3 | import getMicroserviceServiceByServiceClass from '../microservice/getMicroserviceServiceByServiceClass';
4 | import CaptchaVerificationService from './CaptchaVerificationService';
5 |
6 | export default async function tryVerifyCaptchaToken(microservice: any, captchaToken: string) {
7 | const captchaService = getMicroserviceServiceByServiceClass(microservice, CaptchaVerificationService);
8 |
9 | if (captchaService?.verifyCaptcha) {
10 | const isCaptchaVerified = await captchaService.verifyCaptcha(captchaToken);
11 |
12 | if (!isCaptchaVerified) {
13 | createErrorFromErrorMessageAndThrowError(
14 | createErrorMessageWithStatusCode('Invalid captcha token', 400)
15 | );
16 | }
17 | } else {
18 | throw new Error(
19 | 'Captcha verification service is missing. You must implement a captcha verification service class that extends CaptchaVerificationService and instantiate your class and store in a field in MicroserviceImpl class'
20 | );
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/client/getServiceFunctionType.ts:
--------------------------------------------------------------------------------
1 | import { ServiceFunctionType } from './generateClients';
2 |
3 | export default function getServiceFunctionType(functionName: string, decorators: any): ServiceFunctionType {
4 | const isCreateFunction = !!decorators?.find(
5 | (decorator: any) => decorator.expression.callee.name === 'Create'
6 | );
7 | const isUpdateFunction = !!decorators?.find(
8 | (decorator: any) => decorator.expression.callee.name === 'Update'
9 | );
10 |
11 | if (isCreateFunction || functionName.startsWith('create') || functionName.startsWith('insert')) {
12 | return 'create';
13 | } else if (
14 | isUpdateFunction ||
15 | functionName.startsWith('update') ||
16 | functionName.startsWith('modify') ||
17 | functionName.startsWith('change') ||
18 | functionName.startsWith('patch')
19 | ) {
20 | return 'update';
21 | }
22 |
23 | return 'other';
24 | }
25 |
--------------------------------------------------------------------------------
/src/configuration/reloadLoggingConfigOnChange.ts:
--------------------------------------------------------------------------------
1 | import fs from "fs";
2 |
3 | export default function reloadLoggingConfigOnChange() {
4 | if (fs.existsSync('/etc/config/LOG_LEVEL')) {
5 | try {
6 | const logLevel = fs.readFileSync('/etc/config/LOG_LEVEL', { encoding: 'UTF-8' });
7 | process.env.LOG_LEVEL = logLevel.trim();
8 | } catch (error) {
9 | // NOOP
10 | }
11 |
12 | fs.watchFile('/etc/config/LOG_LEVEL', () => {
13 | try {
14 | const newLogLevel = fs.readFileSync('/etc/config/LOG_LEVEL', { encoding: 'UTF-8' });
15 | process.env.LOG_LEVEL = newLogLevel.trim();
16 | } catch (error) {
17 | // NOOP
18 | }
19 | });
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/continuationlocalstorage/getClsNamespace.ts:
--------------------------------------------------------------------------------
1 | import { getNamespace } from "cls-hooked";
2 |
3 | export default function getClsNamespace(clsNamespace: string): any {
4 | return getNamespace(clsNamespace);
5 | }
6 |
--------------------------------------------------------------------------------
/src/continuationlocalstorage/initializeCls.ts:
--------------------------------------------------------------------------------
1 | import { createNamespace } from 'cls-hooked';
2 |
3 | export const multipleServiceFunctionExecutionsCls = createNamespace('multipleServiceFunctionExecutions');
4 | export const serviceFunctionExecutionCls = createNamespace('serviceFunctionExecution');
5 |
6 | export default function initializeCls() {
7 | // NO OPERATION
8 | }
9 |
--------------------------------------------------------------------------------
/src/crypt/decrypt.spec.ts:
--------------------------------------------------------------------------------
1 | import decrypt from './decrypt';
2 | import encrypt from './encrypt';
3 |
4 | process.env.ENCRYPTION_KEY = 'abcdefghijklmnopqrstuvxyz01234567890123456789';
5 |
6 | describe('decrypt', () => {
7 | it('should decrypt encrypted value with random initialization vector', () => {
8 | // GIVEN
9 | const encryptedValue = encrypt('4');
10 |
11 | // WHEN
12 | const decryptedValue = decrypt(encryptedValue);
13 |
14 | // THEN
15 | expect(decryptedValue).toEqual('4');
16 | });
17 |
18 | it('should decrypt encrypted value with fixed initialization vector', () => {
19 | // GIVEN
20 | const encryptedValue = encrypt('4234 1111 1111 1111', false);
21 | const encryptedValue2 = encrypt('4234 1111 1111 1111', false);
22 |
23 | // WHEN
24 | const decryptedValue = decrypt(encryptedValue);
25 |
26 | // THEN
27 | expect(decryptedValue).toEqual('4234 1111 1111 1111');
28 | expect(encryptedValue).toEqual(encryptedValue2);
29 | });
30 | });
31 |
--------------------------------------------------------------------------------
/src/crypt/decrypt.ts:
--------------------------------------------------------------------------------
1 | import { createDecipheriv } from 'crypto';
2 |
3 | export default function decrypt(encryptedValue: string) {
4 | const encryptionKey = process.env.ENCRYPTION_KEY;
5 |
6 | if (!encryptionKey) {
7 | throw new Error('Encryption key must be provided environment variable ENCRYPTION_KEY');
8 | } else if (encryptionKey.length < 32) {
9 | throw new Error('Encryption key must be 32 characters long');
10 | }
11 |
12 | const encryptedValueBuffer = Buffer.from(encryptedValue, 'base64');
13 | const initializationVector = encryptedValueBuffer.slice(0, 16);
14 | const decipher = createDecipheriv('aes-256-cbc', encryptionKey.slice(0, 32), initializationVector);
15 | return decipher.update(encryptedValueBuffer.slice(16)).toString('utf-8') + decipher.final('utf-8');
16 | }
17 |
--------------------------------------------------------------------------------
/src/crypt/hash.ts:
--------------------------------------------------------------------------------
1 | import * as argon2 from 'argon2';
2 |
3 | export default async function hash(value: string) {
4 | if (value.startsWith('$argon2id$v=19$m=4096,t=5,p=1')) {
5 | const [, saltAndHashValue] = value.split('$argon2id$v=19$m=4096,t=5,p=1');
6 | if (saltAndHashValue.length === 67) {
7 | return Promise.resolve(value);
8 | }
9 | }
10 |
11 | return await argon2.hash(value, {
12 | type: argon2.argon2id,
13 | timeCost: 5
14 | });
15 | }
16 |
17 |
18 |
--------------------------------------------------------------------------------
/src/crypt/shouldHashValue.ts:
--------------------------------------------------------------------------------
1 | import typePropertyAnnotationContainer from '../decorators/typeproperty/typePropertyAnnotationContainer';
2 |
3 | export default function shouldHashValue(propertyName: string, EntityClass: Function): boolean {
4 | return (
5 | (propertyName.toLowerCase().includes('password') ||
6 | typePropertyAnnotationContainer.isTypePropertyHashed(EntityClass, propertyName)) &&
7 | !typePropertyAnnotationContainer.isTypePropertyNotHashed(EntityClass, propertyName)
8 | );
9 | }
10 |
--------------------------------------------------------------------------------
/src/crypt/shouldUseRandomInitializationVector.ts:
--------------------------------------------------------------------------------
1 | export default function shouldUseRandomInitializationVector(propertyName: string): boolean {
2 | const lowerCasePropertyName = propertyName.toLowerCase();
3 | return !(
4 | lowerCasePropertyName === 'user' ||
5 | lowerCasePropertyName.includes('username') ||
6 | lowerCasePropertyName.includes('user_name')
7 | );
8 | }
9 |
--------------------------------------------------------------------------------
/src/datastore/hooks/EntitiesPostHook.ts:
--------------------------------------------------------------------------------
1 | import { BackkEntity } from "../../types/entities/BackkEntity";
2 | import { PromiseErrorOr } from "../../types/PromiseErrorOr";
3 | import { SubEntity } from "../../types/entities/SubEntity";
4 | import { One } from "../DataStore";
5 | import { ErrorDefinition } from "../../types/ErrorDefinition";
6 |
7 | export type EntitiesPostHook =
8 | | {
9 | executePostHookIf?: (entity: T[] | null) => boolean;
10 | shouldSucceedOrBeTrue: (
11 | entity: T[] | null
12 | ) => PromiseErrorOr | null> | Promise | boolean;
13 | error?: ErrorDefinition;
14 | }
15 | | ((entity: T[] | null) => PromiseErrorOr | null> | Promise | boolean);
16 |
--------------------------------------------------------------------------------
/src/datastore/hooks/EntityPreHook.ts:
--------------------------------------------------------------------------------
1 | import { BackkEntity } from '../../types/entities/BackkEntity';
2 | import { SubEntity } from '../../types/entities/SubEntity';
3 | import { PromiseErrorOr } from '../../types/PromiseErrorOr';
4 | import { BackkError } from '../../types/BackkError';
5 | import { Many, One } from "../DataStore";
6 | import { ErrorDefinition } from "../../types/ErrorDefinition";
7 |
8 | export type EntityPreHook =
9 | | {
10 | executePreHookIf?: (entity: T) => boolean | Promise | PromiseErrorOr;
11 | shouldSucceedOrBeTrue: (entity: T) =>
12 | | PromiseErrorOr | Many | null>
13 | | Promise
14 | | boolean;
15 | error?: ErrorDefinition;
16 | }
17 | | ((entity: T) =>
18 | | PromiseErrorOr | One | null>
19 | | Promise
20 | | boolean);
21 |
--------------------------------------------------------------------------------
/src/datastore/hooks/PostHook.ts:
--------------------------------------------------------------------------------
1 | import { BackkEntity } from "../../types/entities/BackkEntity";
2 | import { PromiseErrorOr } from "../../types/PromiseErrorOr";
3 | import { SubEntity } from "../../types/entities/SubEntity";
4 | import { One } from "../DataStore";
5 | import { ErrorDefinition } from "../../types/ErrorDefinition";
6 |
7 | export type PostHook =
8 | | {
9 | executePostHookIf?: (entity: T | null) => boolean;
10 | shouldSucceedOrBeTrue: (
11 | entity: T | null
12 | ) => PromiseErrorOr | null> | Promise | boolean;
13 | error?: ErrorDefinition;
14 | }
15 | | ((entity: T | null) => PromiseErrorOr | null> | Promise | boolean);
16 |
--------------------------------------------------------------------------------
/src/datastore/hooks/PreHook.ts:
--------------------------------------------------------------------------------
1 | import { BackkEntity } from '../../types/entities/BackkEntity';
2 | import { PromiseErrorOr } from '../../types/PromiseErrorOr';
3 | import { BackkError } from '../../types/BackkError';
4 | import { Many, One } from "../DataStore";
5 | import { ErrorDefinition } from "../../types/ErrorDefinition";
6 |
7 | export type PreHook<> =
8 | | {
9 | executePreHookIf?: () => boolean | Promise | PromiseErrorOr;
10 | shouldSucceedOrBeTrue: () =>
11 | | PromiseErrorOr | One | null>
12 | | Promise
13 | | boolean;
14 | error?: ErrorDefinition;
15 | }
16 | | (() =>
17 | | PromiseErrorOr | One | null>
18 | | Promise
19 | | boolean);
20 |
--------------------------------------------------------------------------------
/src/datastore/mongodb/MongoDbFilter.ts:
--------------------------------------------------------------------------------
1 | import { FilterQuery } from "mongodb";
2 | import shouldUseRandomInitializationVector from "../../crypt/shouldUseRandomInitializationVector";
3 | import shouldEncryptValue from "../../crypt/shouldEncryptValue";
4 | import encrypt from "../../crypt/encrypt";
5 |
6 | export default class MongoDbFilter {
7 | subEntityPath: string;
8 | filterQuery: FilterQuery;
9 |
10 | constructor(filterQuery: FilterQuery, subEntityPath?: string) {
11 | this.subEntityPath = subEntityPath ?? '';
12 | this.filterQuery = {}
13 |
14 | Object.entries(filterQuery).forEach(([fieldName, fieldValue]: [string, any]) => {
15 | let finalFieldValue = fieldValue;
16 |
17 | if (!shouldUseRandomInitializationVector(fieldName) && shouldEncryptValue(fieldName)) {
18 | finalFieldValue = encrypt(fieldValue, false);
19 | }
20 |
21 | (this.filterQuery as any)[fieldName] = finalFieldValue;
22 | })
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/datastore/mongodb/convertFilterObjectToMongoDbQueries.ts:
--------------------------------------------------------------------------------
1 | import MongoDbFilter from "./MongoDbFilter";
2 |
3 | export default function convertFilterObjectToMongoDbQueries(filters: object): MongoDbFilter[] {
4 | return Object.entries(filters).map(([fieldPathName, fieldValue]) => {
5 | const lastDotPosition = fieldPathName.lastIndexOf('.');
6 |
7 | if (lastDotPosition !== -1) {
8 | const fieldName = fieldPathName.slice(lastDotPosition + 1);
9 | const subEntityPath = fieldPathName.slice(0, lastDotPosition);
10 | return new MongoDbFilter({ [fieldName]: fieldValue }, subEntityPath);
11 | }
12 |
13 | return new MongoDbFilter({ [fieldPathName]: fieldValue });
14 | });
15 | }
16 |
--------------------------------------------------------------------------------
/src/datastore/mongodb/convertMongoDbQueriesToMatchExpression.ts:
--------------------------------------------------------------------------------
1 | import MongoDbFilter from "./MongoDbFilter";
2 |
3 | export default function convertMongoDbQueriesToMatchExpression(filters: Array>) {
4 | return filters.reduce((matchExpression, mongoDbQuery) => {
5 | return {
6 | ...matchExpression,
7 | ...mongoDbQuery.filterQuery
8 | }
9 | }, {});
10 | }
11 |
--------------------------------------------------------------------------------
/src/datastore/mongodb/getDefaultIncludeFieldsMap.ts:
--------------------------------------------------------------------------------
1 | import getClassPropertyNameToPropertyTypeNameMap from '../../metadata/getClassPropertyNameToPropertyTypeNameMap';
2 |
3 | export default function getDefaultIncludeFieldsMap(EntityClass: new () => any) {
4 | const entityPropertyNameToEntityPropertyTypeNameMap = getClassPropertyNameToPropertyTypeNameMap(
5 | EntityClass
6 | );
7 |
8 | return Object.keys(entityPropertyNameToEntityPropertyTypeNameMap).reduce(
9 | (defaultIncludeFieldsMap, propertyName) => ({
10 | ...defaultIncludeFieldsMap,
11 | [propertyName]: 1
12 | }),
13 | {}
14 | );
15 | }
16 |
--------------------------------------------------------------------------------
/src/datastore/mongodb/getExcludeFieldsMap.ts:
--------------------------------------------------------------------------------
1 | export default function getExcludeFieldsMap(excludeResponseFields?: string[]): object {
2 | return excludeResponseFields
3 | ? excludeResponseFields.reduce(
4 | (excludeFieldsMap: object, excludeResponseField: string) => ({
5 | ...excludeFieldsMap,
6 | [excludeResponseField]: 0
7 | }),
8 | {}
9 | )
10 | : {};
11 | }
12 |
--------------------------------------------------------------------------------
/src/datastore/mongodb/getFieldOrdering.ts:
--------------------------------------------------------------------------------
1 | import getClassPropertyNameToPropertyTypeNameMap from '../../metadata/getClassPropertyNameToPropertyTypeNameMap';
2 |
3 | export default function getFieldOrdering(EntityClass: new () => any) {
4 | const entityPropertyNameToEntityPropertyTypeNameMap = getClassPropertyNameToPropertyTypeNameMap(
5 | EntityClass
6 | );
7 |
8 | const newRoot = Object.keys(entityPropertyNameToEntityPropertyTypeNameMap).reduce(
9 | (defaultIncludeFieldsMap, propertyName) => ({
10 | ...defaultIncludeFieldsMap,
11 | [propertyName]: `$${propertyName}`
12 | }),
13 | {}
14 | );
15 |
16 | return {
17 | $replaceRoot: {
18 | newRoot
19 | }
20 | };
21 | }
22 |
--------------------------------------------------------------------------------
/src/datastore/mongodb/getIncludeFieldsMap.ts:
--------------------------------------------------------------------------------
1 | export default function getIncludeFieldsMap(includeResponseFields?: string[]): object {
2 | return includeResponseFields
3 | ? includeResponseFields.reduce(
4 | (includeFieldsMap: object, includeResponseField: string) => ({
5 | ...includeFieldsMap,
6 | [includeResponseField]: 1
7 | }),
8 | {}
9 | )
10 | : {};
11 | }
12 |
--------------------------------------------------------------------------------
/src/datastore/mongodb/getJoinPipelines.ts:
--------------------------------------------------------------------------------
1 | import entityAnnotationContainer from "../../decorators/entity/entityAnnotationContainer";
2 |
3 | export default function getJoinPipelines(EntityClass: Function, Types: object) {
4 | let joinPipelines: any[] = [];
5 |
6 | if (entityAnnotationContainer.entityNameToJoinsMap[EntityClass.name]) {
7 | joinPipelines = entityAnnotationContainer.entityNameToJoinsMap[EntityClass.name]
8 | .map((joinSpec) => {
9 | return [
10 | { $addFields: { entityIdFieldNameAsString: { $toString: `$${joinSpec.entityIdFieldName}` } } },
11 | {
12 | $lookup: {
13 | from: joinSpec.subEntityTableName,
14 | localField: 'entityIdFieldNameAsString',
15 | foreignField: joinSpec.subEntityForeignIdFieldName,
16 | as: joinSpec.asFieldName
17 | }
18 | }
19 | ];
20 | })
21 | .flat();
22 | }
23 |
24 | return joinPipelines;
25 | }
26 |
--------------------------------------------------------------------------------
/src/datastore/mongodb/removeSubEntities.ts:
--------------------------------------------------------------------------------
1 | export default function removeSubEntities(entity: any, subEntities: any[]) {
2 | Object.entries(entity).forEach(([propertyName, propertyValueOrValues]: [string, any]) => {
3 | if (
4 | Array.isArray(propertyValueOrValues) &&
5 | propertyValueOrValues.length > 0
6 | ) {
7 | entity[propertyName] = propertyValueOrValues.filter(
8 | (propertyValue) => !subEntities.find((subEntity) => subEntity === propertyValue)
9 | );
10 | }
11 |
12 | if (typeof propertyValueOrValues === 'object' && propertyValueOrValues !== null) {
13 | if (
14 | Array.isArray(propertyValueOrValues) &&
15 | propertyValueOrValues.length > 0 &&
16 | typeof propertyValueOrValues[0] === 'object'
17 | ) {
18 | propertyValueOrValues.forEach((propertyValue) => removeSubEntities(propertyValue, subEntities));
19 | } else {
20 | removeSubEntities(propertyValueOrValues, subEntities);
21 | }
22 | }
23 | });
24 | }
25 |
--------------------------------------------------------------------------------
/src/datastore/mongodb/replaceFieldPathNames.ts:
--------------------------------------------------------------------------------
1 | export default function replaceFieldPathNames(
2 | fields: string[] | undefined,
3 | wantedSubEntityPath: string
4 | ): string[] {
5 | return (
6 | fields
7 | ?.filter((field) => {
8 | return field.startsWith(wantedSubEntityPath);
9 | })
10 | .map((field) => {
11 | let [, fieldPathPostFix] = field.split(wantedSubEntityPath);
12 | if (fieldPathPostFix[0] === '.') {
13 | fieldPathPostFix = fieldPathPostFix.slice(1);
14 | }
15 | return fieldPathPostFix;
16 | }) ?? []
17 | );
18 | }
19 |
--------------------------------------------------------------------------------
/src/datastore/mongodb/replaceIdStringsWithObjectIds.ts:
--------------------------------------------------------------------------------
1 | import { ObjectId } from 'mongodb';
2 |
3 | export default function replaceIdStringsWithObjectIds(filters: any, prevFieldName = ''): void {
4 | Object.entries(filters).forEach(([filterName, filterValue]: [string, any]) => {
5 | if (filterName.endsWith('_id') && typeof filterValue === 'string') {
6 | filters[filterName] = new ObjectId(filterValue);
7 | } else if (
8 | prevFieldName.endsWith('_id') &&
9 | filterName === '$in' &&
10 | Array.isArray(filterValue) &&
11 | filterValue.length > 0 &&
12 | typeof filterValue[0] === 'string'
13 | ) {
14 | filters[filterName] = filterValue.map((filterValue) => new ObjectId(filterValue));
15 | }
16 |
17 | if (typeof filterValue === 'object' && filterValue !== null && !Array.isArray(filterValue)) {
18 | replaceIdStringsWithObjectIds(filterValue, filterName.startsWith('$') ? prevFieldName : filterName);
19 | }
20 | });
21 | }
22 |
--------------------------------------------------------------------------------
/src/datastore/mongodb/replaceSubEntityPaths.ts:
--------------------------------------------------------------------------------
1 | export default function replaceSubEntityPaths(
2 | operations: T[] | undefined,
3 | wantedSubEntityPath: string
4 | ): T[] {
5 | return (
6 | operations
7 | ?.filter((operation) => {
8 | return operation.subEntityPath === wantedSubEntityPath || operation.subEntityPath === '*';
9 | })
10 | .map((operation) => {
11 | let newSubEntityPath = operation.subEntityPath;
12 |
13 | if (operation.subEntityPath !== '*') {
14 | [, newSubEntityPath] = (operation.subEntityPath ?? '').split(wantedSubEntityPath);
15 |
16 | if (newSubEntityPath?.[0] === '.') {
17 | newSubEntityPath = newSubEntityPath.slice(1);
18 | }
19 | }
20 |
21 | return {
22 | ...operation,
23 | subEntityPath: newSubEntityPath
24 | };
25 | }) ?? []
26 | );
27 | }
28 |
--------------------------------------------------------------------------------
/src/datastore/mongodb/transformResponse.ts:
--------------------------------------------------------------------------------
1 | import pick from 'lodash/pick';
2 | import omit from 'lodash/omit';
3 | import { PostQueryOperations } from "../../types/postqueryoperations/PostQueryOperations";
4 |
5 | export default function transformResponse(
6 | responseObjects: T[],
7 | args: PostQueryOperations
8 | ): Array> {
9 | return responseObjects.map((responseObject) => {
10 | let newResponseObject: Partial = responseObject;
11 | if (args.includeResponseFields) {
12 | newResponseObject = pick(responseObject, args.includeResponseFields);
13 | }
14 | if (args.excludeResponseFields) {
15 | newResponseObject = omit(newResponseObject, args.excludeResponseFields);
16 | }
17 | return newResponseObject;
18 | });
19 | }
20 |
--------------------------------------------------------------------------------
/src/datastore/mongodb/tryCreateMongoDbIndexesForUniqueFields.ts:
--------------------------------------------------------------------------------
1 | import MongoDbDataStore from '../MongoDbDataStore';
2 | import forEachAsyncSequential from '../../utils/forEachAsyncSequential';
3 | import getClassPropertyNameToPropertyTypeNameMap from '../../metadata/getClassPropertyNameToPropertyTypeNameMap';
4 | import typePropertyAnnotationContainer from '../../decorators/typeproperty/typePropertyAnnotationContainer';
5 |
6 | export default async function tryCreateMongoDbIndexesForUniqueFields(
7 | dataStore: MongoDbDataStore,
8 | EntityClass: Function
9 | ) {
10 | const entityMetadata = getClassPropertyNameToPropertyTypeNameMap(EntityClass as any);
11 |
12 | await forEachAsyncSequential(Object.keys(entityMetadata), async (fieldName) => {
13 | if (typePropertyAnnotationContainer.isTypePropertyUnique(EntityClass, fieldName)) {
14 | await dataStore.tryReserveDbConnectionFromPool();
15 |
16 | await dataStore.executeMongoDbOperationsOrThrow(false, async (client) => {
17 | await client.db(dataStore.getDbName()).createIndex(
18 | EntityClass.name.toLowerCase(),
19 | { [fieldName]: 1 },
20 | {
21 | unique: true
22 | }
23 | );
24 | });
25 | }
26 | });
27 | }
28 |
--------------------------------------------------------------------------------
/src/datastore/sql/filters/SqlFilter.ts:
--------------------------------------------------------------------------------
1 | import shouldUseRandomInitializationVector from "../../../crypt/shouldUseRandomInitializationVector";
2 | import shouldEncryptValue from "../../../crypt/shouldEncryptValue";
3 | import encrypt from "../../../crypt/encrypt";
4 |
5 | export default class SqlFilter {
6 | constructor(readonly sqlExpression: string, readonly values?: object, readonly subEntityPath = '') {}
7 |
8 | // eslint-disable-next-line @typescript-eslint/no-unused-vars
9 | toSqlString(): string {
10 | return this.sqlExpression;
11 | }
12 |
13 | getValues(): object | undefined {
14 | if (this.values) {
15 | return Object.entries(this.values).reduce((filterValues, [fieldName, fieldValue]) => {
16 | let finalFieldValue = fieldValue;
17 |
18 | if (!shouldUseRandomInitializationVector(fieldName) && shouldEncryptValue(fieldName)) {
19 | finalFieldValue = encrypt(fieldValue as any, false);
20 | }
21 |
22 | return {
23 | ...filterValues,
24 | [fieldName]: finalFieldValue
25 | };
26 | }, {});
27 | }
28 |
29 | return this.values;
30 | }
31 |
32 | hasValues(): boolean {
33 | return Object.values(this.values || {}).reduce(
34 | (hasValues: boolean, value: any) => hasValues && value !== undefined,
35 | true
36 | );
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/datastore/sql/filters/SqlNotInFilter.ts:
--------------------------------------------------------------------------------
1 | import SqlInFilter from './SqlInFilter';
2 |
3 | export default class SqlNotInFilter extends SqlInFilter {
4 | constructor(
5 | readonly fieldName: string,
6 | readonly notInExpressionValues?: any[],
7 | subEntityPath = '',
8 | fieldExpression?: string
9 | ) {
10 | super(fieldName, notInExpressionValues, subEntityPath, fieldExpression);
11 | }
12 |
13 | toSqlString(): string {
14 | if (!this.inExpressionValues) {
15 | return '';
16 | }
17 |
18 | const values = this.inExpressionValues
19 | .map(
20 | (_, index) =>
21 | ':' +
22 | this.subEntityPath.replace('_', 'xx') +
23 | 'xx' +
24 | this.fieldName.replace('_', 'xx') +
25 | (index + 1).toString()
26 | )
27 | .join(', ');
28 |
29 | return (this.fieldExpression ?? this.fieldName) + ' NOT IN (' + values + ')';
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/datastore/sql/operations/ddl/removeDbInitialization.ts:
--------------------------------------------------------------------------------
1 | import AbstractSqlDataStore from '../../../AbstractSqlDataStore';
2 | import { DataStore } from '../../../DataStore';
3 |
4 | let intervalId: NodeJS.Timeout | undefined = undefined; // NOSONAR
5 | const RETRY_INTERVAL = 15000;
6 |
7 | export default async function removeDbInitialization(dataStore: DataStore) {
8 | if (process.env.NODE_ENV === 'development') {
9 | return;
10 | }
11 |
12 | if (dataStore instanceof AbstractSqlDataStore) {
13 | const removeAppVersionSql = `DELETE FROM ${dataStore
14 | .getSchema()
15 | .toLowerCase()}.__backk_db_initialization WHERE microserviceversion = ${dataStore.getValuePlaceholder(
16 | 1
17 | )}`;
18 |
19 | try {
20 | await dataStore.tryExecuteSqlWithoutCls(removeAppVersionSql, [process.env.MICROSERVICE_VERSION]);
21 | } catch (error) {
22 | if (intervalId !== undefined) {
23 | clearInterval(intervalId);
24 | }
25 |
26 | intervalId = setInterval(() => removeDbInitialization(dataStore), RETRY_INTERVAL);
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/datastore/sql/operations/ddl/removeDbInitializationWhenPendingTooLong.ts:
--------------------------------------------------------------------------------
1 | import AbstractSqlDataStore from '../../../AbstractSqlDataStore';
2 |
3 | export default async function removeDbInitializationWhenPendingTooLong(dataStore: AbstractSqlDataStore) {
4 | if (process.env.NODE_ENV === 'development') {
5 | return;
6 | }
7 |
8 | const removeAppVersionSql = `DELETE FROM ${dataStore.getSchema().toLowerCase()}.__backk_db_initialization WHERE microserviceversion = ${dataStore.getValuePlaceholder(1)} AND isinitialized = 0 AND createdattimestamp <= current_timestamp - INTERVAL '5' minute`;
9 |
10 | try {
11 | await dataStore.tryExecuteSqlWithoutCls(removeAppVersionSql, [process.env.MICROSERVICE_VERSION]);
12 | } catch (error) {
13 | // No operation
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/datastore/sql/operations/ddl/setDbInitialized.ts:
--------------------------------------------------------------------------------
1 | import AbstractSqlDataStore from '../../../AbstractSqlDataStore';
2 |
3 | export default async function setDbInitialized(dataStore: AbstractSqlDataStore) {
4 | if (process.env.NODE_ENV === 'development') {
5 | return;
6 | }
7 |
8 | const modifyAppVersionInitializationSql = `UPDATE ${dataStore.getSchema().toLowerCase()}.__backk_db_initialization SET isinitialized = 1 WHERE microserviceversion = ${dataStore.getValuePlaceholder(1)}`;
9 |
10 | await dataStore.tryExecuteSqlWithoutCls(modifyAppVersionInitializationSql, [process.env.MICROSERVICE_VERSION]);
11 | }
12 |
--------------------------------------------------------------------------------
/src/datastore/sql/operations/ddl/shouldInitializeDb.ts:
--------------------------------------------------------------------------------
1 | import AbstractSqlDataStore from '../../../AbstractSqlDataStore';
2 |
3 | export default async function shouldInitializeDb(dataStore: AbstractSqlDataStore) {
4 | if (process.env.NODE_ENV === 'development') {
5 | return true;
6 | }
7 |
8 | const addAppVersionSql = `INSERT INTO ${dataStore.getSchema().toLowerCase()}.__backk_db_initialization (microserviceversion, isInitialized, createdattimestamp) VALUES (${dataStore.getValuePlaceholder(1)}, 0, current_timestamp)`;
9 |
10 | try {
11 | await dataStore.tryExecuteSqlWithoutCls(addAppVersionSql, [process.env.MICROSERVICE_VERSION]);
12 | return true;
13 | } catch (error) {
14 | if (dataStore.isDuplicateEntityError(error)) {
15 | return false;
16 | }
17 |
18 | throw error;
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/datastore/sql/operations/ddl/tryAlterOrCreateTable.ts:
--------------------------------------------------------------------------------
1 | import { DataStore } from '../../../DataStore';
2 | import tryAlterTable from './tryAlterTable';
3 | import tryCreateTable from './tryCreateTable';
4 | import entityAnnotationContainer from "../../../../decorators/entity/entityAnnotationContainer";
5 |
6 | export default async function tryAlterOrCreateTable(
7 | dataStore: DataStore,
8 | entityName: string,
9 | EntityClass: Function,
10 | schema: string | undefined
11 | ) {
12 | let fields;
13 | let isPhysicalTable = true
14 |
15 | try {
16 | let tableName = entityName.toLowerCase();
17 |
18 | if (entityAnnotationContainer.entityNameToTableNameMap[entityName]) {
19 | tableName = entityAnnotationContainer.entityNameToTableNameMap[entityName].toLowerCase();
20 | isPhysicalTable = false;
21 | }
22 |
23 | fields = await dataStore.tryExecuteSqlWithoutCls(
24 | `SELECT * FROM ${schema?.toLowerCase()}.${tableName} LIMIT 1`,
25 | undefined,
26 | false
27 | );
28 | } catch (error) {
29 | await tryCreateTable(dataStore, entityName, EntityClass, schema, isPhysicalTable);
30 | return;
31 | }
32 |
33 | await tryAlterTable(dataStore, entityName, EntityClass, schema, fields);
34 | }
35 |
--------------------------------------------------------------------------------
/src/datastore/sql/operations/ddl/tryCreateUniqueIndex.ts:
--------------------------------------------------------------------------------
1 | import { DataStore } from '../../../DataStore';
2 | import tryCreateIndex from './tryCreateIndex';
3 |
4 | export default async function tryCreateUniqueIndex(
5 | dataStore: DataStore,
6 | indexName: string,
7 | schema: string | undefined,
8 | indexFields: string[]
9 | ) {
10 | await tryCreateIndex(dataStore, indexName, schema, indexFields, true);
11 | }
12 |
--------------------------------------------------------------------------------
/src/datastore/sql/operations/ddl/utils/addArrayValuesTableJoinSpec.ts:
--------------------------------------------------------------------------------
1 | import entityAnnotationContainer, {
2 | EntityJoinSpec
3 | } from '../../../../../decorators/entity/entityAnnotationContainer';
4 |
5 | export default function addArrayValuesTableJoinSpec(
6 | entityName: string,
7 | fieldName: string,
8 | subEntityForeignIdFieldName: string
9 | ) {
10 | let tableName = entityName;
11 |
12 | if (entityAnnotationContainer.entityNameToTableNameMap[entityName]) {
13 | tableName = entityAnnotationContainer.entityNameToTableNameMap[entityName];
14 | }
15 |
16 | const entityJoinSpec: EntityJoinSpec = {
17 | entityFieldName: fieldName,
18 | subEntityTableName: (tableName + '_' + fieldName.slice(0, -1)).toLowerCase(),
19 | entityIdFieldName: '_id',
20 | subEntityForeignIdFieldName,
21 | isReadonly: false
22 | };
23 |
24 | if (entityAnnotationContainer.entityNameToJoinsMap[entityName]) {
25 | entityAnnotationContainer.entityNameToJoinsMap[entityName].push(entityJoinSpec);
26 | } else {
27 | entityAnnotationContainer.entityNameToJoinsMap[entityName] = [entityJoinSpec];
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/datastore/sql/operations/ddl/utils/getEnumSqlColumnType.ts:
--------------------------------------------------------------------------------
1 | import parseEnumValuesFromSrcFile from '../../../../../typescript/parser/parseEnumValuesFromSrcFile';
2 | import getSrcFilePathNameForTypeName from '../../../../../utils/file/getSrcFilePathNameForTypeName';
3 | import { DataStore } from "../../../../DataStore";
4 |
5 | export default function getEnumSqlColumnType(dataStore: DataStore, baseFieldTypeName: string) {
6 | let enumValues: string[];
7 | if (baseFieldTypeName[0] === '(') {
8 | enumValues = baseFieldTypeName.slice(1).split(/[|)]/);
9 | } else {
10 | enumValues = parseEnumValuesFromSrcFile(getSrcFilePathNameForTypeName(baseFieldTypeName));
11 | }
12 |
13 | const firstEnumValue = enumValues[0];
14 | if (firstEnumValue[0] === "'") {
15 | const enumValueLengths = enumValues.map((enumValue) => enumValue.length);
16 | const maxEnumValueLength = Math.max(...enumValueLengths);
17 | return dataStore.getVarCharType(maxEnumValueLength);
18 | } else {
19 | const hasFloat = enumValues.reduce(
20 | (hasFloat: boolean, enumValue: string) =>
21 | hasFloat || parseInt(enumValue, 10).toString().length !== enumValue.length,
22 | false
23 | );
24 |
25 | if (hasFloat) {
26 | return 'DOUBLE PRECISION';
27 | } else {
28 | return 'INTEGER';
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/datastore/sql/operations/ddl/utils/getSqlColumnType.ts:
--------------------------------------------------------------------------------
1 | import { DataStore } from '../../../../DataStore';
2 | import getMaxLengthValidationConstraint from '../../../../../validation/getMaxLengthValidationConstraint';
3 | import typePropertyAnnotationContainer from '../../../../../decorators/typeproperty/typePropertyAnnotationContainer';
4 |
5 | export default function getSqlColumnType(
6 | dataStore: DataStore,
7 | EntityClass: Function,
8 | fieldName: string,
9 | baseFieldTypeName: string
10 | ): string | undefined {
11 | switch (baseFieldTypeName) {
12 | case 'integer':
13 | return 'INTEGER';
14 | case 'bigint':
15 | return 'BIGINT';
16 | case 'number':
17 | return 'DOUBLE PRECISION';
18 | case 'boolean':
19 | return 'BOOLEAN';
20 | case 'Date':
21 | return dataStore.getTimestampType();
22 | case 'string':
23 | if (
24 | (fieldName.endsWith('Id') || fieldName === 'id') &&
25 | !typePropertyAnnotationContainer.isTypePropertyExternalId(EntityClass, fieldName)
26 | ) {
27 | return 'BIGINT';
28 | } else {
29 | const maxLength = getMaxLengthValidationConstraint(EntityClass, fieldName);
30 | return dataStore.getVarCharType(maxLength);
31 | }
32 | }
33 |
34 | return undefined;
35 | }
36 |
--------------------------------------------------------------------------------
/src/datastore/sql/operations/dml/utils/getSubEntitiesByAction.ts:
--------------------------------------------------------------------------------
1 | export default function getSubEntitiesByAction(
2 | newSubEntities: T[],
3 | currentSubEntities: T[]
4 | ) {
5 | const subEntitiesToDelete = currentSubEntities.filter(
6 | (currentSubEntity) => !newSubEntities.some((newSubEntity) => currentSubEntity.id === newSubEntity.id)
7 | );
8 |
9 | const subEntitiesToAdd = newSubEntities.filter(
10 | (newSubEntity) => !currentSubEntities.some((currentSubEntity) => currentSubEntity.id === newSubEntity.id)
11 | );
12 |
13 | const subEntitiesToUpdate = newSubEntities.filter((newSubEntity) =>
14 | currentSubEntities.some((currentSubEntity) => currentSubEntity.id === newSubEntity.id)
15 | );
16 |
17 | return {
18 | subEntitiesToAdd,
19 | subEntitiesToDelete,
20 | subEntitiesToUpdate
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/datastore/sql/operations/dml/utils/tryUpdateEntityVersionAndLastModifiedTimestampIfNeeded.ts:
--------------------------------------------------------------------------------
1 | import { DataStore, One } from "../../../../DataStore";
2 | import { BackkEntity } from "../../../../../types/entities/BackkEntity";
3 | import throwIf from "../../../../../utils/exception/throwIf";
4 |
5 | export default async function tryUpdateEntityVersionAndLastModifiedTimestampIfNeeded(
6 | dataStore: DataStore,
7 | currentEntity: One,
8 | EntityClass: new () => T
9 | ) {
10 | if ('version' in currentEntity.data || 'lastModifiedTimestamp' in currentEntity.data) {
11 | const [, error] = await dataStore.updateEntity(EntityClass, {
12 | _id: currentEntity.data._id
13 | } as any);
14 |
15 | throwIf(error);
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/datastore/sql/operations/dql/utils/columns/getSqlColumnForFieldName.ts:
--------------------------------------------------------------------------------
1 | import tryGetProjection from "../../clauses/tryGetProjection";
2 | import getSqlColumnFromProjection from "./getSqlColumnFromProjection";
3 | import AbstractSqlDataStore from "../../../../../AbstractSqlDataStore";
4 | import createErrorFromErrorCodeMessageAndStatus
5 | from "../../../../../../errors/createErrorFromErrorCodeMessageAndStatus";
6 | import { BACKK_ERRORS } from "../../../../../../errors/BACKK_ERRORS";
7 |
8 | export default function tryGetSqlColumnForFieldName(
9 | fieldName: string,
10 | dataStore: AbstractSqlDataStore,
11 | entityClass: Function,
12 | Types: object
13 | ): string {
14 | let projection;
15 | try {
16 | projection = tryGetProjection(dataStore, { includeResponseFields: [fieldName] }, entityClass, Types);
17 | } catch (error) {
18 | throw createErrorFromErrorCodeMessageAndStatus({
19 | ...BACKK_ERRORS.INVALID_ARGUMENT,
20 | message: BACKK_ERRORS.INVALID_ARGUMENT.message + 'invalid sub pagination field: ' + fieldName
21 | });
22 | }
23 |
24 | return getSqlColumnFromProjection(projection);
25 | }
26 |
--------------------------------------------------------------------------------
/src/datastore/sql/operations/dql/utils/columns/getSqlColumnFromProjection.ts:
--------------------------------------------------------------------------------
1 | export default function getSqlColumnFromProjection(projection: string) {
2 | const leftSideOfAs = projection.split(' AS ')[0];
3 |
4 | if (leftSideOfAs.startsWith('CAST(')) {
5 | return leftSideOfAs.slice(5);
6 | }
7 |
8 | return leftSideOfAs;
9 | }
10 |
--------------------------------------------------------------------------------
/src/datastore/sql/operations/dql/utils/convertFilterObjectToSqlEquals.ts:
--------------------------------------------------------------------------------
1 | import SqlEqFilter from '../../../filters/SqlEqFilter';
2 |
3 | export default function convertFilterObjectToSqlEquals(filters: object): SqlEqFilter[] {
4 | return Object.entries(filters).map(([fieldPathName, fieldValue]) => {
5 | const lastDotPosition = fieldPathName.lastIndexOf('.');
6 |
7 | if (lastDotPosition !== -1) {
8 | const fieldName = fieldPathName.slice(lastDotPosition + 1);
9 | const subEntityPath = fieldPathName.slice(0, lastDotPosition);
10 | return new SqlEqFilter({ [fieldName]: fieldValue }, subEntityPath);
11 | }
12 |
13 | return new SqlEqFilter({ [fieldPathName]: fieldValue });
14 | });
15 | }
16 |
--------------------------------------------------------------------------------
/src/datastore/sql/operations/dql/utils/updateDbLocalTransactionCount.ts:
--------------------------------------------------------------------------------
1 | import { DataStore } from "../../../../DataStore";
2 | import { getNamespace } from "cls-hooked";
3 |
4 | export default function updateDbLocalTransactionCount(dataStore: DataStore) {
5 | if (
6 | !dataStore.getClsNamespace()?.get('localTransaction') &&
7 | !dataStore.getClsNamespace()?.get('globalTransaction') &&
8 | !getNamespace('multipleServiceFunctionExecutions')?.get('globalTransaction')
9 | ) {
10 | dataStore
11 | .getClsNamespace()
12 | ?.set('dbLocalTransactionCount', dataStore.getClsNamespace()?.get('dbLocalTransactionCount') + 1);
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/datastore/sql/operations/transaction/cleanupLocalTransactionIfNeeded.ts:
--------------------------------------------------------------------------------
1 | import { DataStore } from "../../../DataStore";
2 |
3 | export default function cleanupLocalTransactionIfNeeded(isInsideTransaction: boolean, dataStore: DataStore) {
4 | if (isInsideTransaction) {
5 | dataStore.getClsNamespace()?.set('localTransaction', false);
6 | dataStore.cleanupTransaction();
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/src/datastore/sql/operations/transaction/tryCommitLocalTransactionIfNeeded.ts:
--------------------------------------------------------------------------------
1 | import AbstractSqlDataStore from "../../../AbstractSqlDataStore";
2 |
3 | export default async function tryCommitLocalTransactionIfNeeded(isInTransaction: boolean, dataStore: AbstractSqlDataStore) {
4 | if (isInTransaction) {
5 | await dataStore.tryCommitTransaction();
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/src/datastore/sql/operations/transaction/tryRollbackLocalTransactionIfNeeded.ts:
--------------------------------------------------------------------------------
1 | import AbstractSqlDataStore from "../../../AbstractSqlDataStore";
2 |
3 | export default async function tryRollbackLocalTransactionIfNeeded(isInTransaction: boolean, dataStore: AbstractSqlDataStore) {
4 | if (isInTransaction) {
5 | await dataStore.tryRollbackTransaction();
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/src/datastore/sql/operations/transaction/tryStartLocalTransactionIfNeeded.ts:
--------------------------------------------------------------------------------
1 | import { getNamespace } from "cls-hooked";
2 | import { DataStore } from "../../../DataStore";
3 |
4 | export default async function tryStartLocalTransactionIfNeeded(
5 | dataStore: DataStore
6 | ): Promise {
7 | if (
8 | !getNamespace('multipleServiceFunctionExecutions')?.get('globalTransaction') &&
9 | !dataStore.getClsNamespace()?.get('globalTransaction') &&
10 | !dataStore.getClsNamespace()?.get('localTransaction')
11 | ) {
12 | await dataStore.tryBeginTransaction();
13 | dataStore.getClsNamespace()?.set('localTransaction', true);
14 | dataStore.getClsNamespace()?.set('session', dataStore.getClient()?.startSession());
15 | dataStore
16 | .getClsNamespace()
17 | ?.set('dbLocalTransactionCount', dataStore.getClsNamespace()?.get('dbLocalTransactionCount') + 1);
18 | return true;
19 | }
20 |
21 | return false;
22 | }
23 |
--------------------------------------------------------------------------------
/src/datastore/utils/createCurrentPageTokens.ts:
--------------------------------------------------------------------------------
1 | import { createHmac } from "crypto";
2 | import Pagination from "../../types/postqueryoperations/Pagination";
3 | import throwException from "../../utils/exception/throwException";
4 |
5 | export default function createCurrentPageTokens(paginations: Pagination[] | undefined) {
6 | return paginations?.map(({ subEntityPath, pageNumber }) => ({
7 | subEntityPath,
8 | currentPageToken: createHmac(
9 | 'sha256',
10 | process.env.ENCRYPTION_KEY ?? throwException('Environment variable ENCRYPTION_KEY is not defined')
11 | )
12 | .update(pageNumber.toString())
13 | .digest('base64')
14 | }));
15 | }
16 |
--------------------------------------------------------------------------------
/src/datastore/utils/getTableName.ts:
--------------------------------------------------------------------------------
1 | import entityAnnotationContainer from "../../decorators/entity/entityAnnotationContainer";
2 |
3 | export default function getTableName(entityName: string): string {
4 | if (entityAnnotationContainer.entityNameToTableNameMap[entityName]) {
5 | return entityAnnotationContainer.entityNameToTableNameMap[entityName].toLowerCase()
6 | }
7 |
8 | return entityName.toLowerCase();
9 | }
10 |
11 | export function getEntityName(entityName: string): string {
12 | if (entityAnnotationContainer.entityNameToTableNameMap[entityName]) {
13 | return entityAnnotationContainer.entityNameToTableNameMap[entityName]
14 | }
15 |
16 | return entityName;
17 | }
18 |
--------------------------------------------------------------------------------
/src/datastore/utils/getUserAccountIdFieldNameAndRequiredValue.ts:
--------------------------------------------------------------------------------
1 | import { DataStore } from '../DataStore';
2 |
3 | export default function getUserAccountIdFieldNameAndRequiredValue(dataStore: DataStore) {
4 | return [
5 | dataStore.getClsNamespace()?.get('userAccountIdFieldName'),
6 | dataStore.getClsNamespace()?.get('userAccountId')
7 | ];
8 | }
9 |
--------------------------------------------------------------------------------
/src/datastore/utils/recordDbOperationDuration.ts:
--------------------------------------------------------------------------------
1 | import { DataStore } from '../DataStore';
2 | import defaultServiceMetrics from '../../observability/metrics/defaultServiceMetrics';
3 |
4 | export default function recordDbOperationDuration(dataStore: DataStore, startTimeInMillis: number) {
5 | const dbOperationProcessingTimeInMillis = Date.now() - startTimeInMillis;
6 | defaultServiceMetrics.incrementDbOperationProcessingTimeInSecsBucketCounterByOne(
7 | dataStore.getDataStoreType(),
8 | dataStore.getDbHost(),
9 | dbOperationProcessingTimeInMillis / 1000
10 | );
11 | }
12 |
--------------------------------------------------------------------------------
/src/datastore/utils/startDbOperation.ts:
--------------------------------------------------------------------------------
1 | import log, { Severity } from "../../observability/logging/log";
2 | import { DataStore } from "../DataStore";
3 |
4 | export default function startDbOperation(dataStore: DataStore, dataStoreOperationName: string): number {
5 | log(Severity.DEBUG, 'Database manager operation', dataStore.constructor.name + '.' + dataStoreOperationName);
6 | return Date.now();
7 | }
8 |
--------------------------------------------------------------------------------
/src/decorators/entity/CompositeIndex.ts:
--------------------------------------------------------------------------------
1 | import entityContainer from './entityAnnotationContainer';
2 | import { SortOrder } from "../typeproperty/Indexed";
3 |
4 | export default function CompositeIndex(
5 | indexFields: string[],
6 | sortOrder: SortOrder = 'ASC',
7 | usingOption?: string,
8 | additionalSqlCreateIndexStatementOptions?: string
9 | ) {
10 | return function(entityClass: Function) {
11 | entityContainer.addEntityIndex(entityClass.name + ':' + indexFields.join('_'), indexFields);
12 | entityContainer.addIndexSortOrder(entityClass.name + ':' + indexFields.join('_'), sortOrder);
13 | entityContainer.addUsingOptionForIndex(entityClass.name + ':' + indexFields.join('_'), usingOption);
14 | entityContainer.addAdditionalSqlCreateIndexStatementOptionsForIndex(
15 | entityClass.name + ':' + indexFields.join('_'),
16 | additionalSqlCreateIndexStatementOptions
17 | );
18 | };
19 | }
20 |
--------------------------------------------------------------------------------
/src/decorators/entity/Entity.ts:
--------------------------------------------------------------------------------
1 | import entityContainer from "./entityAnnotationContainer";
2 |
3 | export default function Entity(
4 | referenceToEntity?: string,
5 | additionalSqlCreateTableStatementOptions?: string
6 | ) {
7 | return function(EntityClass: Function) {
8 | if (
9 | !EntityClass.name.startsWith('__Backk') &&
10 | !EntityClass.name.match(/^[A-Z][a-zA-Z0-9]*$/)
11 | ) {
12 | throw new Error(
13 | EntityClass.name + ': entity class name must match regular expression: /^[A-Z][a-zA-Z0-9]*$/'
14 | );
15 | }
16 |
17 | entityContainer.addEntityNameAndClass(EntityClass.name, EntityClass);
18 |
19 | if (referenceToEntity) {
20 | entityContainer.addEntityTableName(EntityClass.name, referenceToEntity);
21 | }
22 |
23 | if (additionalSqlCreateTableStatementOptions) {
24 | entityContainer.addAdditionalSqlCreateTableStatementOptionsForEntity(
25 | EntityClass.name,
26 | additionalSqlCreateTableStatementOptions
27 | );
28 | }
29 | };
30 | }
31 |
--------------------------------------------------------------------------------
/src/decorators/entity/UniqueCompositeIndex.ts:
--------------------------------------------------------------------------------
1 | import entityContainer from './entityAnnotationContainer';
2 | import { SortOrder } from "../typeproperty/Indexed";
3 |
4 | export default function UniqueCompositeIndex(
5 | indexFields: string[],
6 | sortOrder: SortOrder = 'ASC',
7 | usingOption?: string,
8 | additionalSqlCreateIndexStatementOptions?: string
9 | ) {
10 | return function(entityClass: Function) {
11 | entityContainer.addEntityUniqueIndex(entityClass.name + ':' + indexFields.join('_'), indexFields);
12 | entityContainer.addIndexSortOrder(entityClass.name + ':' + indexFields.join('_'), sortOrder);
13 | entityContainer.addUsingOptionForIndex(entityClass.name + ':' + indexFields.join('_'), usingOption);
14 | entityContainer.addAdditionalSqlCreateIndexStatementOptionsForIndex(
15 | entityClass.name + ':' + indexFields.join('_'),
16 | additionalSqlCreateIndexStatementOptions
17 | );
18 | };
19 | }
20 |
--------------------------------------------------------------------------------
/src/decorators/registerCustomDecorator.ts:
--------------------------------------------------------------------------------
1 | import { ValidationDecoratorOptions, registerDecorator } from 'class-validator';
2 |
3 | export const customDecoratorNameToTestValueMap: { [key: string]: any } = {};
4 |
5 | export default function registerCustomDecorator(options: ValidationDecoratorOptions, testValue: any) {
6 | if (!options.name) {
7 | throw new Error('Name must be specified in ValidationDecoratorOptions');
8 | }
9 |
10 | if (options.name !== options.constraints?.[0]) {
11 | throw new Error("property 'value' and 'constraints[0]' must be same in ValidationDecoratorOptions");
12 | }
13 |
14 | customDecoratorNameToTestValueMap[options.name] = testValue;
15 | registerDecorator(options);
16 | }
17 |
--------------------------------------------------------------------------------
/src/decorators/service/AllowServiceForEveryUser.ts:
--------------------------------------------------------------------------------
1 | import serviceAnnotationContainer from "./serviceAnnotationContainer";
2 |
3 | export default function AllowServiceForEveryUser(isAuthenticationRequired: boolean) {
4 | return function(serviceClass: Function) {
5 | serviceAnnotationContainer.addServiceAllowedForEveryUser(serviceClass, isAuthenticationRequired);
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/src/decorators/service/AllowServiceForKubeClusterInternalUse.ts:
--------------------------------------------------------------------------------
1 | import serviceAnnotationContainer from "./serviceAnnotationContainer";
2 |
3 | export default function AllowServiceForKubeClusterInternalUse() {
4 | return function(serviceClass: Function) {
5 | serviceAnnotationContainer.addServiceAllowedForClusterInternalUse(serviceClass);
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/src/decorators/service/AllowServiceForUserRoles.ts:
--------------------------------------------------------------------------------
1 | import serviceAnnotationContainer from "./serviceAnnotationContainer";
2 |
3 | export default function AllowServiceForUserRoles(roles: string[]) {
4 | return function(serviceClass: Function) {
5 | serviceAnnotationContainer.addAllowedUserRolesForService(serviceClass, roles);
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/src/decorators/service/NoServiceAutoTests.ts:
--------------------------------------------------------------------------------
1 | import serviceAnnotationContainer from "./serviceAnnotationContainer";
2 |
3 | export default function NoServiceAutoTests() {
4 | return function(serviceClass: Function) {
5 | serviceAnnotationContainer.addNoAutoTestsAnnotationToServiceClass(serviceClass);
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/src/decorators/service/function/AllowForEveryUser.ts:
--------------------------------------------------------------------------------
1 | import serviceFunctionAnnotationContainer from './serviceFunctionAnnotationContainer';
2 |
3 | export default function AllowForEveryUser(isAuthenticationRequired: boolean, allowDespiteOfUserIdInArg = false) {
4 | // eslint-disable-next-line @typescript-eslint/ban-types
5 | return function(object: Object, functionName: string) {
6 | serviceFunctionAnnotationContainer.addServiceFunctionAllowedForEveryUser(
7 | object.constructor,
8 | functionName,
9 | allowDespiteOfUserIdInArg
10 | );
11 |
12 | serviceFunctionAnnotationContainer.addServiceFunctionAllowedForEveryUserWithAuthentication(
13 | object.constructor,
14 | functionName,
15 | isAuthenticationRequired
16 | );
17 | };
18 | }
19 |
--------------------------------------------------------------------------------
/src/decorators/service/function/AllowForEveryUserForOwnResources.ts:
--------------------------------------------------------------------------------
1 | import serviceFunctionAnnotationContainer from './serviceFunctionAnnotationContainer';
2 |
3 | export default function AllowForEveryUserForOwnResources(userAccountIdFieldName: string) {
4 | // eslint-disable-next-line @typescript-eslint/ban-types
5 | return function(object: Object, functionName: string) {
6 | serviceFunctionAnnotationContainer.addServiceFunctionAllowedForEveryUserForOwnResources(
7 | object.constructor,
8 | functionName,
9 | userAccountIdFieldName
10 | );
11 | };
12 | }
13 |
--------------------------------------------------------------------------------
/src/decorators/service/function/AllowForKubeClusterInternalUse.ts:
--------------------------------------------------------------------------------
1 | import serviceFunctionAnnotationContainer from './serviceFunctionAnnotationContainer';
2 |
3 | export default function AllowForKubeClusterInternalUse() {
4 | // eslint-disable-next-line @typescript-eslint/ban-types
5 | return function(object: Object, functionName: string) {
6 | serviceFunctionAnnotationContainer.addServiceFunctionAllowedForClusterInternalUse(
7 | object.constructor,
8 | functionName
9 | );
10 | };
11 | }
12 |
--------------------------------------------------------------------------------
/src/decorators/service/function/AllowForMicroserviceInternalUse.ts:
--------------------------------------------------------------------------------
1 | import serviceFunctionAnnotationContainer from './serviceFunctionAnnotationContainer';
2 |
3 | export default function AllowForMicroserviceInternalUse() {
4 | // eslint-disable-next-line @typescript-eslint/ban-types
5 | return function(object: Object, functionName: string) {
6 | serviceFunctionAnnotationContainer.addServiceFunctionAllowedForServiceInternalUse(object.constructor, functionName);
7 | };
8 | }
9 |
--------------------------------------------------------------------------------
/src/decorators/service/function/AllowForTests.ts:
--------------------------------------------------------------------------------
1 | import serviceFunctionAnnotationContainer from './serviceFunctionAnnotationContainer';
2 |
3 | export default function AllowForTests() {
4 | // eslint-disable-next-line @typescript-eslint/ban-types
5 | return function(object: Object, functionName: string) {
6 | serviceFunctionAnnotationContainer.addServiceFunctionAllowedForTests(object.constructor, functionName);
7 | };
8 | }
9 |
--------------------------------------------------------------------------------
/src/decorators/service/function/AllowForUserRoles.ts:
--------------------------------------------------------------------------------
1 | import serviceFunctionAnnotationContainer from './serviceFunctionAnnotationContainer';
2 |
3 | export default function AllowForUserRoles(roles: string[]) {
4 | // eslint-disable-next-line @typescript-eslint/ban-types
5 | return function(object: Object, functionName: string) {
6 | serviceFunctionAnnotationContainer.addAllowedUserRoles(object.constructor, functionName, roles);
7 | };
8 | }
9 |
--------------------------------------------------------------------------------
/src/decorators/service/function/AllowHttpGetMethod.ts:
--------------------------------------------------------------------------------
1 | import serviceFunctionAnnotationContainer from './serviceFunctionAnnotationContainer';
2 |
3 | export default function AllowHttpGetMethod() {
4 | // eslint-disable-next-line @typescript-eslint/ban-types
5 | return function(object: Object, functionName: string) {
6 | serviceFunctionAnnotationContainer.addServiceFunctionAllowHttpGetMethod(object.constructor, functionName);
7 | };
8 | }
9 |
--------------------------------------------------------------------------------
/src/decorators/service/function/AuditLog.ts:
--------------------------------------------------------------------------------
1 | import serviceFunctionAnnotationContainer from './serviceFunctionAnnotationContainer';
2 |
3 | export default function AuditLog(
4 | shouldLog: (argument: T, returnValue: U) => boolean,
5 | attributesToLog: (argument: T, returnValue: U) => { [key: string]: any }
6 | ) {
7 | // eslint-disable-next-line @typescript-eslint/ban-types
8 | return function(object: Object, functionName: string) {
9 | serviceFunctionAnnotationContainer.addServiceFunctionAuditLog(
10 | object.constructor,
11 | functionName,
12 | shouldLog,
13 | attributesToLog
14 | );
15 | };
16 | }
17 |
--------------------------------------------------------------------------------
/src/decorators/service/function/Create.ts:
--------------------------------------------------------------------------------
1 | import serviceFunctionAnnotationContainer from './serviceFunctionAnnotationContainer';
2 |
3 | export default function Create() {
4 | // eslint-disable-next-line @typescript-eslint/ban-types
5 | return function(object: Object, functionName: string) {
6 | serviceFunctionAnnotationContainer.addCreateAnnotation(object.constructor, functionName);
7 | };
8 | }
9 |
--------------------------------------------------------------------------------
/src/decorators/service/function/Delete.ts:
--------------------------------------------------------------------------------
1 | import serviceFunctionAnnotationContainer from './serviceFunctionAnnotationContainer';
2 |
3 | export default function Delete() {
4 | // eslint-disable-next-line @typescript-eslint/ban-types
5 | return function(object: Object, functionName: string) {
6 | serviceFunctionAnnotationContainer.addDeleteAnnotation(object.constructor, functionName);
7 | };
8 | }
9 |
--------------------------------------------------------------------------------
/src/decorators/service/function/ExecuteOnStartUp.ts:
--------------------------------------------------------------------------------
1 | import serviceFunctionAnnotationContainer from './serviceFunctionAnnotationContainer';
2 |
3 | export default function ExecuteOnStartUp() {
4 | // eslint-disable-next-line @typescript-eslint/ban-types
5 | return function(object: Object, functionName: string) {
6 | serviceFunctionAnnotationContainer.addOnStartUpAnnotation(object.constructor, functionName);
7 | };
8 | }
9 |
--------------------------------------------------------------------------------
/src/decorators/service/function/NoAutoTest.ts:
--------------------------------------------------------------------------------
1 | import serviceFunctionAnnotationContainer from './serviceFunctionAnnotationContainer';
2 |
3 | export default function NoAutoTest() {
4 | // eslint-disable-next-line @typescript-eslint/ban-types
5 | return function(object: Object, functionName: string) {
6 | serviceFunctionAnnotationContainer.addNoAutoTestAnnotation(object.constructor, functionName);
7 | };
8 | }
9 |
--------------------------------------------------------------------------------
/src/decorators/service/function/NoCaptcha.ts:
--------------------------------------------------------------------------------
1 | import serviceFunctionAnnotationContainer from './serviceFunctionAnnotationContainer';
2 |
3 | // eslint-disable-next-line @typescript-eslint/no-unused-vars
4 | export default function NoCaptcha(reason: string) {
5 | // eslint-disable-next-line @typescript-eslint/ban-types
6 | return function(object: Object, functionName: string) {
7 | serviceFunctionAnnotationContainer.addNoCaptchaAnnotation(object.constructor, functionName);
8 | };
9 | }
10 |
--------------------------------------------------------------------------------
/src/decorators/service/function/NoDistributedTransactionNeeded.ts:
--------------------------------------------------------------------------------
1 | import serviceFunctionAnnotationContainer from "./serviceFunctionAnnotationContainer";
2 |
3 | export default function NoDistributedTransactionNeeded(reason: string) {
4 | if (!reason) {
5 | throw new Error('reason must be provided for @NoDistributedTransactionNeeded annotation');
6 | }
7 | // eslint-disable-next-line @typescript-eslint/ban-types
8 | return function(object: Object, functionName: string) {
9 | serviceFunctionAnnotationContainer.addNonDistributedTransactionalServiceFunction(object.constructor, functionName);
10 | };
11 | }
12 |
--------------------------------------------------------------------------------
/src/decorators/service/function/NoTransactionNeeded.ts:
--------------------------------------------------------------------------------
1 | import serviceFunctionAnnotationContainer from "./serviceFunctionAnnotationContainer";
2 |
3 | export default function NoTransactionNeeded(reason: string) {
4 | // eslint-disable-next-line @typescript-eslint/ban-types
5 | return function(object: Object, functionName: string) {
6 | if (!reason) {
7 | throw new Error('reason must be provided for @NoTransaction annotation');
8 | }
9 | serviceFunctionAnnotationContainer.addNonTransactionalServiceFunction(object.constructor, functionName);
10 | };
11 | }
12 |
--------------------------------------------------------------------------------
/src/decorators/service/function/ResponseHeaders.ts:
--------------------------------------------------------------------------------
1 | import serviceFunctionAnnotationContainer from './serviceFunctionAnnotationContainer';
2 |
3 | export type HeaderValueGenerator = (
4 | argument: T,
5 | returnValue: U
6 | ) => string | undefined;
7 |
8 | export type HttpHeaders = {
9 | [key: string]: string | HeaderValueGenerator | undefined;
10 | };
11 |
12 | export default function ResponseHeaders(headers: HttpHeaders) {
13 | // eslint-disable-next-line @typescript-eslint/ban-types
14 | return function(object: Object, functionName: string) {
15 | serviceFunctionAnnotationContainer.addResponseHeadersForServiceFunction(
16 | object.constructor,
17 | functionName,
18 | headers
19 | );
20 | };
21 | }
22 |
--------------------------------------------------------------------------------
/src/decorators/service/function/ResponseStatusCode.ts:
--------------------------------------------------------------------------------
1 | import serviceFunctionAnnotationContainer from './serviceFunctionAnnotationContainer';
2 |
3 | export default function ResponseStatusCode(statusCode: number) {
4 | // eslint-disable-next-line @typescript-eslint/ban-types
5 | return function(object: Object, functionName: string) {
6 | serviceFunctionAnnotationContainer.addResponseStatusCodeForServiceFunction(
7 | object.constructor,
8 | functionName,
9 | statusCode
10 | );
11 | };
12 | }
13 |
--------------------------------------------------------------------------------
/src/decorators/service/function/Subscription.ts:
--------------------------------------------------------------------------------
1 | import serviceFunctionAnnotationContainer from './serviceFunctionAnnotationContainer';
2 |
3 | export default function Subscription() {
4 | // eslint-disable-next-line @typescript-eslint/ban-types
5 | return function(object: Object, functionName: string) {
6 | serviceFunctionAnnotationContainer.addSubscription(
7 | object.constructor,
8 | functionName
9 | );
10 | };
11 | }
12 |
--------------------------------------------------------------------------------
/src/decorators/service/function/Test.ts:
--------------------------------------------------------------------------------
1 | import serviceFunctionAnnotationContainer from './serviceFunctionAnnotationContainer';
2 | import { FieldPathNameToFieldValueMap } from './PostTests';
3 |
4 | export default function Test(expectedResult: FieldPathNameToFieldValueMap) {
5 | const finalFieldPathNameToFieldValueMap = Object.entries(expectedResult).reduce(
6 | (finalFieldPathNameToFieldValueMap, [fieldPathName, fieldValue]) => {
7 | let finalFieldValue = fieldValue;
8 |
9 | if (typeof fieldValue === 'string' && fieldValue.startsWith('{{') && fieldValue.endsWith('}}')) {
10 | const idFieldName = fieldValue.slice(2, -2).trim();
11 | finalFieldValue = `pm.collectionVariables.get('${idFieldName}')`;
12 | }
13 |
14 | return {
15 | ...finalFieldPathNameToFieldValueMap,
16 | [fieldPathName]: finalFieldValue
17 | };
18 | },
19 | {}
20 | );
21 |
22 | // eslint-disable-next-line @typescript-eslint/ban-types
23 | return function(object: Object, functionName: string) {
24 | serviceFunctionAnnotationContainer.expectServiceFunctionReturnValueToContainInTests(
25 | object.constructor,
26 | functionName,
27 | finalFieldPathNameToFieldValueMap
28 | );
29 | };
30 | }
31 |
--------------------------------------------------------------------------------
/src/decorators/service/function/TestSetup.ts:
--------------------------------------------------------------------------------
1 | import serviceFunctionAnnotationContainer from './serviceFunctionAnnotationContainer';
2 |
3 | export type TestSetupSpec = {
4 | setupStepName: string;
5 | serviceFunctionName: string;
6 | argument?: {
7 | [key: string]: any;
8 | };
9 | postmanTests?: string[];
10 | };
11 |
12 | export default function TestSetup(serviceFunctionsOrSpecsToExecute: (string | TestSetupSpec)[]) {
13 | // eslint-disable-next-line @typescript-eslint/ban-types
14 | return function(object: Object, functionName: string) {
15 | serviceFunctionAnnotationContainer.addTestSetup(
16 | object.constructor,
17 | functionName,
18 | serviceFunctionsOrSpecsToExecute
19 | );
20 | };
21 | }
22 |
--------------------------------------------------------------------------------
/src/decorators/service/function/Update.ts:
--------------------------------------------------------------------------------
1 | import serviceFunctionAnnotationContainer from './serviceFunctionAnnotationContainer';
2 |
3 | export type UpdateType = 'update' | 'addOrRemove';
4 |
5 | export default function Update(updateType: UpdateType) {
6 | // eslint-disable-next-line @typescript-eslint/ban-types
7 | return function(object: Object, functionName: string) {
8 | serviceFunctionAnnotationContainer.addUpdateAnnotation(object.constructor, functionName, updateType);
9 | };
10 | }
11 |
--------------------------------------------------------------------------------
/src/decorators/typeproperty/ArrayNotUnique.ts:
--------------------------------------------------------------------------------
1 | import { ValidationOptions, registerDecorator } from "class-validator";
2 |
3 | export default function ArrayNotUnique(validationOptions?: ValidationOptions) {
4 | return function(object: Record, propertyName: string) {
5 | registerDecorator({
6 | name: 'arrayNotUnique',
7 | target: object.constructor,
8 | propertyName: propertyName,
9 | constraints: ['arrayNotUnique'],
10 | options: validationOptions,
11 | validator: {
12 | validate() {
13 | return true;
14 | }
15 | }
16 | });
17 | };
18 | }
19 |
--------------------------------------------------------------------------------
/src/decorators/typeproperty/BooleanOrTinyInt.ts:
--------------------------------------------------------------------------------
1 | import { ValidationOptions, registerDecorator } from "class-validator";
2 |
3 | export default function BooleanOrTinyInt(validationOptions?: ValidationOptions) {
4 | return function (object: Record, propertyName: string) {
5 | registerDecorator({
6 | name: 'isBooleanOrTinyInt',
7 | target: object.constructor,
8 | propertyName: propertyName,
9 | constraints: ['isBooleanOrTinyInt'],
10 | options: validationOptions,
11 | validator: {
12 | validate(value: any) {
13 | return typeof value === 'boolean' || value === 0 || value === 1
14 | },
15 | defaultMessage: () => propertyName + ' must be a boolean value'
16 | },
17 | });
18 | };
19 | }
20 |
--------------------------------------------------------------------------------
/src/decorators/typeproperty/Encrypted.ts:
--------------------------------------------------------------------------------
1 | import typeAnnotationContainer from './typePropertyAnnotationContainer';
2 |
3 | export default function Encrypted() {
4 | // eslint-disable-next-line @typescript-eslint/ban-types
5 | return function(object: Object, propertyName: string) {
6 | typeAnnotationContainer.setTypePropertyAsEncrypted(object.constructor, propertyName);
7 | };
8 | }
9 |
--------------------------------------------------------------------------------
/src/decorators/typeproperty/FetchFromRemoteService.ts:
--------------------------------------------------------------------------------
1 | import typeAnnotationContainer from './typePropertyAnnotationContainer';
2 | import { HttpRequestOptions } from '../../remote/http/callRemoteService';
3 |
4 | export default function FetchFromRemoteService(
5 | remoteMicroserviceName: string,
6 | remoteServiceFunctionName: string,
7 | buildRemoteServiceFunctionArgument: (arg: T, response: U) => { [key: string]: any },
8 | remoteMicroserviceNamespace = process.env.MICROSERVICE_NAMESPACE,
9 | options?: HttpRequestOptions
10 | ) {
11 | // eslint-disable-next-line @typescript-eslint/ban-types
12 | return function(object: Object, propertyName: string) {
13 | typeAnnotationContainer.setTypePropertyAsFetchedFromRemoteService(
14 | object.constructor,
15 | propertyName,
16 | remoteMicroserviceName,
17 | remoteMicroserviceNamespace,
18 | remoteServiceFunctionName,
19 | buildRemoteServiceFunctionArgument,
20 | options
21 | );
22 | };
23 | }
24 |
--------------------------------------------------------------------------------
/src/decorators/typeproperty/Hashed.ts:
--------------------------------------------------------------------------------
1 | import typeAnnotationContainer from './typePropertyAnnotationContainer';
2 |
3 | export default function Hashed() {
4 | // eslint-disable-next-line @typescript-eslint/ban-types
5 | return function(object: Object, propertyName: string) {
6 | typeAnnotationContainer.setTypePropertyAsHashed(object.constructor, propertyName);
7 | };
8 | }
9 |
--------------------------------------------------------------------------------
/src/decorators/typeproperty/Indexed.ts:
--------------------------------------------------------------------------------
1 | import entityAnnotationContainer from '../entity/entityAnnotationContainer';
2 |
3 | export type SortOrder = 'ASC' | 'DESC'
4 |
5 | export default function Indexed(
6 | sortOrder: SortOrder = 'ASC',
7 | usingOption?: string,
8 | additionalSqlCreateIndexStatementOptions?: string
9 | ) {
10 | // eslint-disable-next-line @typescript-eslint/ban-types
11 | return function(object: Object, propertyName: string) {
12 | entityAnnotationContainer.addEntityIndex(object.constructor.name + ':' + propertyName, [propertyName]);
13 | entityAnnotationContainer.addIndexSortOrder(object.constructor.name + ':' + propertyName, sortOrder);
14 | entityAnnotationContainer.addUsingOptionForIndex(
15 | object.constructor.name + ':' + propertyName,
16 | usingOption
17 | );
18 | entityAnnotationContainer.addAdditionalSqlCreateIndexStatementOptionsForIndex(
19 | object.constructor.name + ':' + propertyName,
20 | additionalSqlCreateIndexStatementOptions
21 | );
22 | };
23 | }
24 |
--------------------------------------------------------------------------------
/src/decorators/typeproperty/IsAnyString.ts:
--------------------------------------------------------------------------------
1 | import { ValidationArguments, ValidationOptions, registerDecorator } from 'class-validator';
2 |
3 | export default function IsAnyString(validationOptions?: ValidationOptions) {
4 | return function (object: Record, propertyName: string) {
5 | registerDecorator({
6 | name: 'isAnyString',
7 | target: object.constructor,
8 | propertyName: propertyName,
9 | constraints: ['isAnyString'],
10 | options: validationOptions,
11 | validator: {
12 | validate() {
13 | return true;
14 | }
15 | },
16 | });
17 | };
18 | }
19 |
--------------------------------------------------------------------------------
/src/decorators/typeproperty/IsBigInt.ts:
--------------------------------------------------------------------------------
1 | import { ValidationArguments, ValidationOptions, registerDecorator } from 'class-validator';
2 |
3 | export default function IsBigInt(validationOptions?: ValidationOptions) {
4 | return function (object: Record, propertyName: string) {
5 | registerDecorator({
6 | name: 'isBigInt',
7 | target: object.constructor,
8 | propertyName: propertyName,
9 | constraints: ['isBigInt'],
10 | options: validationOptions,
11 | validator: {
12 | validate(value: any, args: ValidationArguments) {
13 | return typeof value === 'number' && Number.isInteger(value);
14 | },
15 | defaultMessage: () => propertyName + ' must be an integer number'
16 | },
17 | });
18 | };
19 | }
20 |
--------------------------------------------------------------------------------
/src/decorators/typeproperty/IsCreditCardVerificationCode.ts:
--------------------------------------------------------------------------------
1 | import { ValidationOptions, registerDecorator } from 'class-validator';
2 |
3 | export default function IsCreditCardVerificationCode(validationOptions?: ValidationOptions) {
4 | return function(object: Record, propertyName: string) {
5 | registerDecorator({
6 | name: 'isCardVerificationCode',
7 | target: object.constructor,
8 | propertyName: propertyName,
9 | constraints: ['isCardVerificationCode'],
10 | options: validationOptions,
11 | validator: {
12 | validate(value: string) {
13 | if (typeof value !== 'string') {
14 | return false;
15 | }
16 |
17 | return !!value.match(/^[0-9]{3,4}$/);
18 | },
19 | defaultMessage: () =>
20 | propertyName + ' is not a valid credit card verification code'
21 | }
22 | });
23 | };
24 | }
25 |
--------------------------------------------------------------------------------
/src/decorators/typeproperty/IsDataUri.ts:
--------------------------------------------------------------------------------
1 | import { ValidationOptions, registerDecorator } from 'class-validator';
2 | import isDataUri from 'validator/lib/isDataURI';
3 |
4 | export default function IsDataUri(validationOptions?: ValidationOptions) {
5 | return function(object: Record, propertyName: string) {
6 | registerDecorator({
7 | name: 'isDataUri',
8 | target: object.constructor,
9 | propertyName: propertyName,
10 | constraints: ['isDataUri'],
11 | options: validationOptions,
12 | validator: {
13 | validate(value: any) {
14 | if (typeof value !== 'string') {
15 | return false;
16 | }
17 |
18 | return isDataUri(value);
19 | },
20 | defaultMessage: () =>
21 | propertyName + ' is not a valid data URI'
22 | }
23 | });
24 | };
25 | }
26 |
--------------------------------------------------------------------------------
/src/decorators/typeproperty/IsExternalId.ts:
--------------------------------------------------------------------------------
1 | import typeAnnotationContainer from './typePropertyAnnotationContainer';
2 |
3 | export default function IsExternalId() {
4 | // eslint-disable-next-line @typescript-eslint/ban-types
5 | return function(object: Object, propertyName: string) {
6 | typeAnnotationContainer.setTypePropertyAsExternalId(object.constructor, propertyName);
7 | };
8 | }
9 |
--------------------------------------------------------------------------------
/src/decorators/typeproperty/IsFloat.ts:
--------------------------------------------------------------------------------
1 | import { ValidationMetadataArgs } from 'class-validator/metadata/ValidationMetadataArgs';
2 | import { MetadataStorage, ValidationTypes, getFromContainer } from 'class-validator';
3 | import { ValidationMetadata } from 'class-validator/metadata/ValidationMetadata';
4 |
5 | export default function IsFloat(maxDecimalPlaces: number, options?: { each: boolean }) {
6 | // eslint-disable-next-line @typescript-eslint/ban-types
7 | return function(object: Object, propertyName: string) {
8 | const validationMetadataArgs: ValidationMetadataArgs = {
9 | type: ValidationTypes.IS_NUMBER,
10 | target: object.constructor,
11 | propertyName,
12 | constraints: [{ maxDecimalPlaces }],
13 | validationOptions: { each: options?.each }
14 | };
15 |
16 | const validationMetadata = new ValidationMetadata(validationMetadataArgs);
17 | getFromContainer(MetadataStorage).addValidationMetadata(validationMetadata);
18 | };
19 | }
20 |
--------------------------------------------------------------------------------
/src/decorators/typeproperty/IsNoneOf.ts:
--------------------------------------------------------------------------------
1 | import { ValidationOptions, registerDecorator } from "class-validator";
2 | import Value from "../../types/Value";
3 | import { PromiseErrorOr } from "../../types/PromiseErrorOr";
4 | import { Many } from "../../datastore/DataStore";
5 |
6 | export default function IsNoneOf(
7 | getPossibleValuesFunc: () => PromiseErrorOr>,
8 | serviceFunctionName: string,
9 | testValue: string,
10 | validationOptions?: ValidationOptions
11 | ) {
12 | return function(object: Record, propertyName: string) {
13 | registerDecorator({
14 | name: 'isNoneOf',
15 | target: object.constructor,
16 | propertyName: propertyName,
17 | constraints: ['isNoneOf', serviceFunctionName, testValue],
18 | options: validationOptions,
19 | validator: {
20 | async validate(value: any) {
21 | const [possibleValues] = await getPossibleValuesFunc();
22 | return possibleValues ? !possibleValues.data.some(({ value }) => value === value) : false;
23 | },
24 | defaultMessage: () =>
25 | propertyName + ' may not be anyone from the result of service function call: ' + serviceFunctionName
26 | }
27 | });
28 | };
29 | }
30 |
--------------------------------------------------------------------------------
/src/decorators/typeproperty/IsOneOf.ts:
--------------------------------------------------------------------------------
1 | import { ValidationOptions, registerDecorator } from 'class-validator';
2 | import Value from '../../types/Value';
3 | import { PromiseErrorOr } from '../../types/PromiseErrorOr';
4 | import { Many } from "../../datastore/DataStore";
5 |
6 | export default function IsOneOf(
7 | getPossibleValuesFunc: () => PromiseErrorOr>,
8 | serviceFunctionName: string,
9 | testValue: string,
10 | validationOptions?: ValidationOptions
11 | ) {
12 | return function(object: Record, propertyName: string) {
13 | registerDecorator({
14 | name: 'isOneOf',
15 | target: object.constructor,
16 | propertyName: propertyName,
17 | constraints: ['isOneOf', serviceFunctionName, testValue],
18 | options: validationOptions,
19 | validator: {
20 | async validate(value: any) {
21 | const [possibleValues] = await getPossibleValuesFunc();
22 |
23 | return possibleValues
24 | ? possibleValues.data.some((possibleValue) => value === possibleValue.value)
25 | : false;
26 | },
27 | defaultMessage: () =>
28 | propertyName + ' must be one from the result of service function call: ' + serviceFunctionName
29 | }
30 | });
31 | };
32 | }
33 |
--------------------------------------------------------------------------------
/src/decorators/typeproperty/IsPostalCode.ts:
--------------------------------------------------------------------------------
1 | import { ValidationArguments, ValidationOptions, registerDecorator } from 'class-validator';
2 | import isPostalCode from 'validator/lib/isPostalCode';
3 |
4 | export default function IsPostalCode(
5 | locale: 'any' | ValidatorJS.PostalCodeLocale,
6 | validationOptions?: ValidationOptions
7 | ) {
8 | return function(object: Record, propertyName: string) {
9 | registerDecorator({
10 | name: 'isPostalCode',
11 | target: object.constructor,
12 | propertyName: propertyName,
13 | constraints: ['isPostalCode', locale],
14 | options: validationOptions,
15 | validator: {
16 | validate(value: any, args: ValidationArguments) {
17 | if (typeof value !== 'string') {
18 | return false;
19 | }
20 |
21 | return isPostalCode(value, args.constraints[1]);
22 | },
23 | defaultMessage: () =>
24 | propertyName + ' is not a valid postal code for locale: ' + locale
25 | }
26 | });
27 | };
28 | }
29 |
--------------------------------------------------------------------------------
/src/decorators/typeproperty/IsStringOrObjectId.ts:
--------------------------------------------------------------------------------
1 | import { ValidationArguments, ValidationOptions, registerDecorator } from 'class-validator';
2 |
3 | export default function IsStringOrObjectId(validationOptions?: ValidationOptions) {
4 | return function (object: Record, propertyName: string) {
5 | registerDecorator({
6 | name: 'isStringOrObjectId',
7 | target: object.constructor,
8 | propertyName: propertyName,
9 | constraints: ['isStringOrObjectId'],
10 | options: validationOptions,
11 | validator: {
12 | validate(value: any) {
13 | return typeof value === 'string' || value?.constructor?.name === 'ObjectID';
14 | },
15 | defaultMessage: () => propertyName + ' must be a string or MongoDB ObjectId'
16 | },
17 | });
18 | };
19 | }
20 |
--------------------------------------------------------------------------------
/src/decorators/typeproperty/IsSubject.ts:
--------------------------------------------------------------------------------
1 | import { ValidationOptions, registerDecorator } from "class-validator";
2 | import isAscii from "validator/lib/isAscii";
3 | import { Lengths } from "../../constants/constants";
4 |
5 | export default function IsSubject(validationOptions?: ValidationOptions) {
6 | return function (object: Record, propertyName: string) {
7 | registerDecorator({
8 | name: 'isSubject',
9 | target: object.constructor,
10 | propertyName: propertyName,
11 | constraints: ['isSubject'],
12 | options: validationOptions,
13 | validator: {
14 | validate(value: any) {
15 | if (typeof value !== 'string') {
16 | return false;
17 | }
18 |
19 | return value.length < Lengths._256 && isAscii(value);
20 | },
21 | defaultMessage: () => propertyName + ' must be an ASCII string maximum 256 characters long'
22 | },
23 | });
24 | };
25 | }
26 |
--------------------------------------------------------------------------------
/src/decorators/typeproperty/IsUndefined.ts:
--------------------------------------------------------------------------------
1 | import { ValidationOptions, registerDecorator } from "class-validator";
2 |
3 | export default function IsUndefined(validationOptions?: ValidationOptions) {
4 | return function (object: Record, propertyName: string) {
5 | registerDecorator({
6 | name: 'isUndefined',
7 | target: object.constructor,
8 | propertyName: propertyName,
9 | constraints: ['isUndefined'],
10 | options: validationOptions,
11 | validator: {
12 | validate(value: any) {
13 | return value === undefined;
14 | },
15 | defaultMessage: () => propertyName + ' is not allowed'
16 | },
17 | });
18 | };
19 | }
20 |
--------------------------------------------------------------------------------
/src/decorators/typeproperty/LengthAndMatches.ts:
--------------------------------------------------------------------------------
1 | import { ValidationArguments, ValidationOptions, registerDecorator } from 'class-validator';
2 | import RE2 from 're2';
3 |
4 | export default function LengthAndMatches(
5 | minLength: number,
6 | maxLength: number,
7 | regexp: RegExp,
8 | validationOptions?: ValidationOptions
9 | ) {
10 | return function(object: Record, propertyName: string) {
11 | registerDecorator({
12 | name: 'lengthAndMatches',
13 | target: object.constructor,
14 | propertyName: propertyName,
15 | constraints: ['lengthAndMatches', minLength, maxLength, regexp],
16 | options: validationOptions,
17 | validator: {
18 | validate(value: any) {
19 | if (value === null || value === undefined) {
20 | return false;
21 | }
22 | if (value.length > maxLength || value.length < minLength) {
23 | return false;
24 | }
25 | const re2RegExp = new RE2(regexp);
26 | return re2RegExp.test(value);
27 | },
28 | defaultMessage: () =>
29 | propertyName + ' length must be between' + minLength + '-' + maxLength + ' and must match ' + regexp
30 | }
31 | });
32 | };
33 | }
34 |
--------------------------------------------------------------------------------
/src/decorators/typeproperty/ManyToMany.ts:
--------------------------------------------------------------------------------
1 | import typeAnnotationContainer from "./typePropertyAnnotationContainer";
2 |
3 | export default function ManyToMany() {
4 | // eslint-disable-next-line @typescript-eslint/ban-types
5 | return function(object: Object, propertyName: string) {
6 | typeAnnotationContainer.setTypePropertyAsManyToMany(object.constructor, propertyName);
7 | };
8 | }
9 |
--------------------------------------------------------------------------------
/src/decorators/typeproperty/MinMax.ts:
--------------------------------------------------------------------------------
1 | import { ValidationArguments, ValidationOptions, registerDecorator } from 'class-validator';
2 |
3 | export default function MinMax(
4 | minValue: number,
5 | maxValue: number,
6 | validationOptions?: ValidationOptions
7 | ) {
8 | return function(object: Record, propertyName: string) {
9 | registerDecorator({
10 | name: 'minMax',
11 | target: object.constructor,
12 | propertyName: propertyName,
13 | constraints: ['minMax', minValue, maxValue],
14 | options: validationOptions,
15 | validator: {
16 | validate(value: any) {
17 | if (value > maxValue || value < minValue) {
18 | return false;
19 | }
20 | return true;
21 | },
22 | defaultMessage: () =>
23 | propertyName + ' value must be between ' + minValue + ' - ' + maxValue
24 | }
25 | });
26 | };
27 | }
28 |
--------------------------------------------------------------------------------
/src/decorators/typeproperty/NotEncrypted.ts:
--------------------------------------------------------------------------------
1 | import typeAnnotationContainer from './typePropertyAnnotationContainer';
2 |
3 | export default function NotEncrypted(reason: string) {
4 | // eslint-disable-next-line @typescript-eslint/ban-types
5 | return function(object: Object, propertyName: string) {
6 | typeAnnotationContainer.setTypePropertyAsNotEncrypted(object.constructor, propertyName);
7 | };
8 | }
9 |
--------------------------------------------------------------------------------
/src/decorators/typeproperty/NotHashed.ts:
--------------------------------------------------------------------------------
1 | import typeAnnotationContainer from './typePropertyAnnotationContainer';
2 |
3 | export default function NotHashed(reason: string) {
4 | // eslint-disable-next-line @typescript-eslint/ban-types
5 | return function(object: Object, propertyName: string) {
6 | typeAnnotationContainer.setTypePropertyAsNotHashed(object.constructor, propertyName);
7 | };
8 | }
9 |
--------------------------------------------------------------------------------
/src/decorators/typeproperty/NotUnique.ts:
--------------------------------------------------------------------------------
1 | import typeAnnotationContainer from './typePropertyAnnotationContainer';
2 |
3 | export default function NotUnique() {
4 | // eslint-disable-next-line @typescript-eslint/ban-types
5 | return function(object: Object, propertyName: string) {
6 | typeAnnotationContainer.setTypePropertyAsNotUnique(object.constructor, propertyName);
7 | };
8 | }
9 |
--------------------------------------------------------------------------------
/src/decorators/typeproperty/OneToMany.ts:
--------------------------------------------------------------------------------
1 | import typeAnnotationContainer from './typePropertyAnnotationContainer';
2 |
3 | export default function OneToMany(isReferenceToExternalEntity = false) {
4 | // eslint-disable-next-line @typescript-eslint/ban-types
5 | return function(object: Object, propertyName: string) {
6 | typeAnnotationContainer.setTypePropertyAsOneToMany(
7 | object.constructor,
8 | propertyName,
9 | isReferenceToExternalEntity
10 | );
11 | };
12 | }
13 |
--------------------------------------------------------------------------------
/src/decorators/typeproperty/ShouldBeTrue.ts:
--------------------------------------------------------------------------------
1 | import { ValidationArguments, ValidationOptions, registerDecorator } from 'class-validator';
2 |
3 | export default function ShouldBeTrue(
4 | validateValue: (value: any) => boolean,
5 | errorMessage?: string,
6 | validationOptions?: ValidationOptions
7 | ) {
8 | // eslint-disable-next-line @typescript-eslint/ban-types
9 | return function(object: Object, propertyName: string) {
10 | registerDecorator({
11 | name: 'shouldBeTrue',
12 | target: object.constructor,
13 | propertyName: propertyName,
14 | constraints: ['shouldBeTrue', validateValue],
15 | options: validationOptions,
16 | validator: {
17 | validate(value: any, args: ValidationArguments) {
18 | return validateValue(value);
19 | },
20 | defaultMessage: () =>
21 | errorMessage ? errorMessage : 'Property did not match predicate: ' + validateValue.toString()
22 | }
23 | });
24 | };
25 | }
26 |
--------------------------------------------------------------------------------
/src/decorators/typeproperty/ShouldBeTrueForObject.ts:
--------------------------------------------------------------------------------
1 | import { ValidationArguments, ValidationOptions, registerDecorator } from 'class-validator';
2 |
3 | export default function ShouldBeTrueForObject(
4 | validateObject: (object: T) => boolean,
5 | errorMessage?: string,
6 | validationOptions?: ValidationOptions
7 | ) {
8 | // eslint-disable-next-line @typescript-eslint/ban-types
9 | return function(object: Object, propertyName: string) {
10 | registerDecorator({
11 | name: 'shouldBeTrueForObject',
12 | target: object.constructor,
13 | propertyName: propertyName,
14 | constraints: ['shouldBeTrueForObject', validateObject],
15 | options: validationOptions,
16 | validator: {
17 | validate(value: any, args: ValidationArguments) {
18 | return validateObject(args.object as any);
19 | },
20 | defaultMessage: () =>
21 | errorMessage ? errorMessage : 'Object did not match predicate: ' + validateObject.toString()
22 | }
23 | });
24 | };
25 | }
26 |
--------------------------------------------------------------------------------
/src/decorators/typeproperty/Transient.ts:
--------------------------------------------------------------------------------
1 | import typeAnnotationContainer from './typePropertyAnnotationContainer';
2 |
3 | export default function Transient() {
4 | // eslint-disable-next-line @typescript-eslint/ban-types
5 | return function(object: Object, propertyName: string) {
6 | typeAnnotationContainer.setTypePropertyAsTransient(object.constructor, propertyName);
7 | };
8 | }
9 |
--------------------------------------------------------------------------------
/src/decorators/typeproperty/UiProperties.ts:
--------------------------------------------------------------------------------
1 | export type UiProps = {
2 | shouldDisplay: boolean,
3 | booleanValueInputType: 'switch' | 'checkbox'
4 | isSearch: boolean
5 | }
6 |
7 | // eslint-disable-next-line @typescript-eslint/no-unused-vars
8 | export default function UiProperties(uiProperties: Partial) {
9 | return function() {
10 | // NO OPERATION
11 | };
12 | }
13 |
--------------------------------------------------------------------------------
/src/decorators/typeproperty/Unique.ts:
--------------------------------------------------------------------------------
1 | import typeAnnotationContainer from './typePropertyAnnotationContainer';
2 |
3 | export default function Unique() {
4 | // eslint-disable-next-line @typescript-eslint/ban-types
5 | return function(object: Object, propertyName: string) {
6 | typeAnnotationContainer.setTypePropertyAsUnique(object.constructor, propertyName);
7 | };
8 | }
9 |
--------------------------------------------------------------------------------
/src/decorators/typeproperty/access/CreateOnly.ts:
--------------------------------------------------------------------------------
1 | import typeAnnotationContainer from '../typePropertyAnnotationContainer';
2 |
3 | export default function CreateOnly() {
4 | // eslint-disable-next-line @typescript-eslint/ban-types
5 | return function(object: Object, propertyName: string) {
6 | typeAnnotationContainer.setTypePropertyAsCreateOnly(object.constructor, propertyName);
7 | };
8 | }
9 |
--------------------------------------------------------------------------------
/src/decorators/typeproperty/access/Private.ts:
--------------------------------------------------------------------------------
1 | import typeAnnotationContainer from '../typePropertyAnnotationContainer';
2 |
3 | export default function Private() {
4 | // eslint-disable-next-line @typescript-eslint/ban-types
5 | return function(object: Object, propertyName: string) {
6 | typeAnnotationContainer.setTypePropertyAsPrivate(object.constructor, propertyName);
7 | };
8 | }
9 |
--------------------------------------------------------------------------------
/src/decorators/typeproperty/access/ReadOnly.ts:
--------------------------------------------------------------------------------
1 | import typeAnnotationContainer from '../typePropertyAnnotationContainer';
2 |
3 | export default function ReadOnly() {
4 | // eslint-disable-next-line @typescript-eslint/ban-types
5 | return function(object: Object, propertyName: string) {
6 | typeAnnotationContainer.setTypePropertyAsReadOnly(object.constructor, propertyName);
7 | };
8 | }
9 |
--------------------------------------------------------------------------------
/src/decorators/typeproperty/access/ReadUpdate.ts:
--------------------------------------------------------------------------------
1 | import typeAnnotationContainer from '../typePropertyAnnotationContainer';
2 |
3 | export default function ReadUpdate() {
4 | // eslint-disable-next-line @typescript-eslint/ban-types
5 | return function(object: Object, propertyName: string) {
6 | typeAnnotationContainer.setTypePropertyAsReadUpdate(object.constructor, propertyName);
7 | };
8 | }
9 |
--------------------------------------------------------------------------------
/src/decorators/typeproperty/access/ReadWrite.ts:
--------------------------------------------------------------------------------
1 | import typeAnnotationContainer from '../typePropertyAnnotationContainer';
2 |
3 | export default function ReadWrite() {
4 | // eslint-disable-next-line @typescript-eslint/ban-types
5 | return function(object: Object, propertyName: string) {
6 | typeAnnotationContainer.setTypePropertyAsReadWrite(object.constructor, propertyName);
7 | };
8 | }
9 |
--------------------------------------------------------------------------------
/src/decorators/typeproperty/access/UpdateOnly.ts:
--------------------------------------------------------------------------------
1 | import typeAnnotationContainer from '../typePropertyAnnotationContainer';
2 |
3 | export default function UpdateOnly() {
4 | // eslint-disable-next-line @typescript-eslint/ban-types
5 | return function(object: Object, propertyName: string) {
6 | typeAnnotationContainer.setTypePropertyAsUpdateOnly(object.constructor, propertyName);
7 | };
8 | }
9 |
--------------------------------------------------------------------------------
/src/decorators/typeproperty/access/WriteOnly.ts:
--------------------------------------------------------------------------------
1 | import typeAnnotationContainer from '../typePropertyAnnotationContainer';
2 |
3 | export default function WriteOnly() {
4 | // eslint-disable-next-line @typescript-eslint/ban-types
5 | return function(object: Object, propertyName: string) {
6 | typeAnnotationContainer.setTypePropertyAsWriteOnly(object.constructor, propertyName);
7 | };
8 | }
9 |
--------------------------------------------------------------------------------
/src/decorators/typeproperty/datetime/IsDateBetween.ts:
--------------------------------------------------------------------------------
1 | import { ValidationOptions, registerDecorator } from 'class-validator';
2 | import dayjs from 'dayjs';
3 | import isBetween from 'dayjs/plugin/isBetween';
4 | import customParseFormat from 'dayjs/plugin/customParseFormat'
5 |
6 | dayjs.extend(isBetween);
7 | dayjs.extend(customParseFormat);
8 |
9 | export default function IsDateBetween(
10 | startValue: string,
11 | endValue: string,
12 | validationOptions?: ValidationOptions
13 | ) {
14 | return function (object: Record, propertyName: string) {
15 | registerDecorator({
16 | name: 'isDateBetween',
17 | target: object.constructor,
18 | propertyName: propertyName,
19 | constraints: ['isDateBetween', startValue, endValue],
20 | options: validationOptions,
21 | validator: {
22 | validate(value: any) {
23 | const date = dayjs(value);
24 | const startDate = dayjs(startValue, 'YYYY-MM-DD');
25 | const endDate = dayjs(endValue, 'YYYY-MM-DD');
26 | return date.isBetween(startDate, endDate, 'date', '[]');
27 | },
28 | defaultMessage: () =>
29 | propertyName +
30 | ' must be a timestamp where ' +
31 | 'date is between ' +
32 | startValue +
33 | ' and ' +
34 | endValue,
35 | },
36 | });
37 | };
38 | }
39 |
--------------------------------------------------------------------------------
/src/decorators/typeproperty/datetime/IsDayOfMonthIn.ts:
--------------------------------------------------------------------------------
1 | import { ValidationOptions, registerDecorator } from 'class-validator';
2 | import dayjs from 'dayjs';
3 |
4 | export default function IsDayOfMonthIn(values: number[], validationOptions?: ValidationOptions) {
5 | return function (object: Record, propertyName: string) {
6 | registerDecorator({
7 | name: 'isDayOfMonthIn',
8 | target: object.constructor,
9 | propertyName: propertyName,
10 | constraints: ['isDayOfMonthIn', values],
11 | options: validationOptions,
12 | validator: {
13 | validate(value: any) {
14 | const date = dayjs(value);
15 | const day = date.date();
16 | return values.includes(day);
17 | },
18 | defaultMessage: () =>
19 | propertyName + ' must have day of month: ' + values.join(', '),
20 | },
21 | });
22 | };
23 | }
24 |
--------------------------------------------------------------------------------
/src/decorators/typeproperty/datetime/IsDayOfWeekBetween.ts:
--------------------------------------------------------------------------------
1 | import { ValidationOptions, registerDecorator } from 'class-validator';
2 | import dayjs from 'dayjs';
3 |
4 | export enum DayOfWeek {
5 | Sunday = 0,
6 | Monday,
7 | Tuesday,
8 | Wednesday,
9 | Thursday,
10 | Friday,
11 | Saturday,
12 | }
13 |
14 | export default function IsDayOfWeekBetween(
15 | startValue: DayOfWeek,
16 | endValue: DayOfWeek,
17 | validationOptions?: ValidationOptions
18 | ) {
19 | return function (object: Record, propertyName: string) {
20 | registerDecorator({
21 | name: 'isDayOfWeekBetween',
22 | target: object.constructor,
23 | propertyName: propertyName,
24 | constraints: ['isDayOfWeekBetween', startValue, endValue],
25 | options: validationOptions,
26 | validator: {
27 | validate(value: any) {
28 | const weekDay = dayjs(value).day();
29 | return weekDay >= startValue && weekDay <= endValue;
30 | },
31 | defaultMessage: () =>
32 | propertyName +
33 | ' must be a timestamp where week day is between ' +
34 | DayOfWeek[startValue] +
35 | ' and ' +
36 | DayOfWeek[endValue],
37 | },
38 | });
39 | };
40 | }
41 |
--------------------------------------------------------------------------------
/src/decorators/typeproperty/datetime/IsHourIn.ts:
--------------------------------------------------------------------------------
1 | import { ValidationOptions, registerDecorator } from 'class-validator';
2 | import dayjs from 'dayjs';
3 |
4 | export default function IsHourIn(values: number[], validationOptions?: ValidationOptions) {
5 | return function (object: Record, propertyName: string) {
6 | registerDecorator({
7 | name: 'isHourIn',
8 | target: object.constructor,
9 | propertyName: propertyName,
10 | constraints: ['isHourIn', values],
11 | options: validationOptions,
12 | validator: {
13 | validate(value: any) {
14 | const date = dayjs(value);
15 | const hour = date.hour();
16 | return values.includes(hour);
17 | },
18 | defaultMessage: () =>
19 | propertyName + ' must have hour: ' + values.join(', '),
20 | },
21 | });
22 | };
23 | }
24 |
--------------------------------------------------------------------------------
/src/decorators/typeproperty/datetime/IsMinuteIn.ts:
--------------------------------------------------------------------------------
1 | import { ValidationOptions, registerDecorator } from 'class-validator';
2 | import dayjs from 'dayjs';
3 |
4 | export default function IsMinuteIn(values: number[], validationOptions?: ValidationOptions) {
5 | return function (object: Record, propertyName: string) {
6 | registerDecorator({
7 | name: 'isMinuteIn',
8 | target: object.constructor,
9 | propertyName: propertyName,
10 | constraints: ['isMinuteIn', values],
11 | options: validationOptions,
12 | validator: {
13 | validate(value: any) {
14 | const date = dayjs(value);
15 | const minute = date.minute();
16 | return values.includes(minute);
17 | },
18 | defaultMessage: () =>
19 | propertyName + ' must have minute: ' + values.join(', '),
20 | },
21 | });
22 | };
23 | }
24 |
--------------------------------------------------------------------------------
/src/decorators/typeproperty/datetime/IsMonthIn.ts:
--------------------------------------------------------------------------------
1 | import { ValidationOptions, registerDecorator } from 'class-validator';
2 | import dayjs from 'dayjs';
3 |
4 | export default function IsMonthIn(values: number[], validationOptions?: ValidationOptions) {
5 | return function (object: Record, propertyName: string) {
6 | registerDecorator({
7 | name: 'isMonthIn',
8 | target: object.constructor,
9 | propertyName: propertyName,
10 | constraints: ['isMonthIn', values],
11 | options: validationOptions,
12 | validator: {
13 | validate(value: any) {
14 | const date = dayjs(value);
15 | const month = date.month() + 1;
16 | return values.includes(month);
17 | },
18 | defaultMessage: () =>
19 | propertyName + ' must have month: ' + values.join(', '),
20 | },
21 | });
22 | };
23 | }
24 |
--------------------------------------------------------------------------------
/src/decorators/typeproperty/datetime/IsTimeBetween.spec.ts:
--------------------------------------------------------------------------------
1 | import dayjs from 'dayjs';
2 | import isBetween from 'dayjs/plugin/isBetween';
3 | import customParseFormat from 'dayjs/plugin/customParseFormat'
4 |
5 | dayjs.extend(isBetween);
6 | dayjs.extend(customParseFormat);
7 |
8 | describe('isTimeBetween', () => {
9 | it('should work', () => {
10 | const startDate = dayjs('08:00', 'HH:mm');
11 | const endDate = dayjs('18:00', 'HH:mm');
12 | const testDate = dayjs('18:00', 'HH:mm');
13 | console.log(testDate.isBetween(startDate, endDate, 'minute', '[]'))
14 | })
15 | })
16 |
--------------------------------------------------------------------------------
/src/decorators/typeproperty/datetime/IsTimeBetween.ts:
--------------------------------------------------------------------------------
1 | import { ValidationOptions, registerDecorator } from 'class-validator';
2 | import dayjs from 'dayjs';
3 | import isBetween from 'dayjs/plugin/isBetween';
4 | import customParseFormat from 'dayjs/plugin/customParseFormat'
5 |
6 | dayjs.extend(isBetween);
7 | dayjs.extend(customParseFormat);
8 |
9 | export default function IsTimeBetween(
10 | startValue: string,
11 | endValue: string,
12 | validationOptions?: ValidationOptions
13 | ) {
14 | return function (object: Record, propertyName: string) {
15 | registerDecorator({
16 | name: 'isTimeBetween',
17 | target: object.constructor,
18 | propertyName: propertyName,
19 | constraints: ['isTimeBetween', startValue, endValue],
20 | options: validationOptions,
21 | validator: {
22 | validate(value: any) {
23 | const date = dayjs(value);
24 | const startDate = dayjs(startValue, 'HH:mm');
25 | const endDate = dayjs(endValue, 'HH:mm');
26 | return date.isBetween(startDate, endDate, 'minute', '[]')
27 | },
28 | defaultMessage: () =>
29 | propertyName +
30 | ' must be a timestamp where ' +
31 | 'year and month is between ' +
32 | startValue +
33 | ' and ' +
34 | endValue,
35 | },
36 | });
37 | };
38 | }
39 |
--------------------------------------------------------------------------------
/src/decorators/typeproperty/datetime/IsTimestampBetween.ts:
--------------------------------------------------------------------------------
1 | import { ValidationOptions, registerDecorator } from 'class-validator';
2 | import dayjs from 'dayjs';
3 | import isBetween from 'dayjs/plugin/isBetween';
4 | import customParseFormat from 'dayjs/plugin/customParseFormat'
5 |
6 | dayjs.extend(isBetween);
7 | dayjs.extend(customParseFormat);
8 |
9 | export default function IsTimestampBetween(
10 | startValue: string,
11 | endValue: string,
12 | validationOptions?: ValidationOptions
13 | ) {
14 | return function (object: Record, propertyName: string) {
15 | registerDecorator({
16 | name: 'isTimestampBetween',
17 | target: object.constructor,
18 | propertyName: propertyName,
19 | constraints: ['isTimestampBetween', startValue, endValue],
20 | options: validationOptions,
21 | validator: {
22 | validate(value: any) {
23 | const date = dayjs(value);
24 | const startDate = dayjs(startValue);
25 | const endDate = dayjs(endValue);
26 | return date.isBetween(startDate, endDate, 'minute', '[]');
27 | },
28 | defaultMessage: () =>
29 | propertyName +
30 | ' must be a timestamp where ' +
31 | 'year and month is between ' +
32 | startValue +
33 | ' and ' +
34 | endValue,
35 | },
36 | });
37 | };
38 | }
39 |
--------------------------------------------------------------------------------
/src/decorators/typeproperty/testing/TestValue.ts:
--------------------------------------------------------------------------------
1 | import testValueContainer from './testValueContainer';
2 |
3 | export default function TestValue(testValue: any){
4 | // eslint-disable-next-line
5 | return function(object: Object, propertyName: string) {
6 | testValueContainer.addTestValue(object.constructor, propertyName, testValue);
7 | };
8 | }
9 |
--------------------------------------------------------------------------------
/src/errors/createBackkErrorFromErrorCodeMessageAndStatus.ts:
--------------------------------------------------------------------------------
1 | import createBackkErrorFromError from './createBackkErrorFromError';
2 | import { HttpStatusCodes } from '../constants/constants';
3 | import { BackkError } from "../types/BackkError";
4 |
5 | export default function createBackkErrorFromErrorCodeMessageAndStatus(
6 | errorCodeMessageAndStatus: BackkError
7 | ) {
8 | return createBackkErrorFromError(
9 | new Error(
10 | (errorCodeMessageAndStatus.statusCode ?? HttpStatusCodes.INTERNAL_SERVER_ERROR) +
11 | ':Error code ' +
12 | errorCodeMessageAndStatus.errorCode +
13 | ':' +
14 | errorCodeMessageAndStatus.message
15 | )
16 | );
17 | }
18 |
--------------------------------------------------------------------------------
/src/errors/createBackkErrorFromErrorMessageAndStatusCode.ts:
--------------------------------------------------------------------------------
1 | import createErrorMessageWithStatusCode from "./createErrorMessageWithStatusCode";
2 | import createBackkErrorFromError from "./createBackkErrorFromError";
3 |
4 | export default function createBackkErrorFromErrorMessageAndStatusCode(errorMessage: string, statusCode: number) {
5 | const finalErrorMessage = createErrorMessageWithStatusCode(errorMessage, statusCode);
6 | return createBackkErrorFromError(new Error(finalErrorMessage));
7 | }
8 |
--------------------------------------------------------------------------------
/src/errors/createErrorFromErrorCodeMessageAndStatus.ts:
--------------------------------------------------------------------------------
1 | import { HttpStatusCodes } from "../constants/constants";
2 | import { BackkError } from "../types/BackkError";
3 |
4 | export default function createErrorFromErrorCodeMessageAndStatus(
5 | errorCodeMessageAndStatus: BackkError
6 | ): Error {
7 | return new Error(
8 | (errorCodeMessageAndStatus.statusCode ?? HttpStatusCodes.INTERNAL_SERVER_ERROR) +
9 | ':Error code ' +
10 | errorCodeMessageAndStatus.errorCode +
11 | ':' +
12 | errorCodeMessageAndStatus.message
13 | );
14 | }
15 |
--------------------------------------------------------------------------------
/src/errors/createErrorFromErrorMessageAndThrowError.ts:
--------------------------------------------------------------------------------
1 | import createBackkErrorFromError from "./createBackkErrorFromError";
2 |
3 | export default function createErrorFromErrorMessageAndThrowError(errorMessage: string) {
4 | throw createBackkErrorFromError(new Error(errorMessage));
5 | }
6 |
--------------------------------------------------------------------------------
/src/errors/createErrorMessageWithStatusCode.ts:
--------------------------------------------------------------------------------
1 | export default function createErrorMessageWithStatusCode(
2 | errorMessage: string,
3 | statusCode: number
4 | ): string {
5 | return statusCode + ':' + errorMessage;
6 | }
7 |
--------------------------------------------------------------------------------
/src/errors/createInternalServerError.ts:
--------------------------------------------------------------------------------
1 | import { BackkError } from "../types/BackkError";
2 | import { HttpStatusCodes } from "../constants/constants";
3 |
4 | export default function createInternalServerError(errorMessage: string): BackkError {
5 | return {
6 | message: errorMessage,
7 | statusCode: HttpStatusCodes.INTERNAL_SERVER_ERROR
8 | };
9 | }
10 |
--------------------------------------------------------------------------------
/src/errors/emptyError.ts:
--------------------------------------------------------------------------------
1 | import { BackkError } from "../types/BackkError";
2 |
3 | const emptyError: BackkError = {
4 | statusCode: 500,
5 | message: 'Empty error'
6 | }
7 |
8 | export default emptyError;
9 |
--------------------------------------------------------------------------------
/src/errors/isBackkError.ts:
--------------------------------------------------------------------------------
1 | export default function isBackkError(error: any): boolean {
2 | return 'statusCode' in error && 'message' in error;
3 | }
4 |
--------------------------------------------------------------------------------
/src/execution/BackkResponse.ts:
--------------------------------------------------------------------------------
1 | import { HttpStatusCodes } from '../constants/constants';
2 | import { BackkError } from '../types/BackkError';
3 |
4 | export default class BackkResponse {
5 | private statusCode: number = HttpStatusCodes.INTERNAL_SERVER_ERROR;
6 | private response: object | null | undefined = {};
7 |
8 | setHeader() {
9 | // No operation
10 | }
11 |
12 | writeHead(statusCode: number) {
13 | this.statusCode = statusCode;
14 | }
15 |
16 | end(response: object | null | undefined) {
17 | this.response = response;
18 | }
19 |
20 | getStatusCode(): number {
21 | return this.statusCode;
22 | }
23 |
24 | getResponse(): object | null | undefined {
25 | return this.response;
26 | }
27 |
28 | getErrorResponse(): BackkError | null {
29 | if (this.statusCode >= HttpStatusCodes.ERRORS_START) {
30 | return {
31 | statusCode: this.statusCode,
32 | errorCode: (this.response as any).errorCode,
33 | stackTrace: (this.response as any).stackTrace,
34 | message: (this.response as any).message
35 | };
36 | }
37 |
38 | return null;
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/execution/ServiceFunctionCall.ts:
--------------------------------------------------------------------------------
1 | export interface ServiceFunctionCall {
2 | serviceFunctionName: string;
3 | serviceFunctionArgument: any;
4 | microserviceName?: string;
5 | microserviceNamespace?: string;
6 | }
7 |
--------------------------------------------------------------------------------
/src/execution/ServiceFunctionCallResponse.ts:
--------------------------------------------------------------------------------
1 | export interface ServiceFunctionCallResponse {
2 | statusCode: number;
3 | response: object | null | void;
4 | }
5 |
--------------------------------------------------------------------------------
/src/execution/isExecuteMultipleRequest.ts:
--------------------------------------------------------------------------------
1 | export default function isExecuteMultipleRequest(serviceFunctionName: string) {
2 | if (
3 | serviceFunctionName === 'executeMultipleServiceFunctionsInParallelInsideTransaction' ||
4 | serviceFunctionName === 'executeMultipleServiceFunctionsInParallelWithoutTransaction' ||
5 | serviceFunctionName === 'executeMultipleServiceFunctionsInSequenceInsideTransaction' ||
6 | serviceFunctionName === 'executeMultipleServiceFunctionsInSequenceWithoutTransaction'
7 | ) {
8 | return true;
9 | }
10 |
11 | return false;
12 | }
13 |
--------------------------------------------------------------------------------
/src/execution/isValidServiceFunctionName.ts:
--------------------------------------------------------------------------------
1 | export default function isValidServiceFunctionName(serviceFunctionName: string, controller: any) {
2 | const [serviceName, functionName] = serviceFunctionName.split('.');
3 |
4 | if (!controller[serviceName]) {
5 | return false;
6 | }
7 |
8 | const serviceFunctionResponseValueTypeName =
9 | controller[`${serviceName}__BackkTypes__`].functionNameToReturnTypeNameMap[functionName];
10 |
11 | if (!controller[serviceName][functionName] || !serviceFunctionResponseValueTypeName) {
12 | return false;
13 | }
14 |
15 | return true;
16 | }
17 |
--------------------------------------------------------------------------------
/src/file/getObjectsFromCsvFileOrThrow.ts:
--------------------------------------------------------------------------------
1 | import { readFileSync } from 'fs';
2 | import parse from 'csv-parse/lib/sync';
3 |
4 | export default function getObjectsFromCsvFileOrThrow(
5 | filePathNameRelativeToResourcesDir: string,
6 | columnNames: string[] | 'readFromFirstRow' = 'readFromFirstRow',
7 | delimiter = ','
8 | ): { [key: string]: string }[] {
9 | const fullPathName = process.cwd() + "/build/resources/" + filePathNameRelativeToResourcesDir;
10 | return parse(readFileSync(fullPathName, { encoding: 'UTF-8' }), {
11 | columns: columnNames === 'readFromFirstRow' ? true : columnNames,
12 | // eslint-disable-next-line @typescript-eslint/camelcase
13 | skip_empty_lines: true,
14 | trim: true,
15 | delimiter
16 | });
17 | }
18 |
--------------------------------------------------------------------------------
/src/file/getSeparatedIntegerValuesFromTextFileOrThrow.ts:
--------------------------------------------------------------------------------
1 | import { readFileSync } from 'fs';
2 |
3 | export default function getSeparatedIntegerValuesFromTextFileOrThrow(
4 | filePathNameRelativeToResourcesDir: string,
5 | separator = '\n'
6 | ): number[] {
7 | const fullPathName = process.cwd() + "/build/resources/" + filePathNameRelativeToResourcesDir;
8 | return readFileSync(fullPathName, { encoding: 'UTF-8' })
9 | .split(separator)
10 | .filter((value) => value)
11 | .map((value) => value.trim())
12 | .map((value) => parseInt(value, 10));
13 | }
14 |
--------------------------------------------------------------------------------
/src/file/getSeparatedValuesFromTextFileOrThrow.ts:
--------------------------------------------------------------------------------
1 | import { readFileSync } from 'fs';
2 |
3 | export default function getSeparatedValuesFromTextFileOrThrow(
4 | filePathNameRelativeToResourcesDir: string,
5 | separator = '\n'
6 | ): string[] {
7 | const fullPathName = process.cwd() + '/build/resources/' + filePathNameRelativeToResourcesDir;
8 | return readFileSync(fullPathName, { encoding: 'UTF-8' })
9 | .split(separator)
10 | .filter((value) => value)
11 | .map((value) => value.trim());
12 | }
13 |
--------------------------------------------------------------------------------
/src/file/getValuesByJsonPathFromJsonFileOrThrow.ts:
--------------------------------------------------------------------------------
1 | import { readFileSync } from 'fs';
2 | import { JSONPath } from 'jsonpath-plus';
3 |
4 | export default function getValuesByJsonPathFromJsonFileOrThrow(filePathNameRelativeToResourcesDir: string, jsonPath: string): any[] {
5 | const fullPathName = process.cwd() + "/build/resources/" + filePathNameRelativeToResourcesDir;
6 | const object = JSON.parse(readFileSync(fullPathName, { encoding: 'UTF-8' }));
7 | return JSONPath({ json: object, path: jsonPath });
8 | }
9 |
--------------------------------------------------------------------------------
/src/file/getValuesByXPathFromXmlFileOrThrow.ts:
--------------------------------------------------------------------------------
1 | import { readFileSync } from 'fs';
2 | import xmldom from 'xmldom';
3 | import xpath from 'xpath';
4 |
5 | export default function getValuesByXPathFromXmlFileOrThrow(filePathNameRelativeToResourcesDir: string, xPath: string): any[] {
6 | const fullPathName = process.cwd() + "/build/resources/" + filePathNameRelativeToResourcesDir;
7 | const document = new xmldom.DOMParser().parseFromString(readFileSync(fullPathName, { encoding: 'UTF-8' }));
8 | return xpath.select(xPath, document);
9 | }
10 |
--------------------------------------------------------------------------------
/src/metadata/findServiceFunctionArgumentType.ts:
--------------------------------------------------------------------------------
1 | import { ServiceMetadata } from './types/ServiceMetadata';
2 |
3 | export default function findServiceFunctionArgumentType(
4 | controller: any,
5 | serviceFunctionName: string
6 | ): (new() => any) | undefined {
7 | const [serviceName, functionName] = serviceFunctionName.split('.');
8 | const servicesMetadata: ServiceMetadata[] = controller.servicesMetadata;
9 |
10 | const foundServiceMetadata = servicesMetadata.find(
11 | (serviceMetadata) => serviceMetadata.serviceName === serviceName
12 | );
13 |
14 | const foundFunctionMetadata = foundServiceMetadata?.functions.find(func => func.functionName === functionName);
15 |
16 | if (foundFunctionMetadata) {
17 | return controller[serviceName].Types[foundFunctionMetadata.argType];
18 | }
19 |
20 | return undefined;
21 | }
22 |
--------------------------------------------------------------------------------
/src/metadata/getTypeDocumentation.ts:
--------------------------------------------------------------------------------
1 | import typeAnnotationContainer from '../decorators/typeproperty/typePropertyAnnotationContainer';
2 | import typePropertyAnnotationContainer from '../decorators/typeproperty/typePropertyAnnotationContainer';
3 |
4 | export default function getTypeDocumentation(
5 | typeMetadata: { [key: string]: string } | undefined,
6 | Class: new () => T
7 | ): { [key: string]: string } {
8 | return Object.keys(typeMetadata ?? {}).reduce((accumulatedTypeDocs, propertyName) => {
9 | if (typePropertyAnnotationContainer.isTypePropertyPrivate(Class, propertyName)) {
10 | return accumulatedTypeDocs;
11 | }
12 |
13 | const typePropertyDocumentation = typeAnnotationContainer.getDocumentationForTypeProperty(
14 | Class,
15 | propertyName
16 | );
17 |
18 | return typePropertyDocumentation
19 | ? {
20 | ...accumulatedTypeDocs,
21 | [propertyName]: typePropertyDocumentation.trim()
22 | }
23 | : accumulatedTypeDocs;
24 | }, {});
25 | }
26 |
--------------------------------------------------------------------------------
/src/metadata/types/FunctionMetadata.ts:
--------------------------------------------------------------------------------
1 | import { ErrorDefinition } from "../../types/ErrorDefinition";
2 |
3 | export type FunctionMetadata = {
4 | functionName: string;
5 | documentation?: string;
6 | argType: string;
7 | returnValueType: string;
8 | errors: ErrorDefinition[];
9 | };
10 |
--------------------------------------------------------------------------------
/src/metadata/types/ServiceMetadata.ts:
--------------------------------------------------------------------------------
1 | import { FunctionMetadata } from './FunctionMetadata';
2 |
3 | export type ServiceMetadata = {
4 | serviceName: string;
5 | serviceDocumentation?: string;
6 | functions: FunctionMetadata[];
7 | types: { [p: string]: object };
8 | publicTypes: { [p: string]: object };
9 | propertyAccess: { [p: string]: object };
10 | typeReferences: { [p: string]: string };
11 | typesDocumentation?: object;
12 | validations: { [p: string]: any[] };
13 | };
14 |
--------------------------------------------------------------------------------
/src/microservice/getMicroserviceServiceByServiceClass.ts:
--------------------------------------------------------------------------------
1 | export default function getMicroserviceServiceByServiceClass(
2 | microservice: any,
3 | ServiceClass: Function
4 | ): any {
5 | const services = Object.values(microservice).filter((service) => service instanceof ServiceClass);
6 | if (services.length > 1) {
7 | throw new Error('There can be only one instance of class ' + ServiceClass.name + ' in your microservice')
8 | }
9 | return services[0];
10 | }
11 |
--------------------------------------------------------------------------------
/src/microservice/getMicroserviceServiceNameByServiceClass.ts:
--------------------------------------------------------------------------------
1 | export default function getMicroserviceServiceNameByServiceClass(
2 | microservice: any,
3 | ServiceClass: Function
4 | ): string {
5 | const service = Object.entries(microservice).find(([, service]) => service instanceof ServiceClass);
6 | if (service) {
7 | return service[0];
8 | }
9 |
10 | return 'Service name not found';
11 | }
12 |
--------------------------------------------------------------------------------
/src/observability/distributedtracinig/initializeDefaultJaegerTracing.ts:
--------------------------------------------------------------------------------
1 | import { SimpleSpanProcessor, SpanProcessor } from "@opentelemetry/tracing";
2 | import { JaegerExporter } from "@opentelemetry/exporter-jaeger";
3 | import { ExporterConfig } from "@opentelemetry/exporter-jaeger/build/src/types";
4 | import tracerProvider from "./tracerProvider";
5 | import getMicroserviceName from "../../utils/getMicroserviceName";
6 |
7 | function initializeTracing(spanProcessor: SpanProcessor) {
8 | tracerProvider.addSpanProcessor(spanProcessor);
9 | }
10 |
11 | export default function initializeDefaultJaegerTracing(jaegerExporterOptions?: Partial) {
12 | initializeTracing(
13 | new SimpleSpanProcessor(
14 | new JaegerExporter({
15 | serviceName: getMicroserviceName(),
16 | ...(jaegerExporterOptions ?? {})
17 | })
18 | )
19 | );
20 | }
21 |
--------------------------------------------------------------------------------
/src/observability/distributedtracinig/tracerProvider.ts:
--------------------------------------------------------------------------------
1 | import { NodeTracerProvider } from "@opentelemetry/node";
2 |
3 | const tracerProvider = new NodeTracerProvider({
4 | plugins: {
5 | express: {
6 | enabled: false
7 | }
8 | }
9 | });
10 |
11 | tracerProvider.register();
12 |
13 | export default tracerProvider;
14 |
--------------------------------------------------------------------------------
/src/observability/logging/LogEntry.ts:
--------------------------------------------------------------------------------
1 | export interface Resource {
2 | 'service.name': string;
3 | 'service.namespace': string;
4 | 'service.instance.id': string;
5 | 'service.version': string;
6 | 'node.name': string;
7 | }
8 |
9 | export interface LogEntry {
10 | Timestamp: string;
11 | TraceId?: string;
12 | SpanId?: string;
13 | TraceFlags?: number;
14 | SeverityText: string;
15 | SeverityNumber: number;
16 | Name: string;
17 | Body: string;
18 | Resource: Resource;
19 | Attributes?: { [key: string]: string | number | boolean | undefined };
20 | }
21 |
--------------------------------------------------------------------------------
/src/observability/logging/audit/AuditLogEntry.ts:
--------------------------------------------------------------------------------
1 | import { Resource } from '../LogEntry';
2 |
3 | export type UserOperationResult = 'success' | 'failure'
4 |
5 | export interface AuditLogEntry {
6 | Timestamp: string;
7 | TraceId?: string;
8 | SpanId?: string;
9 | TraceFlags?: number;
10 | subject: string;
11 | clientIp: string;
12 | authorizationHeader: string;
13 | userOperation: {
14 | name: string,
15 | result: UserOperationResult,
16 | statusCode: number,
17 | errorMessage: string
18 | }
19 | Resource: Resource;
20 | Attributes?: { [key: string]: string | number | boolean | undefined };
21 | }
22 |
--------------------------------------------------------------------------------
/src/observability/logging/audit/AuditLoggingService.ts:
--------------------------------------------------------------------------------
1 | import { AuditLogEntry } from "./AuditLogEntry";
2 |
3 | export default abstract class AuditLoggingService{
4 | abstract log(auditLogEntry: AuditLogEntry): Promise;
5 | }
6 |
--------------------------------------------------------------------------------
/src/observability/logging/logEnvironment.ts:
--------------------------------------------------------------------------------
1 | import log, { Severity } from './log';
2 |
3 | export default function logEnvironment() {
4 | const filteredEnvironment = Object.entries(process.env).reduce(
5 | (accumulatedEnvironment, [envVariableName, envVariableValue]) => {
6 | const upperCaseEnvVariableName = envVariableName.toUpperCase();
7 | if (
8 | !upperCaseEnvVariableName.includes('SECRET') &&
9 | !upperCaseEnvVariableName.includes('KEY') &&
10 | !upperCaseEnvVariableName.includes('CRYPT') &&
11 | !upperCaseEnvVariableName.includes('CIPHER') &&
12 | !upperCaseEnvVariableName.includes('CODE') &&
13 | !upperCaseEnvVariableName.includes('PASSWORD') &&
14 | !upperCaseEnvVariableName.includes('TOKEN') &&
15 | !upperCaseEnvVariableName.includes('PASSWD') &&
16 | !upperCaseEnvVariableName.includes('PWD') &&
17 | !upperCaseEnvVariableName.includes('PASSPHRASE')
18 | ) {
19 | return { ...accumulatedEnvironment, [envVariableName]: envVariableValue };
20 | }
21 |
22 | return accumulatedEnvironment;
23 | },
24 | {}
25 | );
26 |
27 | log(Severity.DEBUG, 'Environment', '', filteredEnvironment);
28 | }
29 |
--------------------------------------------------------------------------------
/src/observability/metrics/defaultPrometheusMeter.ts:
--------------------------------------------------------------------------------
1 | import { MeterProvider } from "@opentelemetry/metrics";
2 | import { PrometheusExporter } from "@opentelemetry/exporter-prometheus";
3 |
4 | export const DEFAULT_METER_INTERVAL_IN_MILLIS = 5000;
5 | export const prometheusExporter = new PrometheusExporter();
6 |
7 | const cwd = process.cwd();
8 | const microserviceName = cwd.split('/').reverse()[0];
9 |
10 | const defaultPrometheusMeter = new MeterProvider({
11 | exporter: prometheusExporter,
12 | interval: DEFAULT_METER_INTERVAL_IN_MILLIS,
13 | }).getMeter(microserviceName);
14 |
15 | export default defaultPrometheusMeter;
16 |
--------------------------------------------------------------------------------
/src/postman/samplevalues/getPhoneNumberSampleValue.ts:
--------------------------------------------------------------------------------
1 | // eslint-disable-next-line @typescript-eslint/ban-ts-ignore
2 | // @ts-ignore
3 | import examples from 'libphonenumber-js/examples.mobile.json'
4 | import { getExampleNumber } from 'libphonenumber-js'
5 |
6 | export default function getPhoneNumberSampleValue(locale: string): string {
7 | const phoneNumber = getExampleNumber(locale as any, examples);
8 | return phoneNumber?.formatNational() ?? 'Invalid phone number locale'
9 | }
10 |
--------------------------------------------------------------------------------
/src/remote/messagequeue/kafka/getKafkaServerFromEnv.ts:
--------------------------------------------------------------------------------
1 | import throwException from '../../../utils/exception/throwException';
2 |
3 | export default function getKafkaServerFromEnv() {
4 | return (
5 | (process.env.KAFKA_HOST ?? throwException('KAFKA_HOST environment value must be defined')) +
6 | ':' +
7 | (process.env.KAFKA_PORT ?? throwException('KAFKA_PORT environment value must be defined'))
8 | );
9 | }
10 |
--------------------------------------------------------------------------------
/src/remote/messagequeue/kafka/logCreator.ts:
--------------------------------------------------------------------------------
1 | import log, { severityNameToSeverityMap } from "../../../observability/logging/log";
2 |
3 | const logCreator = () => ({ label, log: { message, ...extra } }: any) =>
4 | log(severityNameToSeverityMap[label], message, '', extra);
5 |
6 | export default logCreator;
7 |
--------------------------------------------------------------------------------
/src/remote/messagequeue/kafka/minimumLoggingSeverityToKafkaLoggingLevelMap.ts:
--------------------------------------------------------------------------------
1 | import { logLevel } from "kafkajs";
2 |
3 | const minimumLoggingSeverityToKafkaLoggingLevelMap: { [key: string]: number } = {
4 | DEBUG: logLevel.DEBUG,
5 | INFO: logLevel.INFO,
6 | WARN: logLevel.WARN,
7 | ERROR: logLevel.ERROR
8 | };
9 |
10 | export default minimumLoggingSeverityToKafkaLoggingLevelMap;
11 |
--------------------------------------------------------------------------------
/src/remote/messagequeue/kafka/sendToKafka.ts:
--------------------------------------------------------------------------------
1 | import sendToRemoteService from '../sendToRemoteService';
2 |
3 | export default async function sendToKafka(broker: string, topic: string, key: string, message: object) {
4 | return await sendToRemoteService('kafka', broker, key, message);
5 | }
6 |
--------------------------------------------------------------------------------
/src/remote/messagequeue/redis/getRedisServerFromEnv.ts:
--------------------------------------------------------------------------------
1 | import throwException from '../../../utils/exception/throwException';
2 |
3 | export default function getRedisServerFromEnv() {
4 | return (
5 | (process.env.REDIS_HOST ?? throwException('REDIS_HOST environment value must be defined')) +
6 | ':' +
7 | (process.env.REEDIS_PORT ?? throwException('REDIS_PORT environment value must be defined'))
8 | );
9 | }
10 |
--------------------------------------------------------------------------------
/src/remote/utils/parseRemoteServiceFunctionCallUrlParts.ts:
--------------------------------------------------------------------------------
1 | export default function parseRemoteServiceFunctionCallUrlParts(remoteServiceUrl: string) {
2 | const scheme = remoteServiceUrl.split('://')[0];
3 | let server = '', topic, serviceFunctionName;
4 |
5 | if (scheme === 'kafka') {
6 | [server, topic, serviceFunctionName] = remoteServiceUrl.slice(8).split('/');
7 | } else if (scheme === 'redis') {
8 | [server, topic, serviceFunctionName] = remoteServiceUrl.slice(8).split('/');
9 | } else if (scheme === 'http') {
10 | [topic, serviceFunctionName] = remoteServiceUrl.slice(7).split('/');
11 | } else {
12 | throw new Error('Only schemes http://, kafka:// and redis:// are supported');
13 | }
14 |
15 | if ((scheme === 'kafka' || scheme === 'redis') && (!server || server === 'undefined')) {
16 | throw new Error('Remote server not defined in remote service url: ' + remoteServiceUrl);
17 | } else if (!topic || topic === 'undefined') {
18 | throw new Error('Service name not defined in remote service url: ' + remoteServiceUrl);
19 | } else if (!serviceFunctionName || serviceFunctionName === 'undefined') {
20 | throw new Error('Service function not defined in remote service url: ' + remoteServiceUrl);
21 | }
22 |
23 | return { scheme, server, topic, serviceFunctionName };
24 | }
25 |
--------------------------------------------------------------------------------
/src/requestprocessor/AbstractAsyncRequestProcessor.ts:
--------------------------------------------------------------------------------
1 | import Microservice from '../microservice/Microservice';
2 | import { CommunicationMethod } from '../remote/messagequeue/sendToRemoteService';
3 | import { RequestProcessor } from './RequestProcessor';
4 |
5 | export default abstract class AbstractAsyncRequestProcessor implements RequestProcessor {
6 | isAsyncProcessor(): boolean {
7 | return true;
8 | }
9 |
10 | abstract getCommunicationMethod(): CommunicationMethod;
11 | abstract startProcessingRequests(microservice: Microservice): void;
12 | }
13 |
--------------------------------------------------------------------------------
/src/requestprocessor/Http2Response.ts:
--------------------------------------------------------------------------------
1 | import { ServerHttp2Stream } from "http2";
2 |
3 | // noinspection JSClassNamingConvention
4 | export default class Http2Response {
5 | private headers: Record = {};
6 |
7 | constructor (private readonly stream: ServerHttp2Stream) {}
8 |
9 | setHeader(headerName: string, headerValue: string) {
10 | this.headers[headerName] = headerValue;
11 | }
12 |
13 | writeHead(statusCode: number, headers?: any) {
14 | this.headers[':status'] = statusCode + '';
15 | this.headers = {
16 | ...this.headers,
17 | headers
18 | }
19 | }
20 |
21 | end(responseBody: string | null | undefined) {
22 | this.stream.respond(this.headers);
23 | this.stream.write(responseBody);
24 | this.stream.end();
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/requestprocessor/KafkaConsumer.ts:
--------------------------------------------------------------------------------
1 | import { ITopicConfig } from 'kafkajs';
2 | import consumeFromKafka from '../remote/messagequeue/kafka/consumeFromKafka';
3 | import getNamespacedMicroserviceName from '../utils/getNamespacedMicroserviceName';
4 | import AbstractAsyncRequestProcessor from './AbstractAsyncRequestProcessor';
5 | import { CommunicationMethod } from "../remote/messagequeue/sendToRemoteService";
6 | import log, { Severity } from "../observability/logging/log";
7 |
8 | export default class KafkaConsumer extends AbstractAsyncRequestProcessor {
9 | constructor(
10 | private readonly defaultTopicConfig?: Omit,
11 | private readonly additionalTopics?: string[]
12 | ) {
13 | super();
14 | }
15 |
16 | startProcessingRequests(): void {
17 | consumeFromKafka(
18 | this,
19 | process.env.KAFKA_HOST,
20 | process.env.KAFKA_PORT,
21 | getNamespacedMicroserviceName(),
22 | this.defaultTopicConfig,
23 | this.additionalTopics
24 | );
25 | log(Severity.INFO, `Kafka consumer started for Kafka server: ${process.env.KAFKA_HOST}:${process.env.KAFKA_PORT}`, '');
26 | }
27 |
28 | getCommunicationMethod(): CommunicationMethod {
29 | return 'kafka';
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/requestprocessor/RedisConsumer.ts:
--------------------------------------------------------------------------------
1 | import consumeFromRedis from "../remote/messagequeue/redis/consumeFromRedis";
2 | import AbstractAsyncRequestProcessor from "./AbstractAsyncRequestProcessor";
3 | import { CommunicationMethod } from "../remote/messagequeue/sendToRemoteService";
4 | import log, { Severity } from "../observability/logging/log";
5 |
6 | export default class RedisConsumer extends AbstractAsyncRequestProcessor {
7 | startProcessingRequests(): void {
8 | consumeFromRedis(this, process.env.REDIS_HOST, process.env.REDIS_PORT);
9 | log(Severity.INFO, `Redis consumer started for Redis server: ${process.env.REDIS_HOST}:${process.env.REDIS_PORT}`, '');
10 |
11 | }
12 |
13 | getCommunicationMethod(): CommunicationMethod {
14 | return 'redis';
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/requestprocessor/RequestProcessor.ts:
--------------------------------------------------------------------------------
1 | import Microservice from "../microservice/Microservice";
2 | import { CommunicationMethod } from "../remote/messagequeue/sendToRemoteService";
3 |
4 | export interface RequestProcessor {
5 | getCommunicationMethod(): CommunicationMethod
6 | isAsyncProcessor(): boolean;
7 | startProcessingRequests(microservice: Microservice): void
8 | }
9 |
--------------------------------------------------------------------------------
/src/scheduling/defaultRetryIntervals.ts:
--------------------------------------------------------------------------------
1 | const defaultRetryIntervals = [1, 2, 5, 10, 30, 60, 120, 500];
2 |
3 | export default defaultRetryIntervals;
4 |
--------------------------------------------------------------------------------
/src/scheduling/entities/JobScheduling.ts:
--------------------------------------------------------------------------------
1 | import { ArrayMaxSize, IsArray, IsDate, IsInt, IsString, MaxLength } from 'class-validator';
2 | import { Type } from "class-transformer";
3 | import { Lengths } from "../../constants/constants";
4 |
5 | export default class JobScheduling {
6 | @IsString()
7 | @MaxLength(Lengths._256)
8 | serviceFunctionName!: string;
9 |
10 | @IsDate()
11 | @Type(() => Date)
12 | scheduledExecutionTimestamp!: Date;
13 |
14 | @IsInt({ each: true })
15 | @IsArray()
16 | @ArrayMaxSize(25)
17 | retryIntervalsInSecs!: number[];
18 | }
19 |
--------------------------------------------------------------------------------
/src/scheduling/entities/__Backk__CronJobScheduling.ts:
--------------------------------------------------------------------------------
1 | /* eslint-disable @typescript-eslint/class-name-casing,@typescript-eslint/camelcase */
2 | import Entity from "../../decorators/entity/Entity";
3 | import _Id from "../../types/_id/_Id";
4 | import { IsDate, IsString, MaxLength } from "class-validator";
5 | import Unique from "../../decorators/typeproperty/Unique";
6 | import { Lengths } from "../../constants/constants";
7 | import ReadWrite from "../../decorators/typeproperty/access/ReadWrite";
8 |
9 | @Entity()
10 | export default class __Backk__CronJobScheduling extends _Id {
11 | @Unique()
12 | @IsString()
13 | @MaxLength(Lengths._512)
14 | @ReadWrite()
15 | public serviceFunctionName!: string;
16 |
17 | @IsDate()
18 | @ReadWrite()
19 | lastScheduledTimestamp!: Date;
20 |
21 | @IsDate()
22 | @ReadWrite()
23 | nextScheduledTimestamp!: Date;
24 | }
25 |
--------------------------------------------------------------------------------
/src/scheduling/entities/__Backk__JobScheduling.ts:
--------------------------------------------------------------------------------
1 | import Entity from "../../decorators/entity/Entity";
2 | import _Id from "../../types/_id/_Id";
3 | import { IsDate, IsString, MaxLength } from "class-validator";
4 | import { Lengths } from "../../constants/constants";
5 | import NotUnique from "../../decorators/typeproperty/NotUnique";
6 | import ReadWrite from "../../decorators/typeproperty/access/ReadWrite";
7 |
8 | @Entity()
9 | // eslint-disable-next-line @typescript-eslint/camelcase,@typescript-eslint/class-name-casing
10 | export default class __Backk__JobScheduling extends _Id {
11 | @IsString()
12 | @NotUnique()
13 | @MaxLength(Lengths._512)
14 | @ReadWrite()
15 | public serviceFunctionName!: string;
16 |
17 | @IsString()
18 | @NotUnique()
19 | @MaxLength(Lengths._8K)
20 | @ReadWrite()
21 | public serviceFunctionArgument!: string;
22 |
23 | @IsDate()
24 | @ReadWrite()
25 | public scheduledExecutionTimestamp!: Date;
26 |
27 | @IsString()
28 | @NotUnique()
29 | @MaxLength(Lengths._512)
30 | @ReadWrite()
31 | public retryIntervalsInSecs!: string;
32 | }
33 |
--------------------------------------------------------------------------------
/src/services/LivenessCheckService.ts:
--------------------------------------------------------------------------------
1 | import BaseService from "./BaseService";
2 | import { PromiseErrorOr } from "../types/PromiseErrorOr";
3 |
4 | export default abstract class LivenessCheckService extends BaseService {
5 | getServiceType(): string {
6 | return "LivenessCheckService";
7 | }
8 |
9 | abstract isMicroserviceAlive(): PromiseErrorOr;
10 | }
11 |
--------------------------------------------------------------------------------
/src/services/Service.ts:
--------------------------------------------------------------------------------
1 | import { DataStore } from '../datastore/DataStore';
2 |
3 | export interface Service {
4 | getDataStore(): DataStore;
5 | getServiceType(): string;
6 | isUserService(): boolean;
7 | isTenantService(): boolean;
8 | }
9 |
--------------------------------------------------------------------------------
/src/services/crudentity/CrudEntityService.ts:
--------------------------------------------------------------------------------
1 | import BaseService from "../BaseService";
2 |
3 | export default class CrudEntityService extends BaseService {}
4 |
--------------------------------------------------------------------------------
/src/services/crudentity/utils/isCreateFunction.ts:
--------------------------------------------------------------------------------
1 | import serviceFunctionAnnotationContainer from '../../../decorators/service/function/serviceFunctionAnnotationContainer';
2 |
3 | export default function isCreateFunction(ServiceClass: Function, functionName: string) {
4 | return (
5 | functionName.startsWith('create') ||
6 | functionName.startsWith('insert') ||
7 | serviceFunctionAnnotationContainer.isCreateServiceFunction(ServiceClass, functionName)
8 | );
9 | }
10 |
--------------------------------------------------------------------------------
/src/services/crudentity/utils/isDeleteFunction.ts:
--------------------------------------------------------------------------------
1 | import serviceFunctionAnnotationContainer from '../../../decorators/service/function/serviceFunctionAnnotationContainer';
2 |
3 | export default function isDeleteFunction(ServiceClass: Function, functionName: string) {
4 | return (
5 | functionName.startsWith('delete') ||
6 | functionName.startsWith('erase') ||
7 | functionName.startsWith('destroy') ||
8 | serviceFunctionAnnotationContainer.isDeleteServiceFunction(ServiceClass, functionName)
9 | );
10 | }
11 |
--------------------------------------------------------------------------------
/src/services/crudentity/utils/isReadFunction.ts:
--------------------------------------------------------------------------------
1 | export default function isReadFunction(serviceClass: Function, functionName: string) {
2 | return functionName.startsWith('get') ||
3 | functionName.startsWith('find') ||
4 | functionName.startsWith('read') ||
5 | functionName.startsWith('fetch') ||
6 | functionName.startsWith('retrieve') ||
7 | functionName.startsWith('obtain');
8 | }
9 |
--------------------------------------------------------------------------------
/src/services/crudentity/utils/isUpdateFunction.ts:
--------------------------------------------------------------------------------
1 | import serviceFunctionAnnotationContainer
2 | from "../../../decorators/service/function/serviceFunctionAnnotationContainer";
3 |
4 | export default function isUpdateFunction(ServiceClass: Function, functionName: string): boolean {
5 | return (
6 | functionName.startsWith('update') ||
7 | functionName.startsWith('modify') ||
8 | functionName.startsWith('change') ||
9 | functionName.startsWith('patch') ||
10 | !!serviceFunctionAnnotationContainer.getUpdateTypeForServiceFunction(ServiceClass, functionName)
11 | );
12 | }
13 |
--------------------------------------------------------------------------------
/src/services/readinesscheck/DefaultReadinessCheckServiceImpl.ts:
--------------------------------------------------------------------------------
1 | import { PromiseErrorOr } from "../../types/PromiseErrorOr";
2 | import ReadinessCheckService from "./ReadinessCheckService";
3 | import Microservice from "../../microservice/Microservice";
4 | import NullDataStore from "../../datastore/NullDataStore";
5 | import createBackkErrorFromErrorMessageAndStatusCode
6 | from "../../errors/createBackkErrorFromErrorMessageAndStatusCode";
7 | import { HttpStatusCodes } from "../../constants/constants";
8 |
9 | export default class DefaultReadinessCheckServiceImpl extends ReadinessCheckService {
10 | constructor(private readonly microservice: Microservice) {
11 | super({}, new NullDataStore());
12 | }
13 |
14 | isMicroserviceReady(): PromiseErrorOr {
15 | if (this.microservice.getIsTerminationRequested()) {
16 | return Promise.resolve([
17 | null,
18 | createBackkErrorFromErrorMessageAndStatusCode(
19 | 'Service is terminating',
20 | HttpStatusCodes.SERVICE_UNAVAILABLE
21 | )
22 | ]);
23 | }
24 |
25 | return Promise.resolve([null, null]);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/src/services/readinesscheck/ReadinessCheckService.ts:
--------------------------------------------------------------------------------
1 | import BaseService from "../BaseService";
2 | import { PromiseErrorOr } from "../../types/PromiseErrorOr";
3 |
4 | export default abstract class ReadinessCheckService extends BaseService {
5 | getServiceType(): string {
6 | return 'ReadinessCheckService';
7 | }
8 |
9 | abstract isMicroserviceReady(): PromiseErrorOr;
10 | }
11 |
--------------------------------------------------------------------------------
/src/services/startup/StartupCheckService.ts:
--------------------------------------------------------------------------------
1 | import BaseService from "../BaseService";
2 | import { PromiseErrorOr } from "../../types/PromiseErrorOr";
3 |
4 | export default abstract class StartupCheckService extends BaseService {
5 | getServiceType(): string {
6 | return 'StartupCheckService'
7 | }
8 |
9 | static microservice: any | undefined;
10 |
11 | abstract isMicroserviceStarted(): PromiseErrorOr;
12 | }
13 |
--------------------------------------------------------------------------------
/src/services/startup/types/entity/SampleEntity.ts:
--------------------------------------------------------------------------------
1 | import Entity from "../../../../decorators/entity/Entity";
2 | import _Id from "../../../../types/_id/_Id";
3 | import ReadWrite from "../../../../decorators/typeproperty/access/ReadWrite";
4 | import IsAnyString from "../../../../decorators/typeproperty/IsAnyString";
5 | import Private from "../../../../decorators/typeproperty/access/Private";
6 |
7 | @Entity()
8 | export default class SampleEntity extends _Id {
9 | @ReadWrite()
10 | @IsAnyString()
11 | name!: string;
12 |
13 | @ReadWrite()
14 | @IsAnyString()
15 | name2!: string;
16 |
17 | @Private()
18 | @IsAnyString()
19 | name3!: string;
20 | }
21 |
--------------------------------------------------------------------------------
/src/services/tenant/TenantBaseService.ts:
--------------------------------------------------------------------------------
1 | import _Id from "../../types/_id/_Id";
2 | import CrudEntityService from "../crudentity/CrudEntityService";
3 | import AllowForMicroserviceInternalUse
4 | from "../../decorators/service/function/AllowForMicroserviceInternalUse";
5 | import { PromiseErrorOr } from "../../types/PromiseErrorOr";
6 | import { One } from "../../datastore/DataStore";
7 | import { TenantService } from "./TenantService";
8 | import Issuer from "../../types/useraccount/Issuer";
9 |
10 | export default class TenantBaseService extends CrudEntityService implements TenantService {
11 | isTenantService(): boolean {
12 | return true;
13 | }
14 |
15 | @AllowForMicroserviceInternalUse()
16 | // eslint-disable-next-line @typescript-eslint/no-unused-vars
17 | getIdByIssuer(issuer: Issuer): PromiseErrorOr> {
18 | throw new Error('Not implemented. This method must be implemented in the sub class.')
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/services/tenant/TenantService.ts:
--------------------------------------------------------------------------------
1 | import { One } from "../../datastore/DataStore";
2 | import { PromiseErrorOr } from "../../types/PromiseErrorOr";
3 | import _Id from "../../types/_id/_Id";
4 | import { Service } from "../Service";
5 | import Issuer from "../../types/useraccount/Issuer";
6 |
7 | export interface TenantService extends Service {
8 | isUserService(): boolean;
9 | getIdByIssuer(issuer: Issuer): PromiseErrorOr>;
10 | }
11 |
--------------------------------------------------------------------------------
/src/services/useraccount/UserBaseService.ts:
--------------------------------------------------------------------------------
1 | import _Id from "../../types/_id/_Id";
2 | import CrudEntityService from "../crudentity/CrudEntityService";
3 | import AllowForMicroserviceInternalUse from "../../decorators/service/function/AllowForMicroserviceInternalUse";
4 | import { PromiseErrorOr } from "../../types/PromiseErrorOr";
5 | import { One } from "../../datastore/DataStore";
6 | import Subject from "../../types/useraccount/Subject";
7 | import { UserService } from "./UserService";
8 |
9 | export default class UserBaseService extends CrudEntityService implements UserService {
10 | isUserService(): boolean {
11 | return true;
12 | }
13 |
14 | @AllowForMicroserviceInternalUse()
15 | // eslint-disable-next-line @typescript-eslint/no-unused-vars
16 | getIdBySubject(subject: Subject): PromiseErrorOr> {
17 | throw new Error('Not implemented. This method must be implemented in the sub class.')
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/services/useraccount/UserService.ts:
--------------------------------------------------------------------------------
1 | import { One } from '../../datastore/DataStore';
2 | import { PromiseErrorOr } from '../../types/PromiseErrorOr';
3 | import Subject from '../../types/useraccount/Subject';
4 | import _Id from '../../types/_id/_Id';
5 | import { Service } from '../Service';
6 |
7 | export interface UserService extends Service {
8 | isUserService(): boolean;
9 | getIdBySubject(subject: Subject): PromiseErrorOr>;
10 | }
11 |
--------------------------------------------------------------------------------
/src/subscription/Subscription.ts:
--------------------------------------------------------------------------------
1 | import { ServerResponse } from "http";
2 | import { ServerHttp2Stream } from "http2";
3 |
4 | export default class Subscription {
5 | constructor(private readonly response: ServerResponse | ServerHttp2Stream) {
6 | }
7 |
8 | publish(data: any) {
9 | this.response.write(`data: ${JSON.stringify(data)}\n\n`);
10 | }
11 |
12 | getResponse() {
13 | return this.response;
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/types/BackkError.ts:
--------------------------------------------------------------------------------
1 | export type BackkError = {
2 | statusCode: number;
3 | message: string;
4 | errorCode?: string | number;
5 | stackTrace?: string;
6 | };
7 |
--------------------------------------------------------------------------------
/src/types/Captcha.ts:
--------------------------------------------------------------------------------
1 | import { IsAscii, IsString, MaxLength } from "class-validator";
2 | import Transient from "../decorators/typeproperty/Transient";
3 | import { Lengths } from "../constants/constants";
4 | import IsUndefined from "../decorators/typeproperty/IsUndefined";
5 | import CreateOnly from "../decorators/typeproperty/access/CreateOnly";
6 |
7 | export default class Captcha {
8 | @IsUndefined({ groups: ['__backk_update__'] })
9 | @Transient()
10 | @CreateOnly()
11 | @IsString()
12 | @MaxLength(Lengths._512)
13 | @IsAscii()
14 | captchaToken!: string;
15 | }
16 |
--------------------------------------------------------------------------------
/src/types/DbTableVersion.ts:
--------------------------------------------------------------------------------
1 | import Entity from "../decorators/entity/Entity";
2 | import MaxLengthAndMatches from "../decorators/typeproperty/MaxLengthAndMatches";
3 | import _IdAndVersion from "./_id/_IdAndVersion";
4 | import Unique from "../decorators/typeproperty/Unique";
5 | import { IsString } from "class-validator";
6 | import { Lengths } from "../constants/constants";
7 | import ReadWrite from "../decorators/typeproperty/access/ReadWrite";
8 |
9 | @Entity()
10 | export default class DbTableVersion extends _IdAndVersion {
11 | @Unique()
12 | @IsString()
13 | @MaxLengthAndMatches(Lengths._512, /^[a-zA-Z_][a-zA-Z0-9_]*$/)
14 | @ReadWrite()
15 | entityName!: string;
16 | }
17 |
--------------------------------------------------------------------------------
/src/types/EntityCountRequest.ts:
--------------------------------------------------------------------------------
1 | export default class EntityCountRequest {
2 | constructor(public readonly subEntityPath: string) {}
3 | }
4 |
--------------------------------------------------------------------------------
/src/types/ErrorDefinition.ts:
--------------------------------------------------------------------------------
1 | export type ErrorDefinition = {
2 | readonly errorCode: string | number;
3 | readonly message: string;
4 | readonly statusCode: number;
5 | };
6 |
7 | export type ErrorNameToErrorDefinitionMap = { [errorName: string]: ErrorDefinition };
8 |
--------------------------------------------------------------------------------
/src/types/ErrorOr.ts:
--------------------------------------------------------------------------------
1 | import { BackkError } from "./BackkError";
2 |
3 | export type ErrorOr = [T | null | undefined, BackkError | null | undefined];
4 |
--------------------------------------------------------------------------------
/src/types/PromiseErrorOr.ts:
--------------------------------------------------------------------------------
1 | import { BackkError } from './BackkError';
2 |
3 | export type PromiseErrorOr = Promise<[T | null | undefined, BackkError | null | undefined]>;
4 |
5 |
--------------------------------------------------------------------------------
/src/types/RecursivePartial.ts:
--------------------------------------------------------------------------------
1 | export type RecursivePartial = {
2 | [P in keyof T]?: T[P] extends (infer U)[]
3 | ? RecursivePartial[]
4 | : T[P] extends object
5 | ? RecursivePartial
6 | : T[P];
7 | };
8 |
--------------------------------------------------------------------------------
/src/types/Value.ts:
--------------------------------------------------------------------------------
1 | import { MaxLength } from "class-validator";
2 | import IsAnyString from "../decorators/typeproperty/IsAnyString";
3 | import { Lengths } from "../constants/constants";
4 | import Entity from "../decorators/entity/Entity";
5 | import ReadWrite from "../decorators/typeproperty/access/ReadWrite";
6 |
7 | @Entity()
8 | export default class Value {
9 | @MaxLength(Lengths._1K)
10 | @IsAnyString()
11 | @ReadWrite()
12 | value!: string;
13 | }
14 |
--------------------------------------------------------------------------------
/src/types/Version.ts:
--------------------------------------------------------------------------------
1 | import IsUndefined from "../decorators/typeproperty/IsUndefined";
2 | import { Max, Min } from "class-validator";
3 | import IsBigInt from "../decorators/typeproperty/IsBigInt";
4 | import NotUnique from "../decorators/typeproperty/NotUnique";
5 | import ReadUpdate from "../decorators/typeproperty/access/ReadUpdate";
6 |
7 | export default class Version {
8 | @IsUndefined({groups: ['__backk_create__']})
9 | @IsBigInt({ groups: ['__backk_none__'] })
10 | @NotUnique()
11 | @Min(-1)
12 | @Max(Number.MAX_SAFE_INTEGER)
13 | @ReadUpdate()
14 | version!: number;
15 | }
16 |
--------------------------------------------------------------------------------
/src/types/_id/_Id.ts:
--------------------------------------------------------------------------------
1 | import MaxLengthAndMatches from '../../decorators/typeproperty/MaxLengthAndMatches';
2 | import { BackkEntity } from '../entities/BackkEntity';
3 | import IsUndefined from '../../decorators/typeproperty/IsUndefined';
4 | import IsStringOrObjectId from '../../decorators/typeproperty/IsStringOrObjectId';
5 | import ReadUpdate from "../../decorators/typeproperty/access/ReadUpdate";
6 |
7 | // eslint-disable-next-line @typescript-eslint/class-name-casing
8 | export default class _Id implements BackkEntity {
9 | @IsUndefined({ groups: ['__backk_create__'] })
10 | @IsStringOrObjectId({ groups: ['__backk_update__'] })
11 | @MaxLengthAndMatches(24, /^[a-f\d]{1,24}$/, { groups: ['__backk_update__'] })
12 | @ReadUpdate()
13 | _id!: string;
14 | }
15 |
--------------------------------------------------------------------------------
/src/types/_id/_IdAndCaptcha.ts:
--------------------------------------------------------------------------------
1 | import { IsAscii, IsString, MaxLength } from 'class-validator';
2 | import _Id from './_Id';
3 | import Transient from '../../decorators/typeproperty/Transient';
4 | import { Lengths } from '../../constants/constants';
5 | import IsUndefined from "../../decorators/typeproperty/IsUndefined";
6 | import CreateOnly from "../../decorators/typeproperty/access/CreateOnly";
7 |
8 | // eslint-disable-next-line @typescript-eslint/class-name-casing
9 | export default class _IdAndCaptcha extends _Id {
10 | @IsUndefined({ groups: ['__backk_update__'] })
11 | @CreateOnly()
12 | @Transient()
13 | @IsString()
14 | @MaxLength(Lengths._512)
15 | @IsAscii()
16 | captchaToken!: string;
17 | }
18 |
--------------------------------------------------------------------------------
/src/types/_id/_IdAndCaptchaAndCreatedAtTimestamp.ts:
--------------------------------------------------------------------------------
1 | import _IdAndCaptcha from './_IdAndCaptcha';
2 | import { IsDate } from 'class-validator';
3 | import IsUndefined from '../../decorators/typeproperty/IsUndefined';
4 | import ReadOnly from "../../decorators/typeproperty/access/ReadOnly";
5 |
6 | // eslint-disable-next-line @typescript-eslint/class-value-casing
7 | export default class _IdAndCaptchaAndCreatedAtTimestamp extends _IdAndCaptcha {
8 | @IsUndefined({ groups: ['__backk_create__', '__backk_update__'] })
9 | @IsDate({ groups: ['__backk_none__'] })
10 | @ReadOnly()
11 | createdAtTimestamp!: Date;
12 | }
13 |
--------------------------------------------------------------------------------
/src/types/_id/_IdAndCaptchaAndCreatedAtTimestampAndLastModifiedTimestamp.ts:
--------------------------------------------------------------------------------
1 | import _IdAndCaptchaAndCreatedAtTimestamp from "./_IdAndCaptchaAndCreatedAtTimestamp";
2 | import IsUndefined from "../../decorators/typeproperty/IsUndefined";
3 | import { IsDate } from "class-validator";
4 | import ReadOnly from "../../decorators/typeproperty/access/ReadOnly";
5 |
6 | // eslint-disable-next-line @typescript-eslint/class-name-casing
7 | export default class _IdAndCaptchaAndCreatedAtTimestampAndLastModifiedTimestamp extends _IdAndCaptchaAndCreatedAtTimestamp {
8 | @IsUndefined({ groups: ['__backk_create__', '__backk_update__'] })
9 | @IsDate({ groups: ['__backk_none__'] })
10 | @ReadOnly()
11 | lastModifiedTimestamp!: Date;
12 | }
13 |
--------------------------------------------------------------------------------
/src/types/_id/_IdAndCaptchaAndLastModifiedTimestamp.ts:
--------------------------------------------------------------------------------
1 | import _IdAndCaptcha from './_IdAndCaptcha';
2 | import { IsDate } from 'class-validator';
3 | import IsUndefined from '../../decorators/typeproperty/IsUndefined';
4 | import ReadOnly from "../../decorators/typeproperty/access/ReadOnly";
5 |
6 | // eslint-disable-next-line @typescript-eslint/class-value-casing
7 | export default class _IdAndCaptchaAndLastModifiedTimestamp extends _IdAndCaptcha {
8 | @IsUndefined({ groups: ['__backk_create__', '__backk_update__'] })
9 | @IsDate({ groups: ['__backk_none__'] })
10 | @ReadOnly()
11 | lastModifiedTimestamp!: Date;
12 | }
13 |
--------------------------------------------------------------------------------
/src/types/_id/_IdAndCaptchaAndVersion.ts:
--------------------------------------------------------------------------------
1 | import _IdAndCaptcha from './_IdAndCaptcha';
2 | import { Max, Min } from 'class-validator';
3 | import IsUndefined from '../../decorators/typeproperty/IsUndefined';
4 | import IsBigInt from '../../decorators/typeproperty/IsBigInt';
5 | import NotUnique from "../../decorators/typeproperty/NotUnique";
6 | import ReadUpdate from "../../decorators/typeproperty/access/ReadUpdate";
7 |
8 | // eslint-disable-next-line @typescript-eslint/class-value-casing
9 | export default class _IdAndCaptchaAndVersion extends _IdAndCaptcha {
10 | @IsUndefined({ groups: ['__backk_create__'] })
11 | @IsBigInt({ groups: ['__backk_none__'] })
12 | @Min(-1)
13 | @NotUnique()
14 | @Max(Number.MAX_SAFE_INTEGER)
15 | @ReadUpdate()
16 | version!: number;
17 | }
18 |
--------------------------------------------------------------------------------
/src/types/_id/_IdAndCaptchaAndVersionAndCreatedAtTimestamp.ts:
--------------------------------------------------------------------------------
1 | import _IdAndCaptchaAndVersion from './_IdAndCaptchaAndVersion';
2 | import { IsDate } from 'class-validator';
3 | import IsUndefined from '../../decorators/typeproperty/IsUndefined';
4 | import ReadOnly from "../../decorators/typeproperty/access/ReadOnly";
5 |
6 | // eslint-disable-next-line @typescript-eslint/class-value-casing
7 | export default class _IdAndCaptchaAndVersionAndCreatedAtTimestamp extends _IdAndCaptchaAndVersion {
8 | @IsUndefined({ groups: ['__backk_create__', '__backk_update__'] })
9 | @IsDate({ groups: ['__backk_none__'] })
10 | @ReadOnly()
11 | createdAtTimestamp!: Date;
12 | }
13 |
--------------------------------------------------------------------------------
/src/types/_id/_IdAndCaptchaAndVersionAndCreatedAtTimestampAndLastModifiedTimestamp.ts:
--------------------------------------------------------------------------------
1 | import _IdAndCaptchaAndVersionAndCreatedAtTimestamp from './_IdAndCaptchaAndVersionAndCreatedAtTimestamp';
2 | import IsUndefined from '../../decorators/typeproperty/IsUndefined';
3 | import { IsDate } from 'class-validator';
4 | import ReadOnly from "../../decorators/typeproperty/access/ReadOnly";
5 |
6 | // eslint-disable-next-line @typescript-eslint/class-value-casing
7 | export default class _IdAndCaptchaAndVersionAndCreatedAtTimestampAndLastModifiedTimestamp extends _IdAndCaptchaAndVersionAndCreatedAtTimestamp {
8 | @IsUndefined({ groups: ['__backk_create__', '__backk_update__'] })
9 | @IsDate({ groups: ['__backk_none__'] })
10 | @ReadOnly()
11 | lastModifiedTimestamp!: Date;
12 | }
13 |
--------------------------------------------------------------------------------
/src/types/_id/_IdAndCaptchaAndVersionAndLastModifiedTimestamp.ts:
--------------------------------------------------------------------------------
1 | import _IdAndCaptchaAndVersion from './_IdAndCaptchaAndVersion';
2 | import { IsDate } from 'class-validator';
3 | import IsUndefined from '../../decorators/typeproperty/IsUndefined';
4 | import ReadOnly from "../../decorators/typeproperty/access/ReadOnly";
5 |
6 | // eslint-disable-next-line @typescript-eslint/class-value-casing
7 | export default class _IdAndCaptchaAndVersionAndLastModifiedTimestamp extends _IdAndCaptchaAndVersion {
8 | @IsUndefined({ groups: ['__backk_create__', '__backk_update__'] })
9 | @IsDate({ groups: ['__backk_none__'] })
10 | @ReadOnly()
11 | lastModifiedTimestamp!: Date;
12 | }
13 |
--------------------------------------------------------------------------------
/src/types/_id/_IdAndCreatedAtTimestamp.ts:
--------------------------------------------------------------------------------
1 | import _Id from './_Id';
2 | import { BackkEntity } from '../entities/BackkEntity';
3 | import { IsDate } from 'class-validator';
4 | import IsUndefined from '../../decorators/typeproperty/IsUndefined';
5 | import ReadOnly from "../../decorators/typeproperty/access/ReadOnly";
6 |
7 | // eslint-disable-next-line @typescript-eslint/class-value-casing
8 | export default class _IdAndCreatedAtTimestamp extends _Id implements BackkEntity {
9 | @IsUndefined({ groups: ['__backk_create__', '__backk_update__'] })
10 | @IsDate({ groups: ['__backk_none__'] })
11 | @ReadOnly()
12 | createdAtTimestamp!: Date;
13 | }
14 |
--------------------------------------------------------------------------------
/src/types/_id/_IdAndCreatedAtTimestampAndLastModifiedTimestamp.ts:
--------------------------------------------------------------------------------
1 | import _IdAndCreatedAtTimestamp from './_IdAndCreatedAtTimestamp';
2 | import { BackkEntity } from '../entities/BackkEntity';
3 | import IsUndefined from '../../decorators/typeproperty/IsUndefined';
4 | import { IsDate } from 'class-validator';
5 | import ReadOnly from "../../decorators/typeproperty/access/ReadOnly";
6 |
7 | // eslint-disable-next-line @typescript-eslint/class-value-casing
8 | export default class _IdAndCreatedAtTimestampAndLastModifiedTimestamp extends _IdAndCreatedAtTimestamp
9 | implements BackkEntity {
10 | @IsUndefined({ groups: ['__backk_create__', '__backk_update__'] })
11 | @IsDate({ groups: ['__backk_none__'] })
12 | @ReadOnly()
13 | lastModifiedTimestamp!: Date;
14 | }
15 |
--------------------------------------------------------------------------------
/src/types/_id/_IdAndCreatedAtTimestampAndLastModifiedTimestampAndUserAccountId.ts:
--------------------------------------------------------------------------------
1 | import _IdAndCreatedAtTimestampAndLastModifiedTimestamp from './_IdAndCreatedAtTimestampAndLastModifiedTimestamp';
2 | import IsStringOrObjectId from '../../decorators/typeproperty/IsStringOrObjectId';
3 | import MaxLengthAndMatches from '../../decorators/typeproperty/MaxLengthAndMatches';
4 | import NotUnique from "../../decorators/typeproperty/NotUnique";
5 | import ReadWrite from "../../decorators/typeproperty/access/ReadWrite";
6 |
7 | // eslint-disable-next-line @typescript-eslint/class-value-casing
8 | export default class _IdAndCreatedAtTimestampAndLastModifiedTimestampAndUserAccountId extends _IdAndCreatedAtTimestampAndLastModifiedTimestamp {
9 | @IsStringOrObjectId()
10 | @MaxLengthAndMatches(24, /^[a-f\d]{1,24}$/)
11 | @NotUnique()
12 | @ReadWrite()
13 | userAccountId!: string;
14 | }
15 |
--------------------------------------------------------------------------------
/src/types/_id/_IdAndCreatedAtTimestampAndUserAccountId.ts:
--------------------------------------------------------------------------------
1 | import { BackkEntity } from '../entities/BackkEntity';
2 | import IsStringOrObjectId from '../../decorators/typeproperty/IsStringOrObjectId';
3 | import MaxLengthAndMatches from '../../decorators/typeproperty/MaxLengthAndMatches';
4 | import _IdAndCreatedAtTimestamp from './_IdAndCreatedAtTimestamp';
5 | import NotUnique from "../../decorators/typeproperty/NotUnique";
6 | import ReadWrite from "../../decorators/typeproperty/access/ReadWrite";
7 |
8 | // eslint-disable-next-line @typescript-eslint/class-value-casing
9 | export default class _IdAndCreatedAtTimestampAndUserAccountId extends _IdAndCreatedAtTimestamp
10 | implements BackkEntity {
11 | @IsStringOrObjectId()
12 | @MaxLengthAndMatches(24, /^[a-f\d]{1,24}$/)
13 | @NotUnique()
14 | @ReadWrite()
15 | userAccountId!: string;
16 | }
17 |
--------------------------------------------------------------------------------
/src/types/_id/_IdAndLastModifiedTimestamp.ts:
--------------------------------------------------------------------------------
1 | import _Id from './_Id';
2 | import { BackkEntity } from '../entities/BackkEntity';
3 | import IsUndefined from '../../decorators/typeproperty/IsUndefined';
4 | import { IsDate } from 'class-validator';
5 | import ReadOnly from "../../decorators/typeproperty/access/ReadOnly";
6 |
7 | // eslint-disable-next-line @typescript-eslint/class-value-casing
8 | export default class _IdAndLastModifiedTimestamp extends _Id implements BackkEntity {
9 | @IsUndefined({ groups: ['__backk_create__', '__backk_update__'] })
10 | @IsDate({ groups: ['__backk_none__'] })
11 | @ReadOnly()
12 | lastModifiedTimestamp!: Date;
13 | }
14 |
--------------------------------------------------------------------------------
/src/types/_id/_IdAndLastModifiedTimestampAndUserAccountId.ts:
--------------------------------------------------------------------------------
1 | import _IdAndLastModifiedTimestamp from './_IdAndLastModifiedTimestamp';
2 | import IsStringOrObjectId from '../../decorators/typeproperty/IsStringOrObjectId';
3 | import MaxLengthAndMatches from '../../decorators/typeproperty/MaxLengthAndMatches';
4 | import NotUnique from "../../decorators/typeproperty/NotUnique";
5 | import ReadWrite from "../../decorators/typeproperty/access/ReadWrite";
6 |
7 | // eslint-disable-next-line @typescript-eslint/class-value-casing
8 | export default class _IdAndLastModifiedTimestampAndUserAccountId extends _IdAndLastModifiedTimestamp {
9 | @IsStringOrObjectId()
10 | @MaxLengthAndMatches(24, /^[a-f\d]{1,24}$/)
11 | @NotUnique()
12 | @ReadWrite()
13 | userAccountId!: string;
14 | }
15 |
--------------------------------------------------------------------------------
/src/types/_id/_IdAndUserAccountId.ts:
--------------------------------------------------------------------------------
1 | import _Id from './_Id';
2 | import MaxLengthAndMatches from '../../decorators/typeproperty/MaxLengthAndMatches';
3 | import { BackkEntity } from '../entities/BackkEntity';
4 | import IsStringOrObjectId from '../../decorators/typeproperty/IsStringOrObjectId';
5 | import NotUnique from "../../decorators/typeproperty/NotUnique";
6 | import ReadWrite from "../../decorators/typeproperty/access/ReadWrite";
7 |
8 | // eslint-disable-next-line @typescript-eslint/class-value-casing
9 | export default class _IdAndUserAccountId extends _Id implements BackkEntity {
10 | @IsStringOrObjectId()
11 | @MaxLengthAndMatches(24, /^[a-f\d]{1,24}$/)
12 | @NotUnique()
13 | @ReadWrite()
14 | userAccountId!: string;
15 | }
16 |
--------------------------------------------------------------------------------
/src/types/_id/_IdAndVersion.ts:
--------------------------------------------------------------------------------
1 | import _Id from "./_Id";
2 | import { Max, Min } from "class-validator";
3 | import { BackkEntity } from "../entities/BackkEntity";
4 | import IsUndefined from "../../decorators/typeproperty/IsUndefined";
5 | import IsBigInt from "../../decorators/typeproperty/IsBigInt";
6 | import NotUnique from "../../decorators/typeproperty/NotUnique";
7 | import ReadUpdate from "../../decorators/typeproperty/access/ReadUpdate";
8 |
9 | // eslint-disable-next-line @typescript-eslint/class-value-casing
10 | export default class _IdAndVersion extends _Id implements BackkEntity {
11 | @IsUndefined({ groups: ['__backk_create__'] })
12 | @IsBigInt({ groups: ['__backk_none__'] })
13 | @NotUnique()
14 | @Min(-1)
15 | @Max(Number.MAX_SAFE_INTEGER)
16 | @ReadUpdate()
17 | version!: number;
18 | }
19 |
--------------------------------------------------------------------------------
/src/types/_id/_IdAndVersionAndCreatedAtTimestamp.ts:
--------------------------------------------------------------------------------
1 | import _IdAndVersion from './_IdAndVersion';
2 | import { BackkEntity } from '../entities/BackkEntity';
3 | import { IsDate } from 'class-validator';
4 | import IsUndefined from '../../decorators/typeproperty/IsUndefined';
5 | import ReadOnly from "../../decorators/typeproperty/access/ReadOnly";
6 |
7 | // eslint-disable-next-line @typescript-eslint/class-value-casing
8 | export default class _IdAndVersionAndCreatedAtTimestamp extends _IdAndVersion implements BackkEntity {
9 | @IsUndefined({ groups: ['__backk_create__', '__backk_update__'] })
10 | @IsDate({ groups: ['__backk_none__'] })
11 | @ReadOnly()
12 | createdAtTimestamp!: Date;
13 | }
14 |
--------------------------------------------------------------------------------
/src/types/_id/_IdAndVersionAndCreatedAtTimestampAndLastModifiedTimestamp.ts:
--------------------------------------------------------------------------------
1 | import _IdAndVersionAndCreatedAtTimestamp from './_IdAndVersionAndCreatedAtTimestamp';
2 | import { BackkEntity } from '../entities/BackkEntity';
3 | import IsUndefined from '../../decorators/typeproperty/IsUndefined';
4 | import { IsDate } from 'class-validator';
5 | import ReadOnly from "../../decorators/typeproperty/access/ReadOnly";
6 |
7 | // eslint-disable-next-line @typescript-eslint/class-value-casing
8 | export default class _IdAndVersionAndCreatedAtTimestampAndLastModifiedTimestamp
9 | extends _IdAndVersionAndCreatedAtTimestamp
10 | implements BackkEntity {
11 | @IsUndefined({ groups: ['__backk_create__', '__backk_update__'] })
12 | @IsDate({ groups: ['__backk_none__'] })
13 | @ReadOnly()
14 | lastModifiedTimestamp!: Date;
15 | }
16 |
--------------------------------------------------------------------------------
/src/types/_id/_IdAndVersionAndCreatedAtTimestampAndLastModifiedTimestampAndUserAccountId.ts:
--------------------------------------------------------------------------------
1 | import _IdAndVersionAndCreatedAtTimestampAndLastModifiedTimestamp from './_IdAndVersionAndCreatedAtTimestampAndLastModifiedTimestamp';
2 | import IsStringOrObjectId from '../../decorators/typeproperty/IsStringOrObjectId';
3 | import MaxLengthAndMatches from '../../decorators/typeproperty/MaxLengthAndMatches';
4 | import NotUnique from "../../decorators/typeproperty/NotUnique";
5 | import ReadWrite from "../../decorators/typeproperty/access/ReadWrite";
6 |
7 | // eslint-disable-next-line @typescript-eslint/class-value-casing
8 | export default class _IdAndVersionAndCreatedAtTimestampAndLastModifiedTimestampAndUserAccountId extends _IdAndVersionAndCreatedAtTimestampAndLastModifiedTimestamp {
9 | @IsStringOrObjectId()
10 | @MaxLengthAndMatches(24, /^[a-f\d]{1,24}$/)
11 | @NotUnique()
12 | @ReadWrite()
13 | userAccountId!: string;
14 | }
15 |
--------------------------------------------------------------------------------
/src/types/_id/_IdAndVersionAndCreatedAtTimestampAndUserAccountId.ts:
--------------------------------------------------------------------------------
1 | import _IdAndVersionAndCreatedAtTimestamp from './_IdAndVersionAndCreatedAtTimestamp';
2 | import IsStringOrObjectId from '../../decorators/typeproperty/IsStringOrObjectId';
3 | import MaxLengthAndMatches from '../../decorators/typeproperty/MaxLengthAndMatches';
4 | import NotUnique from "../../decorators/typeproperty/NotUnique";
5 | import ReadWrite from "../../decorators/typeproperty/access/ReadWrite";
6 |
7 | // eslint-disable-next-line @typescript-eslint/class-value-casing
8 | export default class _IdAndVersionAndCreatedAtTimestampAndUserAccountId extends _IdAndVersionAndCreatedAtTimestamp {
9 | @IsStringOrObjectId()
10 | @MaxLengthAndMatches(24, /^[a-f\d]{1,24}$/)
11 | @NotUnique()
12 | @ReadWrite()
13 | userAccountId!: string;
14 | }
15 |
--------------------------------------------------------------------------------
/src/types/_id/_IdAndVersionAndLastModifiedTimestamp.ts:
--------------------------------------------------------------------------------
1 | import _IdAndVersion from './_IdAndVersion';
2 | import { BackkEntity } from '../entities/BackkEntity';
3 | import IsUndefined from '../../decorators/typeproperty/IsUndefined';
4 | import { IsDate } from 'class-validator';
5 | import ReadOnly from "../../decorators/typeproperty/access/ReadOnly";
6 |
7 | // eslint-disable-next-line @typescript-eslint/class-value-casing
8 | export default class _IdAndVersionAndLastModifiedTimestamp extends _IdAndVersion implements BackkEntity {
9 | @IsUndefined({ groups: ['__backk_create__', '__backk_update__'] })
10 | @IsDate({ groups: ['__backk_none__'] })
11 | @ReadOnly()
12 | lastModifiedTimestamp!: Date;
13 | }
14 |
--------------------------------------------------------------------------------
/src/types/_id/_IdAndVersionAndLastModifiedTimestampAndUserAccountId.ts:
--------------------------------------------------------------------------------
1 | import _IdAndVersionAndLastModifiedTimestamp from './_IdAndVersionAndLastModifiedTimestamp';
2 | import IsStringOrObjectId from '../../decorators/typeproperty/IsStringOrObjectId';
3 | import MaxLengthAndMatches from '../../decorators/typeproperty/MaxLengthAndMatches';
4 | import NotUnique from "../../decorators/typeproperty/NotUnique";
5 | import ReadWrite from "../../decorators/typeproperty/access/ReadWrite";
6 |
7 | // eslint-disable-next-line @typescript-eslint/class-value-casing
8 | export default class _IdAndVersionAndLastModifiedTimestampAndUserAccountId extends _IdAndVersionAndLastModifiedTimestamp {
9 | @IsStringOrObjectId()
10 | @MaxLengthAndMatches(24, /^[a-f\d]{1,24}$/)
11 | @NotUnique()
12 | @ReadWrite()
13 | userAccountId!: string;
14 | }
15 |
--------------------------------------------------------------------------------
/src/types/_id/_IdAndVersionAndUserAccountId.ts:
--------------------------------------------------------------------------------
1 | import IsStringOrObjectId from '../../decorators/typeproperty/IsStringOrObjectId';
2 | import MaxLengthAndMatches from '../../decorators/typeproperty/MaxLengthAndMatches';
3 | import _IdAndVersion from './_IdAndVersion';
4 | import NotUnique from "../../decorators/typeproperty/NotUnique";
5 | import ReadWrite from "../../decorators/typeproperty/access/ReadWrite";
6 |
7 | // eslint-disable-next-line @typescript-eslint/class-value-casing
8 | export default class _IdAndVersionAndUserAccountId extends _IdAndVersion {
9 | @IsStringOrObjectId()
10 | @MaxLengthAndMatches(24, /^[a-f\d]{1,24}$/)
11 | @NotUnique()
12 | @ReadWrite()
13 | userAccountId!: string;
14 | }
15 |
--------------------------------------------------------------------------------
/src/types/entities/BackkEntity.ts:
--------------------------------------------------------------------------------
1 | export interface BackkEntity {
2 | _id: string;
3 | id?: string;
4 | version?: number;
5 | lastModifiedTimestamp?: Date;
6 | [key: string]: any;
7 | }
8 |
--------------------------------------------------------------------------------
/src/types/entities/SubEntity.ts:
--------------------------------------------------------------------------------
1 | export interface SubEntity {
2 | _id?: string;
3 | id?: string;
4 | }
5 |
--------------------------------------------------------------------------------
/src/types/id/Id.ts:
--------------------------------------------------------------------------------
1 | import MaxLengthAndMatches from "../../decorators/typeproperty/MaxLengthAndMatches";
2 | import IsStringOrObjectId from "../../decorators/typeproperty/IsStringOrObjectId";
3 | import ReadWrite from "../../decorators/typeproperty/access/ReadWrite";
4 | import NotUnique from "../../decorators/typeproperty/NotUnique";
5 |
6 | export default class Id {
7 | @IsStringOrObjectId()
8 | @MaxLengthAndMatches(24, /^[a-f\d]{1,24}$/)
9 | @NotUnique()
10 | @ReadWrite()
11 | id!: string;
12 | }
13 |
--------------------------------------------------------------------------------
/src/types/postqueryoperations/CurrentPageToken.ts:
--------------------------------------------------------------------------------
1 | import { IsAlphanumeric, IsOptional, IsString, MaxLength } from "class-validator";
2 | import MaxLengthAndMatches from "../../decorators/typeproperty/MaxLengthAndMatches";
3 | import { Lengths } from "../../constants/constants";
4 |
5 | export default class CurrentPageToken {
6 | constructor(subEntityPath: string, currentPageToken: string) {
7 | this.subEntityPath = subEntityPath;
8 | this.currentPageToken = currentPageToken;
9 | }
10 |
11 | @IsOptional()
12 | @MaxLengthAndMatches(Lengths._2K, /^([a-zA-Z_][a-zA-Z0-9_.]*|\*|)$/)
13 | @IsString()
14 | subEntityPath?: string = '';
15 |
16 | @IsString()
17 | @IsAlphanumeric()
18 | @MaxLength(64)
19 | currentPageToken!: string;
20 | }
21 |
--------------------------------------------------------------------------------
/src/types/postqueryoperations/DefaultPagination.ts:
--------------------------------------------------------------------------------
1 | import { PostQueryOperations } from "./PostQueryOperations";
2 | import { ArrayMaxSize, ArrayMinSize, IsArray, IsInstance, IsOptional, ValidateNested } from "class-validator";
3 | import Pagination from "./Pagination";
4 | import { Values } from "../../constants/constants";
5 | import { Type } from "class-transformer";
6 |
7 | export default class DefaultPagination implements PostQueryOperations {
8 | @IsOptional()
9 | @IsInstance(Pagination, { each: true })
10 | @ValidateNested({ each: true })
11 | @Type(() => Pagination)
12 | @IsArray()
13 | @ArrayMinSize(0)
14 | @ArrayMaxSize(Values._25)
15 | paginations: Pagination[] = [new Pagination('*', 1, Values._50)];
16 | }
17 |
--------------------------------------------------------------------------------
/src/types/postqueryoperations/DefaultSorting.ts:
--------------------------------------------------------------------------------
1 | import { PostQueryOperations } from "./PostQueryOperations";
2 | import SortBy from "./SortBy";
3 | import { ArrayMaxSize, ArrayMinSize, IsArray, IsInstance, IsOptional, ValidateNested } from "class-validator";
4 | import { Values } from "../../constants/constants";
5 | import { Type } from "class-transformer";
6 |
7 | export default class DefaultSorting implements PostQueryOperations {
8 | @IsOptional()
9 | @IsInstance(SortBy, { each: true })
10 | @ValidateNested({ each: true })
11 | @Type(() => SortBy)
12 | @IsArray()
13 | @ArrayMinSize(0)
14 | @ArrayMaxSize(Values._25)
15 | sortBys: SortBy[] = [new SortBy('*', '_id', 'ASC'), new SortBy('*', 'id', 'ASC')];
16 | }
17 |
--------------------------------------------------------------------------------
/src/types/postqueryoperations/DefaultSortingAndPagination.ts:
--------------------------------------------------------------------------------
1 | import { PostQueryOperations } from "./PostQueryOperations";
2 | import SortBy from "./SortBy";
3 | import { ArrayMaxSize, ArrayMinSize, IsArray, IsInstance, IsOptional, ValidateNested } from "class-validator";
4 | import Pagination from "./Pagination";
5 | import { Values } from "../../constants/constants";
6 | import { Type } from "class-transformer";
7 |
8 | export default class DefaultSortingAndPagination implements PostQueryOperations {
9 | @IsOptional()
10 | @IsInstance(SortBy, { each: true })
11 | @ValidateNested({ each: true })
12 | @Type(() => SortBy)
13 | @IsArray()
14 | @ArrayMinSize(0)
15 | @ArrayMaxSize(Values._25)
16 | sortBys: SortBy[] = [new SortBy('*', '_id', 'ASC'), new SortBy('*', 'id', 'ASC')];
17 |
18 | @IsOptional()
19 | @IsInstance(Pagination, { each: true })
20 | @ValidateNested({ each: true })
21 | @Type(() => Pagination)
22 | @IsArray()
23 | @ArrayMinSize(0)
24 | @ArrayMaxSize(Values._25)
25 | paginations: Pagination[] = [new Pagination('*', 1, Values._50)];
26 | }
27 |
--------------------------------------------------------------------------------
/src/types/postqueryoperations/Pagination.ts:
--------------------------------------------------------------------------------
1 | import MaxLengthAndMatches from "../../decorators/typeproperty/MaxLengthAndMatches";
2 | import { IsInt, IsOptional, IsString, Max, Min } from "class-validator";
3 | import { Lengths } from "../../constants/constants";
4 |
5 | export default class Pagination {
6 | constructor(subEntityPath: string, pageNumber: number, pageSize: number) {
7 | this.subEntityPath = subEntityPath;
8 | this.pageNumber = pageNumber;
9 | this.pageSize = pageSize;
10 | }
11 |
12 | @IsOptional()
13 | @MaxLengthAndMatches(Lengths._2K, /^([a-zA-Z_][a-zA-Z0-9_.]*|\*|)$/)
14 | @IsString()
15 | subEntityPath?: string = '';
16 |
17 | @IsInt()
18 | @Min(1)
19 | @Max(100)
20 | pageNumber!: number;
21 |
22 | @IsInt()
23 | @Min(1)
24 | @Max(100)
25 | pageSize!: number;
26 | }
27 |
--------------------------------------------------------------------------------
/src/types/postqueryoperations/PostQueryOperations.ts:
--------------------------------------------------------------------------------
1 | import { Projection } from "./Projection";
2 | import { SortBys } from "./SortBys";
3 | import Pagination from "./Pagination";
4 | import CurrentPageToken from "./CurrentPageToken";
5 |
6 | export interface PostQueryOperations extends Projection, SortBys {
7 | paginations?: Pagination[];
8 | currentPageTokens?: CurrentPageToken[];
9 | }
10 |
--------------------------------------------------------------------------------
/src/types/postqueryoperations/Projection.ts:
--------------------------------------------------------------------------------
1 | export interface Projection {
2 | includeResponseFields?: string[];
3 | excludeResponseFields?: string[];
4 | }
5 |
--------------------------------------------------------------------------------
/src/types/postqueryoperations/SortBys.ts:
--------------------------------------------------------------------------------
1 | import SortBy from "./SortBy";
2 |
3 | export interface SortBys {
4 | sortBys?: SortBy[];
5 | }
6 |
--------------------------------------------------------------------------------
/src/types/postqueryoperations/_IdAndDefaultPostQueryOperations.ts:
--------------------------------------------------------------------------------
1 | import DefaultPostQueryOperationsImpl from "./DefaultPostQueryOperationsImpl";
2 | import IsStringOrObjectId from "../../decorators/typeproperty/IsStringOrObjectId";
3 | import MaxLengthAndMatches from "../../decorators/typeproperty/MaxLengthAndMatches";
4 | import { Values } from "../../constants/constants";
5 |
6 | // eslint-disable-next-line @typescript-eslint/class-name-casing
7 | export default class _IdAndDefaultPostQueryOperations extends DefaultPostQueryOperationsImpl {
8 | @IsStringOrObjectId()
9 | @MaxLengthAndMatches(Values._24, /^[a-f\d]{1,24}$/)
10 | _id!: string;
11 | }
12 |
--------------------------------------------------------------------------------
/src/types/postqueryoperations/_IdsAndDefaultPostQueryOperations.ts:
--------------------------------------------------------------------------------
1 | import { ArrayMaxSize, ArrayMinSize, IsArray } from "class-validator";
2 | import DefaultPostQueryOperationsImpl from "./DefaultPostQueryOperationsImpl";
3 | import IsStringOrObjectId from "../../decorators/typeproperty/IsStringOrObjectId";
4 | import MaxLengthAndMatches from "../../decorators/typeproperty/MaxLengthAndMatches";
5 |
6 | // eslint-disable-next-line @typescript-eslint/class-value-casing
7 | export default class _IdsAndDefaultPostQueryOperations extends DefaultPostQueryOperationsImpl {
8 | @IsStringOrObjectId({ each: true })
9 | @MaxLengthAndMatches(24, /^[a-f\d]{1,24}$/, { each: true })
10 | @IsArray()
11 | @ArrayMinSize(1)
12 | @ArrayMaxSize(1000)
13 | _ids!: string[];
14 | }
15 |
--------------------------------------------------------------------------------
/src/types/useraccount/BaseUserAccount.ts:
--------------------------------------------------------------------------------
1 | import _IdAndCaptcha from "../_id/_IdAndCaptcha";
2 | import Unique from "../../decorators/typeproperty/Unique";
3 | import { IsString, MaxLength } from "class-validator";
4 | import IsUndefined from "../../decorators/typeproperty/IsUndefined";
5 | import IsSubject from "../../decorators/typeproperty/IsSubject";
6 | import CreateOnly from "../../decorators/typeproperty/access/CreateOnly";
7 |
8 | export default class BaseUserAccount extends _IdAndCaptcha {
9 | @IsUndefined({ groups: ['__backk_update__'] })
10 | @Unique()
11 | @IsString()
12 | @MaxLength(255)
13 | @IsSubject()
14 | @CreateOnly()
15 | subject!: string;
16 | }
17 |
--------------------------------------------------------------------------------
/src/types/useraccount/Issuer.ts:
--------------------------------------------------------------------------------
1 | import { IsString, IsUrl, MaxLength } from "class-validator";
2 | import Unique from "../../decorators/typeproperty/Unique";
3 | import CreateOnly from "../../decorators/typeproperty/access/CreateOnly";
4 |
5 | export default class Issuer {
6 | @Unique()
7 | @IsString()
8 | @IsUrl()
9 | @MaxLength(255)
10 | @CreateOnly()
11 | issuer!: string;
12 | }
13 |
--------------------------------------------------------------------------------
/src/types/useraccount/Subject.ts:
--------------------------------------------------------------------------------
1 | import { IsString, MaxLength } from "class-validator";
2 | import Unique from "../../decorators/typeproperty/Unique";
3 | import IsSubject from "../../decorators/typeproperty/IsSubject";
4 | import CreateOnly from "../../decorators/typeproperty/access/CreateOnly";
5 |
6 | export default class Subject {
7 | @Unique()
8 | @IsString()
9 | @IsSubject()
10 | @MaxLength(255)
11 | @CreateOnly()
12 | subject!: string;
13 | }
14 |
--------------------------------------------------------------------------------
/src/types/useraccount/UserAccountId.ts:
--------------------------------------------------------------------------------
1 | import IsStringOrObjectId from "../../decorators/typeproperty/IsStringOrObjectId";
2 | import MaxLengthAndMatches from "../../decorators/typeproperty/MaxLengthAndMatches";
3 | import { Values } from "../../constants/constants";
4 | import NotUnique from "../../decorators/typeproperty/NotUnique";
5 | import ReadWrite from "../../decorators/typeproperty/access/ReadWrite";
6 |
7 | export default class UserAccountId {
8 | @IsStringOrObjectId()
9 | @MaxLengthAndMatches(Values._24, /^[a-f\d]{1,24}$/)
10 | @NotUnique()
11 | @ReadWrite()
12 | userAccountId!: string;
13 | }
14 |
--------------------------------------------------------------------------------
/src/typescript/parser/parseEnumValuesFromSrcFile.ts:
--------------------------------------------------------------------------------
1 | import { parseSync } from '@babel/core';
2 | import { readFileSync } from 'fs';
3 | import getSrcFilePathNameForTypeName from '../../utils/file/getSrcFilePathNameForTypeName';
4 |
5 | export default function parseEnumValuesFromSrcFile(typeName: string) {
6 | const fileContentsStr = readFileSync(getSrcFilePathNameForTypeName(typeName), { encoding: 'UTF-8' });
7 |
8 | const ast = parseSync(fileContentsStr, {
9 | plugins: [
10 | ['@babel/plugin-proposal-decorators', { legacy: true }],
11 | '@babel/plugin-proposal-class-properties',
12 | '@babel/plugin-transform-typescript'
13 | ]
14 | });
15 |
16 | const nodes = (ast as any).program.body;
17 |
18 | for (const node of nodes) {
19 | if (
20 | node.type === 'ExportNamedDeclaration' &&
21 | node.declaration.type === 'TSTypeAliasDeclaration' &&
22 | node.declaration.id.name === typeName
23 | ) {
24 | return node.declaration.typeAnnotation.types.map((type: any) => type.literal.value);
25 | }
26 | }
27 |
28 | return [];
29 | }
30 |
--------------------------------------------------------------------------------
/src/utils/array/pushIfNotExists.ts:
--------------------------------------------------------------------------------
1 | export default function pushIfNotExists(values: string[], value: string): void {
2 | if (!values.includes(value)) {
3 | values.push(value);
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/src/utils/changePackageJsonNameProperty.ts:
--------------------------------------------------------------------------------
1 | import { readFileSync, renameSync } from 'fs';
2 | import { replaceInFile } from 'replace-in-file';
3 |
4 | export default async function changePackageJsonNameProperty() {
5 | if (process.env.NODE_ENV !== 'development') {
6 | return;
7 | }
8 |
9 | try {
10 | const packageJsonContents = readFileSync('package.json', { encoding: 'UTF-8' });
11 | const packageJsonObject = JSON.parse(packageJsonContents);
12 | if (packageJsonObject.name === 'backk-starter') {
13 | const microserviceName = process.cwd().split('/').reverse()[0];
14 | packageJsonObject.name = microserviceName;
15 | const replaceConfig = {
16 | files: [
17 | 'package.json',
18 | '.env.dev',
19 | '.env.ci',
20 | 'package-lock.json',
21 | 'sonar-project.properties',
22 | '.github/workflows/ci.yaml',
23 | 'helm/backk-starter/Chart.yaml',
24 | 'helm/backk-starter/values.yaml',
25 | 'helm/values/values-minikube.yaml'
26 | ],
27 | from: /backk-starter/g,
28 | to: microserviceName
29 | }
30 | await replaceInFile(replaceConfig);
31 | renameSync('helm/backk-starter', 'helm/' + microserviceName);
32 | }
33 | } catch {
34 | // No operation
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/utils/exception/throwException.ts:
--------------------------------------------------------------------------------
1 | export default function throwException(error: string | Error): never {
2 | if (typeof error === 'string') {
3 | throw new Error(error);
4 | }
5 |
6 | throw error;
7 | };
8 |
--------------------------------------------------------------------------------
/src/utils/exception/throwIf.ts:
--------------------------------------------------------------------------------
1 | import { BackkError } from "../../types/BackkError";
2 |
3 | export default function throwIf(error: BackkError | null | undefined) {
4 | if (error) {
5 | throw error;
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/src/utils/exception/throwIfNot.ts:
--------------------------------------------------------------------------------
1 | import { BackkError } from "../../types/BackkError";
2 |
3 | export default function throwIfNot(value: any, error: BackkError | null | undefined) {
4 | if (!value) {
5 | throw error;
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/src/utils/executeForAll.ts:
--------------------------------------------------------------------------------
1 | import { PromiseErrorOr } from '../types/PromiseErrorOr';
2 | import { BackkError } from '../types/BackkError';
3 |
4 | export default async function executeForAll(
5 | values: T[],
6 | func: (value: T) => PromiseErrorOr
7 | ): PromiseErrorOr {
8 | const finalValues = Array.isArray(values) ? values : [values];
9 |
10 | const possibleError = await finalValues.reduce(
11 | async (possibleErrorPromise: Promise, value) => {
12 | const possibleError = await possibleErrorPromise;
13 |
14 | if (possibleError) {
15 | return possibleError;
16 | }
17 |
18 | const [, error] = await func(value);
19 | return error;
20 | },
21 | Promise.resolve(null)
22 | );
23 |
24 | return [null, possibleError];
25 | }
26 |
--------------------------------------------------------------------------------
/src/utils/file/getSourceFileName.ts:
--------------------------------------------------------------------------------
1 | export default function getSourceFileName(fileName: string, distFolderName = 'build'): string {
2 | return fileName.replace(distFolderName, 'src');
3 | }
4 |
--------------------------------------------------------------------------------
/src/utils/file/getTypeFilePathNameFor.ts:
--------------------------------------------------------------------------------
1 | import { getFileNamesRecursively } from './getSrcFilePathNameForTypeName';
2 |
3 | export default function getTypeFilePathNameFor(typeName: string): string | undefined {
4 | const filePathNames = getFileNamesRecursively(process.cwd() + '/src');
5 | return filePathNames.find((filePathName: string) => filePathName.endsWith('/' + typeName + '.type'));
6 | }
7 |
--------------------------------------------------------------------------------
/src/utils/findAsyncSequential.ts:
--------------------------------------------------------------------------------
1 | export default async function findAsyncSequential(
2 | values: T[],
3 | predicate: (value: T) => Promise,
4 | ): Promise {
5 | for (const value of values) {
6 | if (await predicate(value)) {
7 | return value;
8 | }
9 | }
10 | return undefined;
11 | }
12 |
--------------------------------------------------------------------------------
/src/utils/forEachAsyncParallel.ts:
--------------------------------------------------------------------------------
1 | export default async function forEachAsyncParallel(
2 | array: T[],
3 | callback: (value: T, index: number, Array: T[]) => unknown
4 | ): Promise {
5 | await Promise.all(array.map(callback));
6 | }
7 |
--------------------------------------------------------------------------------
/src/utils/forEachAsyncSequential.ts:
--------------------------------------------------------------------------------
1 | export default async function forEachAsyncSequential(array: T[], callback: (value: T, index: number, Array: T[]) => Promise) {
2 | for (let index = 0; index < array.length; index++) {
3 | await callback(array[index], index, array);
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/src/utils/getDbNameFromServiceName.ts:
--------------------------------------------------------------------------------
1 | import throwException from './exception/throwException';
2 |
3 | export default function getDbNameFromServiceName() {
4 | const cwd = process.cwd();
5 | const serviceName = cwd.split('/').reverse()[0];
6 | return (
7 | serviceName.replace(/[-_]/g, '') + '_' + process.env.MICROSERVICE_NAMESPACE ??
8 | throwException('SERVICE_NAMESPACE environment variable must be defined')
9 | );
10 | }
11 |
--------------------------------------------------------------------------------
/src/utils/getMicroserviceName.ts:
--------------------------------------------------------------------------------
1 | export default function getMicroserviceName(): string {
2 | if (process.env.MICROSERVICE_NAME) {
3 | return process.env.MICROSERVICE_NAME;
4 | }
5 |
6 | const cwd = process.cwd();
7 | return cwd.split('/').reverse()[0];
8 | }
9 |
--------------------------------------------------------------------------------
/src/utils/getNamespacedMicroserviceName.ts:
--------------------------------------------------------------------------------
1 | import getMicroserviceName from "./getMicroserviceName";
2 |
3 | export default function getNamespacedMicroserviceName(): string {
4 | if (!process.env.MICROSERVICE_NAMESPACE) {
5 | throw new Error('MICROSERVICE_NAMESPACE environment variable must be defined');
6 | }
7 |
8 | return getMicroserviceName() + '.' + process.env.MICROSERVICE_NAMESPACE;
9 | }
10 |
--------------------------------------------------------------------------------
/src/utils/getSingularName.ts:
--------------------------------------------------------------------------------
1 | export default function getSingularName(name: string) {
2 | if (name.endsWith('ses')) {
3 | return name.slice(0, -2);
4 | } else if (name.endsWith('s')) {
5 | return name.slice(0, -1);
6 | }
7 |
8 | return name;
9 | }
10 |
--------------------------------------------------------------------------------
/src/utils/getTimeZone.ts:
--------------------------------------------------------------------------------
1 | export default function getTimeZone() {
2 | const timezoneOffset = new Date().getTimezoneOffset();
3 | const absoluteTimezoneOffset = Math.abs(timezoneOffset);
4 |
5 | return (
6 | (timezoneOffset < 0 ? '+' : '-') +
7 | ('00' + Math.floor(absoluteTimezoneOffset / 60)).slice(-2) +
8 | ':' +
9 | ('00' + (absoluteTimezoneOffset % 60)).slice(-2)
10 | );
11 | }
12 |
--------------------------------------------------------------------------------
/src/utils/promiseOf.ts:
--------------------------------------------------------------------------------
1 | export default function promiseNull() {
2 | return [null, null];
3 | }
4 |
--------------------------------------------------------------------------------
/src/utils/string/decapitalizeFirstLetter.ts:
--------------------------------------------------------------------------------
1 | export default function decapitalizeFirstLetter(str: string): string {
2 | if (str[0] === '_') {
3 | return str[0] + str[1].toLowerCase() + str.slice(2);
4 | }
5 |
6 | return str[0].toLowerCase() + str.slice(1);
7 | }
8 |
--------------------------------------------------------------------------------
/src/utils/type/findSubEntityClass.ts:
--------------------------------------------------------------------------------
1 | import getClassPropertyNameToPropertyTypeNameMap from '../../metadata/getClassPropertyNameToPropertyTypeNameMap';
2 | import getTypeInfoForTypeName from './getTypeInfoForTypeName';
3 | import isEntityTypeName from './isEntityTypeName';
4 |
5 | export default function findSubEntityClass(
6 | subEntityPath: string,
7 | EntityClass: new () => any,
8 | Types: any,
9 | currentPath = ''
10 | ): (new () => any) | void {
11 | const entityPropertyNameToPropertyTypeNameMap = getClassPropertyNameToPropertyTypeNameMap(EntityClass);
12 | let SubEntityClass: (new () => any) | void = undefined;
13 |
14 | Object.entries(entityPropertyNameToPropertyTypeNameMap).forEach(([propertyName, propertyTypeName]) => {
15 | const propertyPathName = currentPath ? currentPath + '.' + propertyName : propertyName;
16 | const { baseTypeName, isArrayType } = getTypeInfoForTypeName(propertyTypeName);
17 |
18 | if (propertyPathName === subEntityPath) {
19 | SubEntityClass = Types[baseTypeName];
20 | }
21 |
22 | if (!SubEntityClass && isArrayType && isEntityTypeName(baseTypeName)) {
23 | SubEntityClass = findSubEntityClass(subEntityPath, Types[baseTypeName], Types, propertyName);
24 | }
25 | });
26 |
27 | return SubEntityClass;
28 | }
29 |
--------------------------------------------------------------------------------
/src/utils/type/isEntityTypeName.ts:
--------------------------------------------------------------------------------
1 | import entityAnnotationContainer from "../../decorators/entity/entityAnnotationContainer";
2 |
3 | export default function isEntityTypeName(typeName: string): boolean {
4 | return entityAnnotationContainer.entityNameToClassMap[typeName] &&
5 | typeName[0] === typeName[0].toUpperCase() &&
6 | typeName[0] !== '('
7 | }
8 |
--------------------------------------------------------------------------------
/src/utils/type/isEnumTypeName.ts:
--------------------------------------------------------------------------------
1 | import parseEnumValuesFromSrcFile from '../../typescript/parser/parseEnumValuesFromSrcFile';
2 | import entityAnnotationContainer from "../../decorators/entity/entityAnnotationContainer";
3 |
4 | export default function isEnumTypeName(typeName: string): boolean {
5 | return (
6 | typeName[0] === '(' ||
7 | (typeName[0] === typeName[0].toUpperCase() &&
8 | typeName !== 'Date' &&
9 | !entityAnnotationContainer.entityNameToClassMap[typeName] &&
10 | parseEnumValuesFromSrcFile(typeName).length > 0)
11 | );
12 | }
13 |
--------------------------------------------------------------------------------
/src/utils/type/isPropertyReadDenied.ts:
--------------------------------------------------------------------------------
1 | import typePropertyAnnotationContainer from "../../decorators/typeproperty/typePropertyAnnotationContainer";
2 |
3 | export default function isPropertyReadDenied(Class: Function, propertyName: string) {
4 | return typePropertyAnnotationContainer.isTypePropertyCreateOnly(Class, propertyName) ||
5 | typePropertyAnnotationContainer.isTypePropertyPrivate(Class, propertyName) ||
6 | typePropertyAnnotationContainer.isTypePropertyUpdateOnly(Class, propertyName) ||
7 | typePropertyAnnotationContainer.isTypePropertyWriteOnly(Class, propertyName)
8 | }
9 |
--------------------------------------------------------------------------------
/src/utils/type/isValidFunctionArgumentTypeName.ts:
--------------------------------------------------------------------------------
1 | import { parseSync } from '@babel/core';
2 | import { readFileSync } from 'fs';
3 | import getSrcFilePathNameForTypeName from '../file/getSrcFilePathNameForTypeName';
4 |
5 | export default function isValidFunctionArgumentTypeName(typeName: string, remoteServiceRootDir = ''): boolean {
6 | const typeFilePathName = getSrcFilePathNameForTypeName(typeName, remoteServiceRootDir);
7 | const fileContentsStr = readFileSync(typeFilePathName, { encoding: 'UTF-8' });
8 |
9 | const ast = parseSync(fileContentsStr, {
10 | plugins: [
11 | ['@babel/plugin-proposal-decorators', { legacy: true }],
12 | '@babel/plugin-proposal-class-properties',
13 | '@babel/plugin-transform-typescript',
14 | ],
15 | });
16 |
17 | const nodes = (ast as any).program.body;
18 |
19 | for (const node of nodes) {
20 | if (
21 | node.type === 'ExportDefaultDeclaration' &&
22 | node.declaration.type === 'ClassDeclaration' &&
23 | node.declaration.id.name === typeName
24 | ) {
25 | return true;
26 | }
27 | }
28 |
29 | return false;
30 | }
31 |
--------------------------------------------------------------------------------
/src/utils/wait.ts:
--------------------------------------------------------------------------------
1 | export default function wait(waitTimeInMillis: number) {
2 | return new Promise((resolve) => setTimeout(resolve, waitTimeInMillis));
3 | }
4 |
--------------------------------------------------------------------------------
/src/validation/getCustomValidationConstraint.ts:
--------------------------------------------------------------------------------
1 | import { MetadataStorage, getFromContainer } from 'class-validator';
2 | import { ValidationMetadata } from 'class-validator/metadata/ValidationMetadata';
3 |
4 | export default function getCustomValidationConstraint(
5 | Class: Function,
6 | propertyName: string,
7 | validationType: string,
8 | constraintIndex: number
9 | ): any {
10 | const validationMetadatas = getFromContainer(MetadataStorage).getTargetValidationMetadatas(Class, '');
11 |
12 | const foundValidation = validationMetadatas.find(
13 | (validationMetadata: ValidationMetadata) =>
14 | validationMetadata.propertyName === propertyName &&
15 | validationMetadata.type === 'customValidation' &&
16 | validationMetadata.constraints[0] === validationType
17 | );
18 |
19 | return foundValidation ? foundValidation.constraints[constraintIndex] : undefined;
20 | }
21 |
--------------------------------------------------------------------------------
/src/validation/getValidationConstraint.ts:
--------------------------------------------------------------------------------
1 | import { MetadataStorage, getFromContainer } from 'class-validator';
2 | import { ValidationMetadata } from 'class-validator/metadata/ValidationMetadata';
3 |
4 | export default function getValidationConstraint(
5 | Class: Function,
6 | propertyName: string,
7 | validationType: string,
8 | constraintIndex?: number
9 | ): any {
10 | const validationMetadatas = getFromContainer(MetadataStorage).getTargetValidationMetadatas(Class, '');
11 |
12 | const foundValidation = validationMetadatas.find(
13 | (validationMetadata: ValidationMetadata) =>
14 | validationMetadata.propertyName === propertyName && validationMetadata.type === validationType
15 | );
16 |
17 | return foundValidation ? foundValidation.constraints[constraintIndex ?? 0] : undefined;
18 | }
19 |
--------------------------------------------------------------------------------
/src/validation/getValidationErrors.ts:
--------------------------------------------------------------------------------
1 | import { ValidationError } from "class-validator";
2 |
3 | export default function getValidationErrors(errorOrValidationErrors: ValidationError[] | Error): string {
4 | return errorOrValidationErrors instanceof Error
5 | ? errorOrValidationErrors.message
6 | : errorOrValidationErrors
7 | .map((validationError: ValidationError) => {
8 | if (validationError.constraints) {
9 | return Object.values(validationError.constraints)
10 | .map((constraint) => constraint)
11 | .join(', ');
12 | } else {
13 | return validationError.property + ': ' + getValidationErrors(validationError.children);
14 | }
15 | })
16 | .join(', ');
17 | }
18 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "module": "commonjs",
4 | "declaration": true,
5 | "removeComments": true,
6 | "esModuleInterop": true,
7 | "emitDecoratorMetadata": true,
8 | "experimentalDecorators": true,
9 | "target": "es2019",
10 | "sourceMap": true,
11 | "strict": true,
12 | "outDir": "./lib",
13 | "baseUrl": "./",
14 | "incremental": true
15 | },
16 |
17 | "include": ["src"],
18 | "exclude": ["node_modules", "lib"]
19 | }
20 |
--------------------------------------------------------------------------------