├── .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 | 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 | --------------------------------------------------------------------------------