Repository: 4lessandrodev/types-ddd Branch: main Commit: dba35bb7b257 Files: 128 Total size: 329.6 KB Directory structure: gitextract_7wdim7d5/ ├── .github/ │ ├── ISSUE_TEMPLATE/ │ │ ├── bug_report.md │ │ └── feature_request.md │ ├── dependabot.yml │ └── workflows/ │ └── main.yml ├── .gitignore ├── .husky/ │ ├── pre-commit │ └── pre-push ├── .prettierignore ├── .prettierrc ├── CHANGELOG.md ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── Makefile ├── README.md ├── SECURITY.md ├── check-dev-deps.sh ├── docs/ │ └── README.md ├── global-setup.ts ├── jest.config.ts ├── lerna.json ├── package.json ├── packages/ │ ├── cnpj/ │ │ ├── CHANGELOG.md │ │ ├── README.md │ │ ├── __tests__/ │ │ │ ├── cnpj.value-object.util.spec.ts │ │ │ └── is-valid-cpf-digit.util.spec.ts │ │ ├── index.ts │ │ ├── package.json │ │ ├── tsconfig.build.json │ │ ├── tsconfig.json │ │ └── util.ts │ ├── cpf/ │ │ ├── CHANGELOG.md │ │ ├── README.md │ │ ├── __tests__/ │ │ │ ├── cpf.value-object.util.spec.ts │ │ │ └── is-valid-cpf-digit.util.spec.ts │ │ ├── index.ts │ │ ├── package.json │ │ ├── tsconfig.build.json │ │ ├── tsconfig.json │ │ └── util.ts │ ├── date/ │ │ ├── CHANGELOG.md │ │ ├── README.md │ │ ├── __tests__/ │ │ │ └── date.value-object.spec.ts │ │ ├── index.ts │ │ ├── package.json │ │ ├── tsconfig.build.json │ │ ├── tsconfig.json │ │ ├── types.ts │ │ └── util.ts │ ├── email/ │ │ ├── CHANGELOG.md │ │ ├── README.md │ │ ├── __tests__/ │ │ │ ├── email-validator.spec.ts │ │ │ └── email.value-object.spec.ts │ │ ├── email.validator.util.ts │ │ ├── index.ts │ │ ├── package.json │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── logger/ │ │ ├── CHANGELOG.md │ │ ├── README.md │ │ ├── __tests__/ │ │ │ └── logger.spec.ts │ │ ├── index.ts │ │ ├── package.json │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── money/ │ │ ├── CHANGELOG.md │ │ ├── README.md │ │ ├── __tests__/ │ │ │ └── money.spec.ts │ │ ├── index.ts │ │ ├── package.json │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── password/ │ │ ├── CHANGELOG.md │ │ ├── README.md │ │ ├── __tests__/ │ │ │ ├── password.spec.ts │ │ │ └── util.spec.ts │ │ ├── index.ts │ │ ├── package.json │ │ ├── tsconfig.build.json │ │ ├── tsconfig.json │ │ └── utils.ts │ ├── patterns/ │ │ ├── CHANGELOG.md │ │ ├── README.md │ │ ├── __tests__/ │ │ │ └── specification.value-object.spec.ts │ │ ├── index.ts │ │ ├── package.json │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── phone/ │ │ ├── CHANGELOG.md │ │ ├── README.md │ │ ├── __tests__/ │ │ │ ├── home-phone.value-object.spec.ts │ │ │ ├── mobile-phone.value-object.spec.ts │ │ │ └── phone.value-object.spec.ts │ │ ├── ddd.list.ts │ │ ├── home.value-object.ts │ │ ├── index.ts │ │ ├── mobile.value-object.ts │ │ ├── package.json │ │ ├── phone.value-object.ts │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── tsconfig.build.json │ ├── tsconfig.json │ ├── type-ddd/ │ │ ├── CHANGELOG.md │ │ ├── README.md │ │ ├── index.ts │ │ ├── package.json │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ ├── username/ │ │ ├── CHANGELOG.md │ │ ├── README.md │ │ ├── __tests__/ │ │ │ └── user-name-value-object.util.spec.ts │ │ ├── index.ts │ │ ├── package.json │ │ ├── tsconfig.build.json │ │ └── tsconfig.json │ └── zip-code/ │ ├── CHANGELOG.md │ ├── README.md │ ├── __tests__/ │ │ └── zip-code.value-object.spec.ts │ ├── index.ts │ ├── package.json │ ├── tsconfig.build.json │ ├── tsconfig.json │ └── util.ts ├── scripts/ │ ├── login.sh │ └── make-user.sh ├── tsconfig.json └── update-peer-dependency.sh ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/ISSUE_TEMPLATE/bug_report.md ================================================ --- name: Bug report about: Create a report to help us improve title: '' labels: '' assignees: '' --- **Describe the bug** A clear and concise description of what the bug is. **To Reproduce** Steps to reproduce the behavior: 1. Go to '...' 2. Click on '....' 3. Scroll down to '....' 4. See error **Expected behavior** A clear and concise description of what you expected to happen. **Screenshots** If applicable, add screenshots to help explain your problem. **Desktop (please complete the following information):** - OS: [e.g. iOS] - Browser [e.g. chrome, safari] - Version [e.g. 22] **Smartphone (please complete the following information):** - Device: [e.g. iPhone6] - OS: [e.g. iOS8.1] - Browser [e.g. stock browser, safari] - Version [e.g. 22] **Additional context** Add any other context about the problem here. ================================================ FILE: .github/ISSUE_TEMPLATE/feature_request.md ================================================ --- name: Feature request about: Suggest an idea for this project title: '' labels: '' assignees: '' --- **Is your feature request related to a problem? Please describe.** A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] **Describe the solution you'd like** A clear and concise description of what you want to happen. **Describe alternatives you've considered** A clear and concise description of any alternative solutions or features you've considered. **Additional context** Add any other context or screenshots about the feature request here. ================================================ FILE: .github/dependabot.yml ================================================ # To get started with Dependabot version updates, you'll need to specify which # package ecosystems to update and where the package manifests are located. # Please see the documentation for all configuration options: # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates version: 2 updates: - package-ecosystem: "npm" # See documentation for possible values directory: "/" # Location of package manifests schedule: interval: "weekly" ================================================ FILE: .github/workflows/main.yml ================================================ # This is a basic workflow to help you get started with Actions name: CI # Controls when the action will run. on: # Triggers the workflow on push or pull request events but only for the main branch push: branches: [ main ] pull_request: branches: [ main ] # Allows you to run this workflow manually from the Actions tab workflow_dispatch: # A workflow run is made up of one or more jobs that can run sequentially or in parallel jobs: # This workflow contains a single job called "build" build: # The type of runner that the job will run on runs-on: ubuntu-latest # Steps represent a sequence of tasks that will be executed as part of the job steps: # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - uses: actions/checkout@v3 - uses: actions/setup-node@v3 with: node-version: 18 # Runs a single command using the runners shell - name: Install dependencies run: yarn install # Runs a set of commands using the runners shell - name: Run all tests run: yarn test # Runs a set of commands using the runners shell - name: Run typescript check run: yarn tsc --noEmit ================================================ FILE: .gitignore ================================================ # Logs logs *.log npm-debug.log* yarn-debug.log* yarn-error.log* lerna-debug.log* # Diagnostic reports (https://nodejs.org/api/report.html) report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json # Runtime data pids *.pid *.seed *.pid.lock # Directory for instrumented libs generated by jscoverage/JSCover lib-cov # Coverage directory used by tools like istanbul coverage *.lcov # nyc test coverage .nyc_output # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) .grunt # Bower dependency directory (https://bower.io/) bower_components # node-waf configuration .lock-wscript # Compiled binary addons (https://nodejs.org/api/addons.html) build/Release # Dependency directories node_modules/ jspm_packages/ # TypeScript v1 declaration files typings/ # TypeScript cache *.tsbuildinfo # Optional npm cache directory .npm # Optional eslint cache .eslintcache # Microbundle cache .rpt2_cache/ .rts2_cache_cjs/ .rts2_cache_es/ .rts2_cache_umd/ # Optional REPL history .node_repl_history # Output of 'npm pack' *.tgz # Yarn Integrity file .yarn-integrity # dotenv environment variables file .env .env.test # parcel-bundler cache (https://parceljs.org/) .cache # Next.js build output .next # Nuxt.js build / generate output .nuxt dist # Gatsby files .cache/ # Comment in the public line in if your project uses Gatsby and *not* Next.js # https://nextjs.org/blog/next-9-1#public-directory-support # public # vuepress build output .vuepress/dist # Serverless directories .serverless/ # FuseBox cache .fusebox/ # DynamoDB Local files .dynamodb/ # TernJS port file .tern-port build .vscode/ packages/*/*.js packages/*/*.d.ts packages/*/tsconfig.build.tsbuildinfo .npmrc .nx/ ================================================ FILE: .husky/pre-commit ================================================ #!/bin/sh . "$(dirname "$0")/_/husky.sh" npm run check:types npx lint-staged ================================================ FILE: .husky/pre-push ================================================ #!/bin/sh . "$(dirname "$0")/_/husky.sh" npm run test ================================================ FILE: .prettierignore ================================================ # Ignore everything: /* /__snapshots__/* *.ts.snap # Except: !/lib !/example !/tests !jest.config.ts !package.json !tsconfig.json !tsconfig.lib.json !webpack.config.ts ================================================ FILE: .prettierrc ================================================ { "useTabs": true, "arrowParens": "always", "singleQuote": true, "semi": true, "bracketSpacing": true, "endOfLine": "lf", "tabWidth": 4 } ================================================ FILE: CHANGELOG.md ================================================ # Changelog All notable changes to this project will be documented in this file. ## Unreleased --- ## Released --- ### [4.1.0] - 2024-12-15 #### Feat - Atualizando lib core para nova versão - rich-domain v1.25.0 --- ### [4.0.5] - 2024-11-28 ### Fix - update rich-domain lib to check nullish type: now 'create' return a possibly null value in Result instance. --- ### [4.0.4] - 2024-09-26 ### Fix - Corrected `"files"` in `package.json` to include `utils.js` and `utils.d.ts`, resolving module not found errors during compilation in email and password. --- ### [4.0.2] - 2024-06-26 ### Fix - update: update rich-domain to v1.23.3 --- ### [4.0.0] - 2024-05-31 ### Refactor (Break Change) - refactor: change lib to individual packages --- ### [3.9.0] - 2024-04-28 ### Update (Break Change) - Update core to v1.23.0 - check [Core Changelog](https://github.com/4lessandrodev/rich-domain/blob/main/CHANGELOG.md) --- ## Released ### [3.8.3] - 2024-04-13 ### Update - Update core - Added support to multi context name - Details [Commit](https://github.com/4lessandrodev/rich-domain/commit/00db292f0604469c8bf2f2fddf6460901a084cc6) ```ts // Example Usage const context = Context.events(); const handler = (event) => console.log(event); // Subscribing to events under different contexts context.subscribe('Context-A:SIGNUP', handler); context.subscribe('Context-B:SIGNUP', handler); context.subscribe('Context-C:SIGNUP', handler); context.subscribe('Context-B:NOTIFY', handler); context.subscribe('Context-B:SEND-EMAIL', handler); // Dispatching events to specific contexts // Dispatches the SIGNUP event to Context-B context.dispatchEvent('Context-B:SIGNUP'); // Dispatches the SIGNUP event to all contexts context.dispatchEvent('*:SIGNUP'); // Dispatches all events to all contexts. Not recommended context.dispatchEvent('*:*'); // Dispatches all events under Context-B context.dispatchEvent('Context-B:*'); ``` --- ### [3.8.2] - 2024-04-12 ### Update - Update core - Added support to global events [ChangeLog](https://github.com/4lessandrodev/rich-domain/pull/139) --- ### [3.8.1] - 2024-03-18 ### Update - Update core - Fix logger messages --- ### [3.8.0] - 2024-03-18 ### Update - update deps core to v1.20.0 see [changes](https://github.com/4lessandrodev/rich-domain/blob/main/CHANGELOG.md) --- ### [3.7.2] - 2024-03-15 ### Update - update deps - added support to nodejs v21 --- ### [3.7.1] - 2023-12-15 ### Update - update deps --- ### [3.7.0] - 2023-09-30 ### Update - Update core - update deps - rich-domain: update lib core to 1.19.0 - remove support for deprecated history method - improve performance and save memory usage --- ### [3.6.4] - 2023-08-24 ### Update - Update core - update deps - rich-domain: update lib core to 1.18.4 --- ### [3.6.3] - 2023-07-30 ### Update - Update core - rich-domain: update lib core to 1.18.3 #272 #282 --- ### [3.6.2] - 2023-07-09 ### Update - Update core - rich-domain: update lib core to 1.18.2 #272 --- --- ### [3.6.1] - 2023-06-30 ### Update - Update core - rich-domain: update lib core to 1.18.1 --- ### [3.6.0] - 2023-04-21 ### Update - Update core - rich-domain: update lib core to 1.18.0 [More](https://github.com/4lessandrodev/rich-domain/blob/main/CHANGELOG.md) --- ### [3.5.3] - 2023-02-18 ### Update - Update core - rich-domain: update lib core to 1.17.3 --- ### [3.5.2] - 2023-02-18 ### Changed - validation to `url.value-object` use URL default validation and remove regex. by @ArturHamannRonconi ### Added - added separator as optional param to `getInitials`method from `user-name.value-object` --- ### [3.5.1] - 2023-01-27 ### Update - Update core - rich-domain: update lib core to 1.17.1 --- ### [3.5.0] - 2023-01-21 ### Update - Update core ### Breaking Change - rich-domain: update lib core to 1.17.0 check on [pull request 33](https://github.com/4lessandrodev/rich-domain/pull/33) ```ts // Example using set now const changed = user.set("name").to(age); console.log(changed); > true ``` ```ts // Example using clone now const copy = user.clone(); console.log(copy.get("name").get("value")) > "Jane Doe" ``` --- ### [3.4.7] - 2023-01-19 ### Update - rich-domain: update lib core to 1.16.2 --- ### [3.4.6] - 2023-01-18 ### Update - rich-domain: update lib core to 1.16.1 --- ### [3.4.5] - 2023-01-14 ### Changed - date.value-object: rename method from `isEqual` to `isEqualDate` ### Update - rich-domain: update lib core to 1.16.0 - Entity: added method isEqual to compare current instance with another one. - ValueObject: added method isEqual to compare current instance with another one. [Issue 27](https://github.com/4lessandrodev/rich-domain/issues/27) --- ### [3.4.4] - 2023-01-12 ### Added - custom-string.value-object: By: [VinnyLima](https://github.com/VinnyLima) - removeSpecialChars and onlyNumbers: [Issue 223](https://github.com/4lessandrodev/types-ddd/issues/223) - email.value-object: added MESSAGE as customizable value --- ### [3.4.3] - 2023-01-05 ### Updated - rich-domain: update lib core to 1.15.2 --- ### [3.4.2] - 2023-01-03 ### Fix - node version: update requirements. node version required >=16 and < 19 --- ### [3.4.1] - 2023-01-03 ### Fix - user-name.value-object: remove empty spaces. By: [VinnyLima](https://github.com/VinnyLima) --- ### [3.4.0] - 2022-12-25 ### Update - rich-domain: update lib core to 1.15.0 - value-objects: added MESSAGE attribute to instance Now its possible to customize error message Example: ```ts // custom-user-name.ts import { UserNameValueObject } from 'types-ddd'; Reflect.set(UserNameValueObject, "MIN_LENGTH", 3); Reflect.set(UserNameValueObject, "MAX_LENGTH", 20); Reflect.set(UserNameValueObject, "MESSAGE", "Username must be a maximum of 3 and a minimum of 20 characters"); const CustomName = UserNameValueObject; export CustomName; // > import this to create your user name export default CustomName; ``` --- ### [3.3.7] - 2022-11-27 ### Update - rich-domain: update lib core to 1.14.6 --- ### [3.3.6] - 2022-11-25 ### Update - rich-domain: update lib core to 1.14.5 --- ### [3.3.5] - 2022-11-22 ### Update - rich-domain: update lib core to 1.14.4 --- ### [3.3.4] - 2022-11-22 ### Update - rich-domain: update lib core to 1.14.3 --- ### [3.3.3] - 2022-11-17 ### Changed - Ok and Fail: ensure export from lib --- ### [3.3.2] - 2022-11-07 ### Changed - chore: deps - update deps --- ### [3.3.1] - 2022-11-03 ### Fixed - value-objects: calc validation --- ### [3.3.0] - 2022-10-05 ### Changed - value-objects: implement customization for value objects --- ### [3.2.2] - 2022-10-03 ### Changed - result: implement freeze result instance - password: define protected props as MAX_LENGTH and MIN_LENGTH - update deps: rich-domain --- ### [3.2.1] - 2022-09-26 ### Changed - update deps: rich-domain - refactor: Fail - refactor: Ok - refactor: Result.Ok - refactor: Result.fail Change generic types order for `Result.fail` and `Result.Ok` Now each method has your own order Example: ```ts // for implementation: IResult; // before the generic order was the same for both method. // now for Result.Ok // the generic order is Result.Ok(payload metaData); // for Result.fail //the generic order is Result.fail(error, metaData); ``` Changes made on Ok ```ts import { Ok } from 'rich-domain'; // simple use case for success. no arg required return Ok(); // arg required return Ok('my payload'); // arg and metaData required interface MetaData { arg: string; } return Ok('payload', { arg: 'sample' }); ``` Changes made on Fail ```ts import { Fail } from 'rich-domain'; // simple use case for success. no arg required return Fail(); // arg required return Fail('my payload'); // arg and metaData required interface MetaData { arg: string; } return Fail('payload', { arg: 'sample' }); ``` --- ### [3.2.0] - 2022-09-26 ### Added - update deps: rich-domain - feat: implement function Fail - feat: implement function Ok --- ### [3.1.5] - 2022-09-26 ### Fixed - EmailValueObject: remove regex and added function validation --- ### [3.1.4] - 2022-09-20 ### Update deps: update dependencies - rich-domain to v1.12.0 --- ### [3.1.3] - 2022-09-03 ### Update deps: update dependencies - rich-domain to v1.11.2 - typescript to 4.8.2 --- ### [3.1.2] - 2022-08-14 ### Update docs: update readme and documentation deps: update dependencies ### Added ci: install dependabot to check deps --- ### [3.1.1] - 2022-08-14 ### Update docs: update readme and documentation --- ### [3.1.0] - 2022-08-10 ### Changed - deps: update dependencies rich-domain to version 1.11.0 Change order validation args in value objects ```ts // from validation(key: Key, value: Props[Key]): boolean {}; // to validation(value: Props[Key], key: Key): boolean {}; ``` --- ### [3.0.2] - 2022-08-07 ### Update - deps: update dependencies rich-domain to version 1.10.0 --- ### [3.0.1-beta.0] - 2022-08-05 ### Update - deps: update dependencies rich-domain to version 1.9.0 --- ### [3.0.0-beta.0] - 2022-08-05 ### Update - deps: update dependencies --- ### [3.0.0-beta] - 2022-08-04 ### Change - change core (**breaking changes**). using now rich-domain lib [npm rich-domain](https://www.npmjs.com/package/rich-domain) --- ### [2.12.1] - 2022-07-18 ### Update - deps: update dependencies --- ### [2.12.0] - 2022-04-18 ### Changes - TSProxy: change context param from function to instance of class [pull request](https://github.com/4lessandrodev/types-ddd/pull/144) - deps: update dependencies --- ### [2.11.0] - 2022-04-02 ### Added - TSProxy: added abstract class as proxy implementation [pull request](https://github.com/4lessandrodev/types-ddd/pull/142) --- ### [2.10.3] - 2022-03-28 ### Changes - logger: make instance a singleton --- ### [2.10.2] - 2022-03-28 ### Changes - logger: update configs ### Update - deps: update dependencies --- ### [2.10.1] - 2022-03-23 ### Update - deps: update dependencies --- ### [2.10.0] - 2022-02-27 ### Fixed - toObject: return string when there is a domainId as value-object attribute --- ### [2.9.13] - 2022-02-14 ### Fixed - toObject: added support to convert a simple object on entity --- ### [2.9.11] ~ [2.9.12] - 2022-02-13 ### Changed - toObject: added support to convert a value object inside another one --- ### [2.9.9] ~ [2.9.10] - 2022-02-13 ### Changed - update dependencies - update documentation --- ### [2.9.8] - 2022-02-09 ### Fixed - toObject: fix adding support for string, boolean and numbers to domain entity attributes on call toObject method. - create: ensure all domain entity implements create method ### Added - clone: added method to clone a domain entity --- ### [2.9.7] - 2022-01-31 ### Added - logs deactivation: now its possible deactivate all logs; ```sh NODE_ENV=production # automatically turn off all logs TYPES_DDD_LOGS=off # manual turn off logs TYPES_DDD_LOGS=error # show only errors log TYPES_DDD_LOGS=info # show only info log TYPES_DDD_LOGS=warn # show only warn log ``` --- ### [2.9.6] - 2022-01-30 ### Fixed - toObject: ensure to convert a moderately complex value object --- ### [2.9.5] - 2022-01-30 ### Added - toObject: update types on entity.toObject method ### Changed - update and change some documentation and examples - mark IMapper interface as deprecated tool. Use TMapper instead --- ### [2.9.4] - 2022-01-29 ### Added - Imports: Create shortcuts for imports : Issue #114 --- ### [2.9.3] - 2022-01-21 ### Fixed - AutoMapper: get string value when prop is DomainId or ShortDomainId --- ### [2.9.1] ~ [2.9.2] - 2022-01-21 ### Changed - DomainId: added clone method to create a new id from an instance - ShortDomainId: added clone method to create a new id from an instance ### Added - Available AutoMapper to convert Entity, Aggregate and ValueObject from domain instance to a persistence model --- ### [2.9.0] - 2022-01-21 ### Changed - DomainId and ShortDomainId: added property isNew to identify if is a new id - Entity, Aggregate and ValueObject: added method toObject to convert domain instance to a persistence model ### Added - Available AutoMapper to convert Entity, Aggregate and ValueObject from domain instance to a persistence model --- ### [2.8.8] - 2021-12-29 --- ### Added - Entity: hasSomeTypes method to validate different types from instance keys ### Changed - Entity: isSome method > new accepted type: 'null' - Entity: isAll method > new accepted type: 'null' ### [2.8.7] - 2021-12-28 --- ### Added - State: addManyState method add many results to state and return unique keys - State: getStateByKeys method get many results by keys ### [2.8.6]- 2021-12-26 --- ### Added - Entity: toObject method transform instance in persistence object ### [2.8.5] - 2021-12-25 --- ### Added - Entity: added method checkProps to entity instance ### [2.8.4] - 2021-12-24 --- ### Changed - State: define exists method as protected ### Added - State: added callback on state ### [2.8.3] - 2021-12-23 --- ### Changed - State: define exists method as protected ### [2.8.2] - 2021-12-22 --- ### Changed - Mapper: rename to State - Mapper: added exists method ### [2.8.1] - 2021-12-22 --- ### Changed - Mapper: added logger if state key does not exits ### [2.8.0] - 2021-12-22 --- ### Changed - static method on domain entities - buildFromDto > change to build - buildFromModel > change to build - buildToModel > change to build - IMapper2 > change to TMapper ### [2.7.15] - 2021-12-21 --- ### Added - static method on domain entities - buildFromDto - buildFromModel - buildToModel ### [2.7.14] - 2021-12-21 --- ### Added - abstract class Mapper with state management methods - IMappers interface with new methods ### [2.7.12] - [2.7.13] - 2021-12-14 --- ### Changed - DomainId and ShortDomainId: make both compatible ### [2.7.11] - 2021-12-14 --- ### Changed - BaseDomainEntity: ID accept DomainId or ShortDomainId - Entity: getHashCode - now returns uid value base value added to ID - Breaking change - Remove methods from DomainId: - toShort() - shortUid ### [2.7.10] - 2021-12-14 --- ### Fix - ShortDomainId: export resource ### [2.7.9] - 2021-12-14 --- ### Added - ShortDomainId: default short domain id - 16 bit ### [2.7.8] - 2021-11-22 --- ### Fixed - PasswordValueObject: validate if instance value already is encrypted. ### [2.7.7] - 2021-11-22 --- ### Changed - DimensionValueObject: now update methods returns updated instance. - PasswordValueObject: now encrypt method returns updated instance. - UserNameValueObject: now capitalize method returns updated instance. - WeightValueObject: now update methods returns updated instance. ### [2.7.6] - 2021-11-21 --- ### Fixed - util: change regex to validate email (includes dot as valid char). ### [2.7.5] - 2021-10-11 --- ### Changed - entities and aggregates: getHashCode > combination of class name and id. Now using short uid. ### Fixed - lib: publish only dist to keep lib small ### [2.7.4] - 2021-10-09 --- ### Fixed - removeUndefinedKeysFromObject: do not remove dates ### [2.7.3] - 2021-10-08 --- ### Changed - DateValueObject: added comparators methods ### [2.7.2] - 2021-10-08 --- ### Changed - DateValueObject: added validation on create a new instance ### [2.7.1] - 2021-10-08 --- ### Added - DateValueObject ### [2.7.0] - 2021-10-06 --- ### Changed - DomainId > change getters method ### [2.6.2] ~ [2.6.4] - 2021-10-06 --- ### Changed - DomainId > added toShort method - DomainId > toShort method. Now you can choose length ### [2.6.1] - 2021-09-30 --- ### Changed - getUndefinedKeysAsObject > added new option to return as value ### [2.6.0] - 2021-09-29 --- ### Changed - CurrencyValueObject > added functions to compare values - getUndefinedKeysAsObject > added option to get path as string ### [2.5.7] ~ 2.5.10 - 2021-09-25 --- ### Added - removeUndefinedKeysFromObject ### [2.5.6] - 2021-09-23 --- ### Changed - dist > update build ### [2.5.5] - 2021-09-24 --- ### Changed - getUndefinedKeysAsObject > define value to be applied ### [2.5.4] - 2021-09-24 --- ### Changed - DimensionValueObject > validate unit before create value object - WeightValueObject > validate unit before create value object ### [2.5.3] - 2021-09-22 --- ### Changed - DimensionEntity > changed to value object: DimensionValueObject - WeightEntity > changed to value object: WeightValueObject ### [2.5.2] - 2021-09-21 --- ### Changed - dist: remove unused files on dist ### [2.5.1] - 2021-09-20 --- ### Changed - Lib utils: Imports path ### Added - WeightUnitValueObject - UnitOfMeasureValueObject - DimensionEntity - WeightEntity ### [2.5.0] - 2021-09-18 --- ### Changed - PinValueObject: Define pin props as optional - Rename folder: from src to lib ### Added - CPFValueObject - CNPJValueObject - CustomStringValueObject - CustomNumberValueObject - HEXColorValueObject: Ensure don't generate light color like white - RGBColorValueObject: Ensure don't generate light color like white ### [2.4.2] ~ [2.4.10] - 2021-09-09 --- ### Fixed - Update dependencies ### [2.4.1] - 2021-09-09 --- ### Fixed - UrlValueObject: export value object ### [2.4.0] - 2021-09-07 --- ### Changed - PinValueObject: util value object ### [2.3.6] - 2021-08-29 --- ### Changed - CurrencyValueObject: docs - identify max safe number ### [2.3.5] - 2021-08-29 --- ### Changed - Result - Change default generic type on `combine` method to `unknown` instead `any` ### [2.3.4] - 2021-08-29 --- ### Changed - ChangesObserver - Fix added possibility to get all added results `getAllAddedResults` ### [2.3.3] - 2021-08-28 --- ### Changed - Result - Fix possibility to return a void instance. Create a specific method `Result.success` ### [2.3.2] - 2021-08-28 --- ### Changed - Result - Fix possibility to return a void instance ### [2.3.1]- 2021-08-28 --- ### Changed - Result - added an internationalization error message ### [2.3.0] - 2021-08-27 --- ### Added - StatusCodeEnum ### Changed - Result - provide an enum as string declaration instead number ### [2.2.3] - 2021-08-24 --- ### Added - SpecificationComposite ### Changed - IBaseRepository - rename params and doc comments ### [2.2.2] - 2021-08-19 --- ### Fixed - colorGenerator ### [2.2.1] - 2021-08-17 --- ### Fixed - index (exports) ### [2.2.0] - 2021-08-17 --- ### Added - getUndefinedKeysAsArray - getUndefinedKeysAsObject ### [2.1.0] - 2021-08-14 --- ### Changed - CurrencyValueObject ### Added - ChangesObserver ### [2.0.4] - 2021-08-13 --- ### Fixed - Result ### [2.0.3] - 2021-08-13 --- ### Fixed - Result: new approach ### [2.0.2] - 2021-08-12 --- ### Changed - Dynamic types to Filter on IBaseRepository ### [2.0.1] - 2021-08-12 - Entity ### [2.0.0] - 2021-08-12 --- ### Changed - DomainId - AggregateRoot: new approach - Entity: new approach - Filter ### Added - OrderStatusValueObject ### Fixed - AggregateRoot ### [1.5.1] - 2021-08-11 --- ### Changed - BirthdayValueObject ### [1.5.0] - 2021-08-11 --- ### Added - colorConverter - colorGenerator ### Changed - RGBColorValueObject - HEXColorValueObject ### [1.4.1] - 2021-08-11 --- ### Added - RGBColorValueObject - HEXColorValueObject - PostalCodeValueObject - UrlValueObject ### Changed - Result ### [1.3.1] - 2021-08-10 --- ### Fixed - TrackingCodeValueObject ### [1.3.0] - 2021-08-10 --- ### Changed - Result StatusCode ### Added - Logger - HomePhoneValueObject - MobilePhoneValueObject - DomainId - TrackingCodeValueObject ### [1.2.0] - 2021-08-09 --- ### Added - PasswordValueObject - passwordGenerator - CurrencyValueObject - EmailValueObject - UserNameValueObject - BirthdayValueObject ### [1.1.0] - 2021-07-28 --- ### Added - Dynamic types to Filter - Types validation to IBaseRepository ### [1.0.3] - 2021-07-16 --- ### Fixed - Define Node crash version on package.json ### [1.0.2] - 2021-07-09 --- ### Changed - Update documentation ================================================ FILE: CODE_OF_CONDUCT.md ================================================ # Contributor Covenant Code of Conduct ## Our Pledge We as members, contributors, and leaders pledge to make participation in our community a harassment-free experience for everyone, regardless of age, body size, visible or invisible disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community. ## Our Standards Examples of behavior that contributes to a positive environment for our community include: * Demonstrating empathy and kindness toward other people * Being respectful of differing opinions, viewpoints, and experiences * Giving and gracefully accepting constructive feedback * Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience * Focusing on what is best not just for us as individuals, but for the overall community Examples of unacceptable behavior include: * The use of sexualized language or imagery, and sexual attention or advances of any kind * Trolling, insulting or derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or email address, without their explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Enforcement Responsibilities Community leaders are responsible for clarifying and enforcing our standards of acceptable behavior and will take appropriate and fair corrective action in response to any behavior that they deem inappropriate, threatening, offensive, or harmful. Community leaders have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, and will communicate reasons for moderation decisions when appropriate. ## Scope This Code of Conduct applies within all community spaces, and also applies when an individual is officially representing the community in public spaces. Examples of representing our community include using an official e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to the community leaders responsible for enforcement at . All complaints will be reviewed and investigated promptly and fairly. All community leaders are obligated to respect the privacy and security of the reporter of any incident. ## Enforcement Guidelines Community leaders will follow these Community Impact Guidelines in determining the consequences for any action they deem in violation of this Code of Conduct: ### 1. Correction **Community Impact**: Use of inappropriate language or other behavior deemed unprofessional or unwelcome in the community. **Consequence**: A private, written warning from community leaders, providing clarity around the nature of the violation and an explanation of why the behavior was inappropriate. A public apology may be requested. ### 2. Warning **Community Impact**: A violation through a single incident or series of actions. **Consequence**: A warning with consequences for continued behavior. No interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, for a specified period of time. This includes avoiding interactions in community spaces as well as external channels like social media. Violating these terms may lead to a temporary or permanent ban. ### 3. Temporary Ban **Community Impact**: A serious violation of community standards, including sustained inappropriate behavior. **Consequence**: A temporary ban from any sort of interaction or public communication with the community for a specified period of time. No public or private interaction with the people involved, including unsolicited interaction with those enforcing the Code of Conduct, is allowed during this period. Violating these terms may lead to a permanent ban. ### 4. Permanent Ban **Community Impact**: Demonstrating a pattern of violation of community standards, including sustained inappropriate behavior, harassment of an individual, or aggression toward or disparagement of classes of individuals. **Consequence**: A permanent ban from any sort of public interaction within the community. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 2.0, available at https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. Community Impact Guidelines were inspired by [Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity). [homepage]: https://www.contributor-covenant.org For answers to common questions about this code of conduct, see the FAQ at https://www.contributor-covenant.org/faq. Translations are available at https://www.contributor-covenant.org/translations. ================================================ FILE: CONTRIBUTING.md ================================================ # Contributing When contributing to this repository, please first discuss the change you wish to make via issue, email, or any other method with the owners of this repository before making a change. Please note we have a code of conduct, please follow it in all your interactions with the project. ================================================ FILE: LICENSE ================================================ MIT License Copyright (c) 2021 Alessandro dev. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: Makefile ================================================ # Define the targets and the commands to be executed .PHONY: startVerdaccio stopVerdaccio publishVerdaccio addUser login build graph publish update # Start Verdaccio Docker container (local NPM registry) # This will install 'expect', pull the latest Verdaccio image, and run it. startVerdaccio: # Install the 'expect' package needed for some automation scripts sudo apt-get update && sudo apt-get install expect # Pull the latest nightly version of the Verdaccio image docker pull verdaccio/verdaccio:nightly-master # Run Verdaccio in detached mode with port 4873 exposed docker run -it -d --rm --name lib_verdaccio -p 4873:4873 verdaccio/verdaccio:nightly-master # Clear any existing NPM configuration echo "" > ./.npmrc # Set the registry to the local Verdaccio instance for project-specific use npm config set registry http://localhost:4873/ --location project # Stop the running Verdaccio Docker container stopVerdaccio: # Stop the Verdaccio container with the name 'lib_verdaccio' docker stop lib_verdaccio # Add a new user to the local NPM registry (Verdaccio) # This will execute the script that interacts with the registry to add a user addUser: ./scripts/make-user.sh # Log in to the local NPM registry (Verdaccio) # This will execute the login script to authenticate the user with Verdaccio login: ./scripts/login.sh # Build the project and its dependencies using npm # This includes running the general build process and the Lerna build process build: npm run build && npm run build:lerna # Build and publish packages to the local Verdaccio registry # This builds all packages using Yarn and Lerna and publishes them to the local registry publishVerdaccio: yarn build yarn build:lerna yarn lerna exec "npm publish --registry http://localhost:4873" # Generate and visualize the dependency graph of the project using Nx # This will show a graphical representation of the dependencies in the monorepo graph: yarn nx graph # Publish packages using Lerna # This command publishes packages to the specified registry using Lerna publish: yarn lerna publish # Update a specific peer dependency across all packages in the monorepo # Example usage: make update lib=rich-domain v=1.1.0 # This will update the specified peer dependency (e.g., 'rich-domain') to the given version (e.g., '1.1.0') in all package.json files in the ./packages directory # update peer dependency in all packages once update: ./update-peer-dependency.sh $(lib) $(v) ================================================ FILE: README.md ================================================ # @type-ddd/core > Now with individual packages This package provide utils file and interfaces to assistant build a complex application as domain driving design and nodeJS with typescript. --- checks stars commits last commit license dependabot tags issues --- ## Install core Install full available packages ```sh $ npm i @type-ddd/core # or $ yarn add @type-ddd/core ``` ## Individual Alternatively you can install individual packages - `@type-ddd/cpf` [Docs](./packages/cpf) - `@type-ddd/cnpj` [Docs](./packages/cnpj) - `@type-ddd/date` [Docs](./packages/date) - `@type-ddd/email` [Docs](./packages/email) - `@type-ddd/password` [Docs](./packages/password) - `@type-ddd/patterns` [Docs](./packages/patterns) - `@type-ddd/phone` [Docs](./packages/phone) - `@type-ddd/username` [Docs](./packages/username) - `@type-ddd/zip-code` [Docs](./packages/zip-code) - `@type-ddd/money` [Docs](./packages/money) --- image --- ## 1. Ubiquitous language: - Language and terms agreed upon by both business users and developers, within a bounded context - Entities with the same name in a different context can have different behavior and data - Bounded context helps in single responsibility for domain models ## 2. Rich domain model: - Models (entities, value objects, aggregates) with rich behavior are preferred over anemic domain models (entities without behavior, which only keep data and represent the DB tables) - Due to single responsibility principle (a class or method should have only one reason to change), non-cohesive behavior should be delegated to other classes (or even handled inside domain services) when necessary - Model methods can also delegate the task to domain services by raising domain events ## 3. Thin domain service working on rich domain models: - Domain services should not hold state (application services are not domain services, they are on the outer layer close to the UI layer, and can hold application/task state) - Domain services have very little behavior and only which does not fit cohesively in any domain model - Domain services sit in the core domain layer along with entities, value objects, aggregates and domain events, and expose domain models in their interfaces ## 4. Layers in a DDD application: - Core domain layer (domain services, entities, value objects, aggregates and domain events) - Core domain layer is surrounded by the UI/Application layer and Infrastructure layer - UI/Application layer (UI and application service facade with messaging, JSON, XML capabilities, session, etc.) - Infrastructure layer (persistence, file system, network, mail, logging, etc.) ## 5. Entities: - Live longer than the application, should endure restarts, and are persisted and read from data sources (DB, file system, network, etc.) - Have an id (preferably a GUID rather than a DB generated int because business transactions do not rely on persistence, can be persisted after other operations carried out in model's behavior) - Have entity semantics (equality and `GetHashCode()` defined by class name + id) - Behavior in an entity mostly orchestrates value objects for a use case - Entity class should not have public property setters, setting a property should be a behavior method - Entities should not have bidirectional relations (depending on the bounded context, either an egg can have a chicken or a chicken can have eggs, but not both) - Entity relations should not reflect the complete set of DB foreign key relationships, should be bare down to the minimum for performing the behavior inside the bounded context - Entity relations should not hold a reference to another entity class, it can only keep the id of another entity - If a business transaction needs a reference to other entities in relation, aggregates should be used instead (aggregates can hold a reference to other aggregate roots, which are entity classes by definition) ## 6. Value objects: - Are only identified by their values, not by their ids (for example money is a value object as long as we are not tracking individual banknotes, if we need to track individual banknotes then it should be a banknote entity) - Can be used to measure or describe things (name, description, amount, height, date, time, range, address, etc.) - You can combine other value types that usually go together into a new value object type, like address (city, street, country, postal code) or ...range, or ...type - Prefer to put the behavior on value objects rather than on entities because value objects are immutable and do not have side effects (like changing their state or changing the state of any entity) - Can be part of an entity - Have value semantics (equality and `GetHashCode()` defined by property values) - Should be immutable, behaviors should not change the state of a value object, but can rather create a new value object (should act similar to C# strings, structs, ints, and other value types) - Can be persisted but only as part of an entity, not individually ## 7. Factories: - Create, build aggregates and entities: - Static Create...() factory method on a model class is used to guard against the construction of an invalid or incomplete model - The model class should not have a public default constructor (however if it is to be persisted, for Entity Framework to work, it can have a protected or private default constructor) ## 8. Aggregates: - Encapsulate and are composed of entity classes and value objects that change together in a business transaction - Aggregates are a transactional graph of model objects - Aggregate root should be an entity, an aggregate can even be a single entity - Aggregate can keep a reference to other aggregate roots, but not to other entity classes which are not aggregate roots themselves - Aggregate should not keep a reference to other aggregate root entity classes if those other entities do not change together with this aggregate root entity - Aggregate can also keep the id of another entity, but keeping too many foreign key ids is a code smell (why?) - If deleting an entity has a cascade effect on the other entities referenced by class in the object graph, these entities are part of the same aggregate, if not, they should not be inside this aggregate ## 9. Repositories: - Persist and read aggregates to/from DB or file system - Should have an interface close to a collection but should allow only the necessary operations needed for this aggregate (for example an aggregate might not need to be allowed to get updated or deleted) - Should not be generic (should be specific for the aggregate type) - Can have specific query methods if needed (like `FindByName()` etc.) - Do not use lazy loading, instead use eager loading (use Include(...) in Entity Framework), else you can face "N+1 problem"s and excessive number of queries sent to DB - Can have specific methods that only load some of the columns from a table - Repository add/update/remove operation should commit to DB by itself (call Entity Framework ...Context.SaveChanges() at the end), because aggregate operations should be ACID transactions - Repository interface sits inside Core domain layer, but implementations are inside Infrastructure layer - Repositories are not used inside the domain models (entities, value objects, aggregates) ## 10. Shared kernel: - Is where cross-cutting concerns or common types shared by all bounded contexts sit (like entity abstract base type, value object abstract base type, common value objects, authorization, etc.) ## 11. Domain events: - Can be raised when a state change occurs in an entity - Decouple models from each other - Only used when an event needs to be handled inside a different model than the one raising this event, or handled inside a domain service or even an application service - Are immutable classes, that represent past, named in the past tense, and cannot change (...Changed, ...Happened, etc.) - Should include the time that this event was raised, as well as any other useful info for handling the event, as well as the id of the entity which raised the event - Should not have behavior - Domain events are subscribed to with a callback (lambda), or using pub sub interfaces, on a singleton or static event message bus - Domain events implemented this way can be subscribed to and handled in the aggregate root of the entity which raised the event, or in domain services, or even in UI/Application layer - Domain events are raised synchronously, if an asynchronous task needs to be carried out, it can be done inside the event handler (async-await pattern) - Outside applications can also be triggered by using a message queue or an enterprise service bus (ESB) inside the domain event handler ## 12. Anti-corruption layer: - Used to translate models from outside systems or legacy apps to models inside the bounded context and vice versa, and also to ease the communication with legacy services - Can use service facades and model adapters --- ## Individual Packages Install individual package - `@type-ddd/cpf` [Docs](./packages/cpf) - `@type-ddd/cnpj` [Docs](./packages/cnpj) - `@type-ddd/date` [Docs](./packages/date) - `@type-ddd/email` [Docs](./packages/email) - `@type-ddd/password` [Docs](./packages/password) - `@type-ddd/patterns` [Docs](./packages/patterns) - `@type-ddd/phone` [Docs](./packages/phone) - `@type-ddd/username` [Docs](./packages/username) - `@type-ddd/zip-code` [Docs](./packages/zip-code) - `@type-ddd/money` [Docs](./packages/money) --- ### Value Object > A value object is a small, simple object that represents a single value or characteristic, such as a monetary amount or a date. It is characterized by having no identity of its own, meaning it is equal to another value object if its values are equal, regardless of its reference. Value objects are often used in domain-driven design to represent simple entities in the system. #### Create a value object with business rules. ```ts import { ValueObject, Ok, Fail, Result } from '@type-ddd/core'; interface Props { amount: number; } // simple example as monetary value object business behavior export default class Money extends ValueObject { // private constructor. Avoid public new. private constructor(props: Props) { super(props); } // any business rule behavior. Check. public isGt(x: Money): boolean { const { number: Check } = this.validator; const xValue = x.get('amount'); const currentValue = this.get('amount'); return Check(xValue).isGreaterThan(currentValue); } // any business rule behavior. Calc. public sum(x: Money): Money { const { number: Calc } = this.util; const value = x.get('amount'); const current = this.get('amount'); const amount = Calc(current).sum(value); return new Money({ amount }); } // any business rule behavior. Calc. public subtract(x: Money): Money { const { number: Calc } = this.util; const value = x.get('amount'); const current = this.get('amount'); const amount = Calc(current).subtract(value); return new Money({ amount }); } // any business rule to validate state. public static isValidProps({ amount }: Props): boolean { const { number: Check } = this.validator; return Check(amount).isPositive(); } // shortcut to create a zero value public static zero(): Money { return new Money({ amount: 0 }); } // factory method to create an instance and validate value. public static create(amount: number): Result { const isValid = this.isValidProps({ amount }); if(!isValid) return Fail("Invalid amount for money"); return Ok(new Money({ amount })); } } ``` How to use value object instance ```ts // operation result const resA = Money.create(500); // check if provided a valid value console.log(resA.isOk()); // > true // money instance const moneyA = resA.value() as Money; moneyA.get("amount"); // 500 // using methods moneyA.isGt(Money.zero()); // > true const moneyB = Money.create(100).value() as Money; const moneyC = moneyA.sum(moneyB); const value = moneyC.get('amount'); console.log(value); // > 600 ``` --- ### Entity > An entity in domain-driven design is an object that represents a concept in the real world and has a unique identity and attributes. It is a fundamental building block used to model complex business domains. #### Create an entity with business rules. ```ts import { Entity, Ok, Fail, Result, UID } from '@type-ddd/core'; interface Props { id?: UID; total: Money; discount: Money; fees: Money; } // simple example as payment entity using money value object export default class Payment extends Entity { // private constructor private constructor(props: Props){ super(props); } // any business rule behavior. Update total. public applyFees(fees: Money): Payment { const props = this.props; const total = props.total.sum(fees); return new Payment({ ...props, total, fees }); } // any business rule behavior. Discount must be less or equal total. public applyDiscount(discount: Money): Payment { const props = this.props; const total = props.total.subtract(discount); return new Payment({ ...props, total, discount }); } // factory method to create a instance. Value must be positive. public static create(props: Props): Result { return Ok(new Payment(props)); } } ``` How to use entity instance ```ts // operation result const total = Money.create(500).value() as Money; const discount = Money.zero(); const fees = Money.zero(); // create a payment const payment = Payment.create({ total, discount, fees }).value() as Payment; // create fee and discount const fee = Money.create(17.50).value(); const disc = Money.create(170.50).value(); // apply fee and discount const result = payment.applyFees(fee).applyDiscount(disc); // get object from domain entity console.log(result.toObject()); { "id": "d7fc98f5-9711-4ad8-aa16-70cb8a52244a", "total": { "amount": 347 }, "discount": { "amount": 170.50 }, "fees": { "amount": 17.50 }, "createdAt":"2023-01-30T23:11:17.815Z", "updatedAt":"2023-01-30T23:11:17.815Z" } ``` ### Aggregate Encapsulate and are composed of entity classes and value objects that change together in a business transaction #### Create an aggregate to compose your context. In my example, let's use the context of payment. All payment transactions are encapsulated by an order (payment order) that represents a user's purchasing context. ```ts import { Aggregate, Ok, Fail, Result, UID, EventHandler } from '@type-ddd/core'; // Entities and VO that encapsulate context. interface Props { id?: UID; payment: Payment; items: List; status: OrderStatus; customer: Customer; } // Simple example of an order aggregate encapsulating entities and // value objects for context. export default class Order extends Aggregate { // Private constructor to ensure instances creation through static methods. private constructor(props: Props){ super(props); } // Static method to begin a new order. // Takes a customer as parameter and returns an instance of Order. public static begin(customer: Customer): Order { // Initialize the status of the order as "begin". const status = OrderStatus.begin(); // Initialize the list of items as empty. const items: List = List.empty(); // Initialize the payment as zero, since the order hasn't been paid yet. const payment = Payment.none(); // Create a new instance of Order with the provided parameters. const order = new Order({ status, payment, items, customer }); // Add an event to indicate that the order has begun. order.addEvent('ORDER_HAS_BEGUN', (order) => { // Perform some important operation when the order begins. console.log('Do something important...'); }); // Alternatively, add an event by creating an // instance of a class that extends EventHandler. order.addEvent(new OrderBeganEventHandler()); // Return the created order instance. return order; } // Method to add an item to the order. // Takes an item as parameter and returns the Order instance. addItem(item: Item): Order { // Add the item to the order's items list. this.props.items.add(item); // Sum item price to payment amount this.props.payment.sum(item.price); // Return the Order instance itself to allow chained calls. return this; } // Method to perform the payment of the order. // Takes a payment object as parameter. pay(payment: Payment): Order { // Set the status of the order to "paid". this.props.status = OrderStatus.paid(); // Set the provided payment object. this.props.payment = payment; // Add an event to indicate that the order has been paid. // Assuming OrderPaidEvent is a class representing // the event of order payment. this.addEvent(new OrderPaidEventHandler()); return this; } // Static method to create an instance of Order. // Returns a Result, which can be Ok (success) or Fail (failure). // The value of the Result is an instance of Order, // if creation is successful. public static create(props: Props): Result { return Ok(new Order(props)); } } ``` #### How to use events Event Handler ```ts import { Context, EventHandler } from '@type-ddd/core'; class OrderCreatedEvent extends EventHandler { constructor() { super({ eventName: 'OrderCreated' }); } dispatch(order: Order): void { // dispatch event to another context order.context().dispatchEvent('Context:Event', order.toObject()); }; } ``` Aggregates domain events ```ts order.addEvent('Event', (...args) => { console.log(args); }); // Or add an EventHandler instance order.addEvent(new OrderCreatedEvent()); order.dispatchEvent('OrderBegun'); // dispatch with args order.dispatchEvent('Event', { info: 'custom_args' }); // OR call all added events await order.dispatchAll(); ``` #### How to subscribe to a global event ```ts import { Context } from '@type-ddd/core'; const context = Context.events(); context.subscribe('Context:Event', (event) => { const [model] = event.detail; console.log(model); }); // dispatch an event to a context with args context.dispatchEvent('Context:Event', { name: 'Jane' }); // Dispatching events to specific contexts // Dispatches the SIGNUP event to Context-X context.dispatchEvent('Context-X:Signup'); // Dispatches the SIGNUP event to all contexts context.dispatchEvent('*:Signup'); // Dispatches all events to all contexts. Not recommended context.dispatchEvent('*:*'); // Dispatches all events under Context-Y context.dispatchEvent('Context-Y:*'); ``` ================================================ FILE: SECURITY.md ================================================ # Security Policy ## Supported Versions Versions of types-ddd are currently being supported with security updates. | Version | Supported | | ------- | ------------------ | | 4.x.x | :white_check_mark: | | 3.9.x | :white_check_mark: | | <= 3.8 | :x: | ## Reporting a Vulnerability If you find some vulnerability please report as issue. Every month security updates are published as 0.0.x version. ================================================ FILE: check-dev-deps.sh ================================================ #!/bin/sh DEV_DEPS="$(cat package.json | grep -A 100 "devDependencies" | grep -B 100 "\}\," | \ awk "NR>1" | sed -e "s/},//" | tr -d '":.^0-9,')"; for dep in "$(echo $DEV_DEPS)"; do echo $dep | sed -e 's/ /\n/g' > deps; done; while IFS= read -r line; do yarn list --depth 0 | grep $line@ done < ./deps; rm -rf ./deps ================================================ FILE: docs/README.md ================================================ # types-ddd documentation ## About the lib The library was created to support developers in developing domain-rich applications. Full documentation. Version 3.x This lib use as core [rich-domain](https://www.npmjs.com/package/rich-domain) ### Simple App Example A simple app example available on [link](https://github.com/4lessandrodev/ddd-app) --- ## Documentation ### Folders Folders structure suggestion Divided by - Domain layer - Application layer - Infra layer ```shell $ tree . ├── package.json ├── README.md └── src ├── configs │ └── env │ ├── shared │ └── infra │ └── server │ └── modules │ └── [module-name] │ │── domain │ ├── value-objects │ ├── entities │ ├── aggregates │ ├── events │ ├── subscriptions │ ├── adapter │ ├── repository-interface │ └── domain-services │ ├── application │ └── use-cases │ └── infra ├── models └── repository ``` --- ### Result What is Result: `Result` is a class that encapsulates the result of an operation and stores the success or failure state without throws the application. #### Interface and Generic Types - A = `Payload` optional default `void` - B = `Error` optional default `string` - C = `MetaData` optional default `{}` ```ts IResult; ``` Alternative shortcuts ```ts import { Ok, Fail } from 'types-ddd'; // Success use case return Ok(); // OR return Ok('success message'); // Failure use case return Fail('error message here'); ``` Example how to use generic types. First let's create our interfaces to use as generic type. - The type of data to be retrieved can be any type you want. ```tS // Payload type interface IData { data: string }; // Error type interface IError { message: string }; // MetaData type interface IMeta { arg: number }; ``` Now let's implement a function that return the result below ```ts IResult; ``` So let's implement that on a simple function. ```ts const isEven = (value: number): Result => { const isPairValue = value % 2 === 0; const metaData: IMeta = { arg: value }; if (isPairValue) { // success payload const payload: IData = { data: `${value} is even` }; // return success return Ok(payload, metaData); } // failure payload const error: IError = { message: `${value} is not even` }; // return failure return Fail(error, metaData); }; ``` Here we have a function that returns success if the value informed is even and returns failure if it is odd. Success Case ```ts const result = isEven(42); console.log(result.isOk()); > true console.log(result.value()); > 'Object { data: "42 is even" }' console.log(result.metaData()); > 'Object { arg: 42 }' console.log(result.error()); > null ``` Failure Case ```ts const result = isEven(43); console.log(result.isFail()); > true console.log(result.error()); > 'Object { message: "43 is not even" }' console.log(result.metaData()); > 'Object { arg: 43 }' console.log(result.value()); > null ``` #### Void The most simple void success example.
Let's see the same example using void. ```ts const checkEven = (value: number): Result => { const isPair = value % 2 === 0; // success case if(isPair) return Ok(); // failure case return Fail('not enven'); } ``` Using the function as success example ```ts const result: Result = checkEven(42); console.log(result.isOk()); > true console.log(result.isFail()); > false console.log(result.error()); > null console.log(result.value()); > null console.log(result.metaData()); > 'Object {}' ``` Fail example ```ts const result: Result = checkEven(43); console.log(result.isFail()); > true console.log(result.isOk()); > false console.log(result.error()); > "not even" console.log(result.value()); > null console.log(result.metaData()); > 'Object {}' ``` #### toObject method you can get a summarized object with the properties of an instance of a `Result` ```ts console.log(result.toObject()); > Object `{ "data": null, "error": "not even", "isFail": true, "isOk": false, "metaData": Object {} }` ``` #### Hooks In the instances of a Result there are two hooks that allow the execution of a command according to the state. ```ts class Command implements ICommand { execute(): void { console.log("running command ..."); } } const myCommand = new Command(); const result = Result.Ok(); result.execute(myCommand).on('Ok'); > "running command ..." ``` You might also want to pass arguments to the command ```ts class Command implements ICommand { execute(error: string): void { console.log(error); } } const myCommand = new Command(); const result = Result.fail('something went wrong'); result.execute(myCommand).withData(result.error()).on('fail'); > "something went wrong" ``` #### Combine You can use the static `combine` function of `Result` to check many instances if any are failed it will return the instance with error state. Success example ```ts const resultA = Result.Ok(); const resultB = Result.Ok(); const resultC = Result.Ok(); const result = Result.combine([resultA, resultB, resultC]); console.log(result.isOk()); > true ``` Failure example ```ts const resultA = Result.Ok(); const resultB = Result.fail('oops err'); const resultC = Result.Ok(); const result = Result.combine([resultA, resultB, resultC]); // OR you can use Combine function const result = Combine([resultA, resultB, resultC]); console.log(result.isOk()); > false console.log(result.error()); > 'oops err' ``` --- ### ID What is ID: A symbol which uniquely identifies an object or record.
In this Lib all IDs are generated by domain and uses uuid v4. #### Create New Create a new uuid. ```ts // ID main const id = ID.create(); // OR Id as function const id = Id(); // OR id instance const id = id.create(); console.log(id.value()); > "eb9c563c-719d-4872-b303-0a82921351f7" ``` #### Short New Create a short id ```ts const id = ID.short(); console.log(id.value()); > "EB9C563DB4872BF7" ``` #### Create Existing Create a id with provided value ```ts const id = ID.create('this-is-my-id-01'); console.log(id.value()); > "this-is-my-id-01" ``` #### Compare ids The id instance has a method to compare two ids. ```ts const idA = ID.short('this-is-my-id-01'); const idB = ID.short('this-is-my-id-02'); console.log(idA.equal(idB)); > false console.log(idB.equal(idB)); > true ``` #### IsNew Check if id instance is has a new value ```ts const idA = ID.create('this-is-my-id-01'); const idB = ID.create(); console.log(idA.isNew()); > false console.log(idB.isNew()); > true ``` #### Type for ID Define type for ID ```ts import { UID, ID } from 'types-ddd'; // UID type let id: UID; // ID value id = ID.create(); ``` --- ### ValueObject What is value object: - Are only identified by their values, not by their ids (for example money is a value object as long as we are not tracking individual banknotes, if we need to track individual banknotes then it should be a banknote entity) - Can be used to measure or describe things (name, description, amount, height, date, time, range, address, etc.) - You can combine other value types that usually go together into a new value object type, like address (city, street, country, postal code) or ...range, or ...type - Prefer to put the behavior on value objects rather than on entities because value objects are immutable and do not have side effects (like changing their state or changing the state of any entity) - Can be part of an entity - Should be immutable, behaviors should not change the state of a value object, but can rather create a new value object (should act similar to C# strings, structs, ints, and other value types) - Can be persisted but only as part of an entity, not individually. #### Simple Value Object. Value objects extend to `ValueObject` class have private constructor and public static method called `create`.
The `create` method receives the props which by default is an object with the key `value`. the value object below is a base example without any kind of validation ```ts import { IResult, Result, ValueObject } from "types-ddd"; export interface NameProps { value: string; } export class Name extends ValueObject{ private constructor(props: NameProps) { super(props); } public static create(value: string): IResult { return Ok(new Name({ value })); } } export default Name; ``` Now that we have defined our value object class, we can create an instance.
The `create` method returns an instance of Name encapsulated by the `Result`, so it is important to always assess whether the result is a success before getting the value. ```ts const result = Name.create('Jane'); console.log(result.isOk()); > true const name = result.value(); console.log(name.get('value')); > "Jane" ``` Once we have an instance of a value object, we can use some methods that the library makes available. By default setters are enabled ```ts // Prefer to create new instance instead of changing value to value object. Do not use set to value object. name.set('value').to('John'); console.log(name.get('value')); > "John" ``` When you use the `set` or `change` function to modify the state, each change is saved in a history ```ts console.log(name.history().count()); > 2 // back to old value on history name.history().back(); console.log(name.get('value')); > "Jane" ``` > **We don't advise you to use state change of a value object. Create a new one instead of changing its state. However the library will leave that up to you to decide.** To disable the setters of a value object use the parameters below in the super.
This property disables the set function of the value object. ```ts constructor(props: NameProps){ super(props, { disableSetters: true }) } ``` Now when trying to change the value using `set` or `change` it will not be modified. ```ts console.log(name.get('value')); > "John" name.set('value').to('James'); console.log(name.get('value')); > "John" ``` #### Using validation Validation before create instance.
A validator instance is available in the "Value Object" domain class. ```ts import { Ok, Fail, Result, ValueObject } from "types-ddd"; export interface NameProps { value: string; } export class Name extends ValueObject{ private constructor(props: NameProps) { super(props); } public static isValidProps({ value }: NameProps): boolean { const { string } = this.validator; return string(value).hasLengthBetweenOrEqual(3, 30); } public static create(value: string): Result { const message = 'name must have length min 3 and max 30 char'; if (!this.isValidProps({ value })) return Fail(message); return Ok(new Name({ value })); } } export default Name; ``` Now when you try to instantiate a name, the value will be checked and if it doesn't meet the validation requirements, a `Result` will be returned with an error state. ```ts const empty = ''; const result = Name.create(empty); console.log(result.isFail()); > true console.log(result.error()); > "name must have length min 3 and max 30 char" console.log(result.value()); > null ``` #### Validation before set The `isValidProps` Method validates properties when creating a new instance, but which method validates before modifying a value? For this there is the method `validation` The validation method takes two arguments, the first the `key` of props and the second the `value`. So when calling the `set` or `change` function, this method will be called automatically to validate the value, if it doesn't pass the validation, the value is not changed. > There must be a validation for each "props" key ```ts validation(value: Props[Key], key: Key): boolean { const { number } = this.validator; const options: IPropsValidation = { value: (value: number) => number.isBetween(0, 130), } return options[key](value); }; ``` In case your value object has only one attribute you can simply use the already created static validation method.
Let's see a complete example as below ```ts import { Result, Result, ValueObject } from "types-ddd"; export interface NameProps { value: string; } export class Name extends ValueObject{ private constructor(props: NameProps) { super(props); } validation(value: string): boolean { return Name.isValidProps({ value }); } public static isValidProps({ value }: NameProps): boolean { const { string } = this.validator; return string(value).hasLengthBetween(3, 30); } public static create(value: string): IResult { const message = 'name must have length min 3 and max 30 char'; if (!this.isValidProps({ value })) return Fail(message); return Ok(new Name({ value })); } } export default Name; ``` Let's test the instance and the validation method.
Value is not modified if it does not pass validation. ```ts const result = Name.create('Jane'); console.log(result.isOk()); > true const name = result.value(); console.log(name.get('value')); > "Jane" const empty = ''; name.set('value').to(empty); console.log(name.get('value')); > "Jane" name.set('value').to("Jack"); console.log(name.get('value')); > "Jack" ``` #### toObject This method transforms a complex object into a simple object or value.
This method is useful for cases where you have value objects inside other value objects ```ts const street = Street.create('Dom Juan').value(); const number = Number.create(42).value(); const result = Address.create({ street, number }); const address = result.value(); console.log(address.toObject()); > Object `{ "street": "Dom Juan", "number": 42, }` ``` #### Clone This method creates a new instance with the same properties as the current value object. ```ts const result = Name.create('Sammy'); const originalName = result.value(); console.log(originalName.value()); > "Sammy" const clone = originalName.clone(); console.log(clone.isOk()); > true const clonedName = clone.value(); console.log(clonedName.value()); > "Sammy" ``` Clone being a new instance does not change the properties of the original value object ```ts clonedName.change('value', 'Jones'); console.log(clonedName.value()); > "Jones" console.log(originalName.value()); > "Sammy" ``` #### createMany Sometimes you will need to create many instances of different value objects and for that there is static method available `createMany` on value objects, entity and aggregate. ```ts const itemPrice = Class(ProductPrice, { value: price }); const itemName = Class(ProductName, { value: name }); const itemQtd = Class(ProductQtd, { value: qtd }); const { data, result } = ValueObject.createMany([ itemPrice, itemName, itemQtd ]); // you check if all value objects are ok if (result.isFail()) return Result.fail(result.error()); // you can get instances from iterator data. In the same order as the array const price = data.next().value() as ProductPrice; // index 0 const name = data.next().value() as ProductName; // index 1 const quantity = data.next().value() as ProductQtd; // index 2 const product = Product.create({ name, price, quantity }); ``` --- ### Entity What is value object: - Live longer than the application, should endure restarts, and are persisted and read from data sources (DB, file system, network, etc.) - Have an id (preferably a GUID rather than a DB generated int because business transactions do not rely on persistence, can be persisted after other operations carried out in model's behavior) - Have entity semantics (equality and `GetHashCode()` defined by class name + id) - Behavior in an entity mostly orchestrates value objects for a use case - Entity class should not have public property setters, setting a property should be a behavior method - Entities should not have bidirectional relations (depending on the bounded context, either an egg can have a chicken or a chicken can have eggs, but not both) - Entity relations should not reflect the complete set of DB foreign key relationships, should be bare down to the minimum for performing the behavior inside the bounded context - Entity relations should not hold a reference to another entity class, it can only keep the id of another entity - If a business transaction needs a reference to other entities in relation, aggregates should be used instead (aggregates can hold a reference to other aggregate roots, which are entity classes by definition) #### Simple Entity Entities extend to `Entity` class, have private constructor and public static method called `create`. The `create` method receives the props which by default is an object with the key `id`. the entity below is a base example without any kind of validation ```ts interface UserProps { id?: UID, name: Name, age: Age }; export class User extends Entity{ private constructor(props: UserProps){ super(props) } public static create(props: UserProps): IResult { return Result.Ok(new User(props)); } } export default User; ``` `id` is a reserved word and must have the type `UID` or `string`. All attributes for an entity must be value object except id. ```ts const nameAttr = Name.create('James'); const ageAttr = Age.create(21); // always check if value objects are success const voResult = Combine([ nameAttr, ageAttr ]) console.log(voResult.isOk()); > true const name = nameAttr.value(); const age = ageAttr.value(); // if you don't provide a value for the id it will be generated automatically const result = User.create({ name, age }); console.log(result.isOk()); > true ``` #### toObject when you extend entity class you get some methods from domain class, one of them is `toObject` method.
In the entity, this method aims to transform a domain class into a simple object, that is, all value objects are transformed into simple attributes. ```ts const user = result.value(); console.log(user.toObject()); > Object `{ age: 21, name: "James", createdAt: "2022-08-13T03:51:25.738Z", updatedAt: "2022-08-13T03:51:25.738Z" id: "0709220f-7c2f-41e2-b535-151926286893", }` ``` #### with id value you can create an instance by entering an id ```ts const name = nameAttr.value(); const id = ID.create('a3a5ea9d-7c57-4743-8a9b-5315fad365d0'); const result = User.create({ id, age, name }); console.log(result.isOk()); > true const user = result.value(); console.log(user.toObject()); > Object `{ age: 21, name: "James", createdAt: "2022-08-13T03:51:25.738Z", updatedAt: "2022-08-13T03:51:25.738Z" id: "a3a5ea9d-7c57-4743-8a9b-5315fad365d0", }` ``` #### isNew Check if instance is a new entity.
if you provide do not provide an id the entity will be considered as a new created entity instance. ```ts // no id provided const newUserResult = User.create({ name, age }); cons newUser = newUserResult.value(); console.log(newUser.isNew()); > true // id provided const userResult = User.create({ id, name, age }); cons user = userResult.value(); console.log(user.isNew()); > false ``` #### isValidProps Validating props before create an instance.
Here you can apply your business validation. ```ts public static isValidProps({ name, age }: UserProps): boolean { // your business validation const isValidName = doSomeBusinessValidation(name); const isValidAge = doSomeBusinessValidation(age); return isValidName && isValidAge; } ``` Let's apply our props validation method to our entity class ```ts interface UserProps { id?: UID, name: Name, age: Age }; export class User extends Entity{ private constructor(props: UserProps){ super(props) } public static isValidProps({ name, age }: UserProps): boolean { // your business validation const isValidName = doSomeBusinessValidation(name); const isValidAge = doSomeBusinessValidation(age); return isValidName && isValidAge; } public static create(props: UserProps): IResult { const isValidRules = User.isValidProps(props); if(!isValidRules) return Result.fail('invalid props'); return Result.Ok(new User(props)); } } export default User; ``` #### change in entities you can easily change an attribute with `change` or `set` method ```ts const result = Name.create('Larry'); const newName = result.value(); user.change("name", newName); console.log(user.get("name").value()); > "Larry" ``` #### Validation before change The `isValidProps` Method validates properties when creating a new instance, but which method validates before modifying a value?
For this there is the method `validation` The validation method takes two arguments, the first the `key` of props and the second the `value`. So when calling the `set` or `change` function, this method will be called automatically to validate the value, if it doesn't pass the validation, the value is not changed. > There must be a validation for each "props" key ```ts validation(value: Props[Key], key: Key): boolean { const options: IPropsValidation = { name: (value: Name) => doSomeBusinessValidation(value), age: (value: Age) => doSomeBusinessValidation(value) } return options[key](value); }; ``` Let's apply our validation method to our entity.
Now if the validation does not pass the value will not be changed. ```ts interface UserProps { id?: UID, name: Name, age: Age }; export class User extends Entity{ private constructor(props: UserProps){ super(props) } validation(value: Props[Key], key: Key): boolean { const options: IPropsValidation = { name: (value: Name) => doSomeBusinessValidation(value), age: (value: Age) => doSomeBusinessValidation(value) } return options[key](value); }; public static isValidProps({ name, age }: UserProps): boolean { // your business validation const isValidName = doSomeBusinessValidation(name); const isValidAge = doSomeBusinessValidation(age); return isValidName && isValidAge; } public static create(props: UserProps): IResult { const isValidRules = User.isValidProps(props); if(!isValidRules) return Result.fail('invalid props'); return Result.Ok(new User(props)); } } export default User; ``` #### disableSetters To disable the setters of an entity use the parameters below in the super.
This property disables the set function of the entity. ```ts constructor(props: NameProps){ super(props, { disableSetters: true }) } ``` #### clone entity you can clone an entity and get a new instance ```ts const result = User.create({ id, age, name }); console.log(result.isOk()); > true const user = result.value(); const clonedUser = user.clone(); const newUser = clonedUser.value(); const newNameResult = Name.create('Luke'); const newName = newNameResult.value(); clonedUser.set('name').to(newName); console.log(user.get('name').value()); > "James" console.log(clonedUser.get('name').value()); > "Luke" ``` #### compare entities You can compare two entities. `compare` just check props values and id value. `deepEqual` check props values, id, types and history. ```ts const isEqual = user1.isEqual(user2); console.log(isEqual); > false ``` #### history Each operation to change any entity state property generates a history.
At any time you can return to a previous state ```ts const result = User.create({ name, age }); const user = result.value(); console.log(user.toObject()); > Object `{ age: 21, name: "James", createdAt: "2022-08-13T03:51:25.738Z", updatedAt: "2022-08-13T03:51:25.738Z", id: "0709220f-7c2f-41e2-b535-151926286893" }` // first history is initial props on create an instance console.log(user.history().count()); > 1 user.set('name').to(newName); console.log(user.toObject()); > Object `{ age: 21, name: "Luke", createdAt: "2022-08-13T03:51:25.738Z", updatedAt: "2022-08-13T03:52:25.738Z", id: "0709220f-7c2f-41e2-b535-151926286893" }` // On change name create a new history console.log(user.history().count()); > 2 // back to history 1 user.history().back(); console.log(user.toObject()); > Object `{ age: 21, name: "James", createdAt: "2022-08-13T03:51:25.738Z", updatedAt: "2022-08-13T03:51:25.738Z", id: "0709220f-7c2f-41e2-b535-151926286893" }` ``` --- ### Aggregate What is aggregate: - Encapsulate and are composed of entity classes and value objects that change together in a business transaction - Aggregates are a transactional graph of model objects - Aggregate root should be an entity, an aggregate can even be a single entity - Aggregate can keep a reference to other aggregate roots, but not to other entity classes which are not aggregate roots themselves - Aggregate should not keep a reference to other aggregate root entity classes if those other entities do not change together with this aggregate root entity - Aggregate can also keep the id of another entity, but keeping too many foreign key ids is a code smell (why?) - If deleting an entity has a cascade effect on the other entities referenced by class in the object graph, these entities are part of the same aggregate, if not, they should not be inside this aggregate The aggregate has the same methods already mentioned in the entity. And in addition to the entity methods, there is another one that is responsible for managing the `domain's events`. #### Simple Aggregate ```ts export interface ProductProps { id?: UID; name: ProductName; price: ProductPrice; createdAt?: Date; updatedAt?: Date; } // extends to Aggregate export class Product extends Aggregate{ private constructor(props: ProductProps) { super(props); } public static create(props: ProductProps): IResult { return Result.Ok(new Product(props)); } } export default Product; ``` #### Domain Event Let's create an aggregate instance and see how to add domain event to it. ```ts export class ProductCreatedEvent implements IHandle{ public eventName: string; constructor() { this.eventName = 'ProductCreated'; } dispatch(event: IDomainEvent): void { // your action here const { aggregate } = event; console.log(`EVENT DISPATCH: ${aggregate.hashCode().value()}`); } } export default ProductCreatedEvent; ``` Now let's add the event to a product instance.
Events are stored in memory and are deleted after being triggered. ```ts const result = Product.create({ name, price }); const product = result.value(); const event = new ProductCreatedEvent(); product.addEvent(event); ``` Now we can dispatch the event whenever we want. ```ts product.dispatch("ProductCreated"); > "EVENT DISPATCH: [Aggregate@Product]:6039756f-d3bc-452e-932a-ec89ff536dda" ``` --- ### Adapter How to adapt the data from persistence to domain or from domain to persistence. ```ts // from domain to data layer class MyAdapterToInfra implements IAdapter{ build(target: DomainUser): Result { // ... } } // from data layer to domain class MyAdapterToDomain implements IAdapter{ build(target: DataUser): Result { // ... } } // You can use adapter instance in toObject function const myAdapter = new MyAdapterToInfra(); const dataUser = domainUser.toObject(myAdapter); ``` --- ### Utils Some util tools available #### Ready to use - ✔ EmailValueObject - ✔ UserNameValueObject - ✔ BirthdayValueObject - ✔ CurrencyValueObject - ✔ PasswordValueObject - ✔ HomePhoneValueObject - ✔ MobilePhoneValueObject - ✔ TrackingCodeValueObject - ✔ RGBColorValueObject - ✔ HEXColorValueObject - ✔ PostalCodeValueObject - ✔ UrlValueObject - ✔ OrderStatusValueObject - ✔ PinValueObject - ✔ CPFValueObject - ✔ CNPJValueObject - ✔ CustomStringValueObject - ✔ CustomNumberValueObject - ✔ WeightUnitValueObject - ✔ UnitOfMeasureValueObject - ✔ DimensionValueObject - ✔ WeightValueObject - ✔ DateValueObject - ✔ getUndefinedKeysAsArray - ✔ getUndefinedKeysAsObject - ✔ removeUndefinedKeysFromObject - ✔ SpecificationComposite - ✔ FactoryMethod - ✔ TSProxy --- #### Password Just import and use ```ts const passOrError = PasswordValueObject.create('my-strength-pass'); console.log(passOrError.isOk()); > true const pass = passOrError.value(); pass.encrypt(); console.log(pass.value()); > "$2a$12$AdLoTarjC5wnc1tAUc3j1.RczGxxImH0mG6dZkS5zPaGrTi/EmPWG" console.log(pass.isEncrypted()); > true const passMatch = pass.compare('my-strength-pass'); console.log(passMatch); > true console.log(PasswordValueObject.random(12).value()); > "WtS65$@!A6by" ``` you can define a custom range for password length and error message ```ts Reflect.set(PasswordValueObject, "MIN_LENGTH", 10); Reflect.set(PasswordValueObject, "MAX_LENGTH", 20); Reflect.set(PasswordValueObject, "MESSAGE", "Password must be between 10 and 20 characters"); ``` #### Date Just import and use ```ts const currentDate = new Date(); const myDate = DateValueObject.create(currentDate).value(); console.log(myDate.value()); > "2021-10-11T14:45:04.758Z" console.log(myDate.format("DD-MM-YYYY")); > "11-10-2021" myDate.addDays(3); console.log(myDate.value()); > "2021-10-14T14:45:04.758Z" const isWeekend = myDate.isWeekend(); console.log(isWeekend); > false myDate.addHours(7); const isAfter = myDate.isAfter(currentDate); console.log(isAfter); > true ``` #### Currency Just import and use ```ts const voOrErr = CurrencyValueObject.create({ currency: 'BRL', value: 0.50 }); const myCurrency = voOrErr.value(); console.log(myCurrency.value()); > 0.5 myCurrency.add(0.50); // 1 myCurrency.multiplyBy(50); // 50 myCurrency.divideBy(2); // 25 myCurrency.subtractBy(5); // 20 myCurrency.add(80); // 100 myCurrency.addPercent(2); // 102 myCurrency.subtractBy(2); // 100 myCurrency.subtractPercent(30); // 70 console.log(myCurrency.value()); > 70 console.log(myCurrency.getCoin()); > "R$ 70.00" // OR chain const result = myCurrency.add(10).addPercent(21).multiplyBy(3).subtractBy(50); ``` #### Weight units Just import and use ```ts const result = WeightValueObject.create({ value: 1000, unit: "TON" }); console.log(result.isOk()); > true const weight = result.value(); console.log(weight.unit); > "TON" console.log(weight.weight.value()); > 1000 // Convert instance value and unit to KG weight.toKG(); console.log(weight.unit); > "KG" console.log(weight.weight.value()); > 1 ``` #### Email Just import and use ```ts const result = EmailValueObject.create('dany@mailer.com'); console.log(result.isOk()); > true const email = result.value(); console.log(email.value()); > "dany@mailer.com" console.log(email.getNick()); > "dany" console.log(email.getDomain()); > "mailer.com" ``` you can block same domain or define only specific domains ```ts Reflect.set(EmailValueObject, "BLOCKED_DOMAINS", ["microsoft.com", "leak.com"]); Reflect.set(EmailValueObject, "VALID_DOMAINS", ["gmail.com", "hotmail.com"]); // now only two domains are accepted gmail and hotmail ``` #### Name Just import and use ```ts const result = UserNameValueObject.create('jannie lan spark'); console.log(result.isOk()); > true const name = result.value(); console.log(name.value()); > "Jannie Lan Spark" console.log(name.getLastName()); > "Spark" console.log(name.getMiddleName()); > "Lan" console.log(name.getFirstName()); > "Jannie" console.log(name.getInitials()); > "J.L.S" ``` #### BirthDay Just import and use ```ts const year2000 = new Date(2000, 1, 1); const result = BirthdayValueObject.create(year2000); console.log(result.isOk()); > true const birthDay = result.value(); console.log(birthDay.value()); > "2000-02-01T02:00:00.000Z" console.log(birthDay.isAgeGreaterThan(18)); > true console.log(birthDay.getAgeAsYearsOld()); > 22 ``` #### Custom string ```ts // my-custom-string.ts import { CustomStringValueObject } from 'types-ddd'; Reflect.set(CustomStringValueObject, 'VALIDATOR', (value: string) => typeof value === 'string'); Reflect.set(CustomStringValueObject, 'MESSAGE', "my custom error message"); const MyCustomString = CustomStringValueObject; export MyCustomString; export default MyCustomString; ``` #### Custom number ```ts // my-custom-number.ts import { CustomNumberValueObject } from 'types-ddd'; Reflect.set(CustomNumberValueObject, 'VALIDATOR', (value: string) => typeof value === 'number'); Reflect.set(CustomNumberValueObject, 'MESSAGE', "my custom error message"); const MyCustomNumber = CustomNumberValueObject; export MyCustomNumber; export default MyCustomNumber; ``` ================================================ FILE: global-setup.ts ================================================ process.env.TZ = 'UTC'; ================================================ FILE: jest.config.ts ================================================ import './global-setup'; module.exports = { roots: [''], collectCoverage: false, coverageDirectory: 'coverage', testEnvironment: 'node', transform: { '.+\\.ts$': 'ts-jest', }, moduleNameMapper: { '@type-ddd': '/lib/index', }, }; ================================================ FILE: lerna.json ================================================ { "packages": [ "packages/*" ], "version": "independent" } ================================================ FILE: package.json ================================================ { "name": "type-ddd", "version": "4.1.0", "description": "This package provide utils file and interfaces to assistant build a complex application with domain driving design", "main": "dist/index.js", "types": "dist/index.d.ts", "license": "ISC", "author": "Alessandro Dev", "private": true, "workspaces": [ "packages/*" ], "engines": { "node": ">=16.x <=22" }, "keywords": [ "Complexity", "NodeJS", "Business Logic", "DDD", "Domain Driving Design", "Typescript", "DDD-Utils", "Base Entity", "Base Aggregate", "Base Value Object", "Use Cases", "Domain Events", "Clean Architecture" ], "scripts": { "prebuild": "rimraf ./dist && npm run check:circular-deps", "build": "tsc -b -v packages", "build:lerna": "lerna run build", "test:prod": "NODE_ENV=production jest --silent --runInBand", "test:dev": "jest --silent --runInBand", "test:cov": "TYPES_DDD_LOGS=off jest --silent --runInBand --coverage", "test": "TYPES_DDD_LOGS=off jest --silent --runInBand --coverage", "test:verbose": "jest --runInBand", "check:circular-deps": "madge --circular --extensions ts ./packages", "prepublish:lib": "rimraf ./dist && npm run check:circular-deps", "publish:lib": "npm publish", "format:all": "npx prettier --write .", "check:types": "tsc -v packages --noEmit" }, "repository": { "type": "git", "url": "git+https://github.com/4lessandrodev/type-ddd.git" }, "bugs": { "url": "https://github.com/4lessandrodev/type-ddd/issues" }, "homepage": "https://github.com/4lessandrodev/type-ddd/tree/main", "peerDependencies": { "rich-domain": "^1.25.0" }, "devDependencies": { "@types/jest": "^27.0.1", "@types/node": "^22.5.2", "@types/pino": "^7.0.5", "husky": "^9.0.6", "jest": "^27.5.1", "lerna": "^8.1.3", "lint-staged": "^15.0.1", "madge": "^8.0.0", "prettier": "^3.0.0", "rich-domain": "1.26.0", "rimraf": "^5.0.5", "ts-jest": "^27.1.4", "ts-node": "^10.7.0", "typescript": "^5.1.6" }, "files": [ "dist/*", "package.json", "packages/*/*.js", "packages/*/*.d.ts", "README.md" ], "lint-staged": { "*": [ "npm run format:all", "npx prettier --ignore-unknown --check" ], "*.{ts,js}": [ "npm run check:circular-deps" ] } } ================================================ FILE: packages/cnpj/CHANGELOG.md ================================================ # Change Log All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. ## [0.0.4-alpha.0](https://github.com/4lessandrodev/type-ddd/compare/@type-ddd/cnpj@0.0.2...@type-ddd/cnpj@0.0.4-alpha.0) (2024-12-16) ## 4.0.5 (2024-11-28) ### Bug Fixes * change type create method return null [#194](https://github.com/4lessandrodev/type-ddd/issues/194) ([2cd03bf](https://github.com/4lessandrodev/type-ddd/commit/2cd03bf34387f4889a0a292ba350f2c0cfc753b7)) ## 4.0.3 (2024-07-26) # Changelog All notable changes to this project will be documented in this file. ## Unreleased --- ## Released --- ### [0.0.3] - 2024-11-28 ### Fix - update rich-domain lib to check nullish type: now 'create' return a possibly null value in Result instance. ### [0.0.2] - 2024-05-31 ### Docs - Doc: update documentation --- ### [0.0.1] - 2024-05-31 ### Base - Create base value object as single pack ================================================ FILE: packages/cnpj/README.md ================================================ # `@type-ddd/cnpj` > The @type-ddd/cnpj library provides TypeScript type definitions for handling CNPJ (Cadastro Nacional da Pessoa Jurídica) in Domain-Driven Design contexts. It facilitates the validation and manipulation of CNPJ numbers, ensuring they adhere to the Brazilian legal standards. --- ## Installation Install `rich-domain` and `@type-ddd/cnpj` with your favorite package manager ```sh npm i rich-domain @type-ddd/cnpj # OR yarn add rich-domain @type-ddd/cnpj ``` ## Usage Don't worry about removing special characters; they are automatically stripped from all instances. ```ts import { CNPJ } from '@type-ddd/cnpj' // Instance of CNPJ or throws an error if provide an invalid value const cnpj = CNPJ.init('54097792000193'); // OR // Result of CNPJ (Check Result pattern docs) const result = CNPJ.create('54097792000193'); result.isOk(); // true // cnpj instance or null if provide an invalid value const cnpj = result.value(); ``` ## Compare values or instances Method to compare two instances or values. ```ts // value as string const isEqual = cnpj.compare('54097792000194') // Output: false // OR // value as instance of CNPJ const isEqual = cnpj.compare(cnpj2) // Output: false ``` ## Check string is valid cnpj Don't worry about removing special characters; they are automatically stripped from all instances. ```ts const result = CNPJ.isValid('54097792000193'); // Output: true ``` ## Special chars If you need the value with the mask, you can use the `toPattern` method: ```ts cnpj.toPattern(); // Output: 54.097.792/0001-93 ``` Or if you need to apply mask from a string value you may use `addMask` method ```ts CNPJ.addMask('54097792000193'); // Output: 54.097.792/0001-93 ``` ================================================ FILE: packages/cnpj/__tests__/cnpj.value-object.util.spec.ts ================================================ import CNPJValueObject from '../index'; describe('CNPJ Value Object', () => { describe('Creation and Definition', () => { it('should be able to create a CNPJ value object', () => { const valueObject = CNPJValueObject.create; expect(valueObject).toBeDefined(); }); it('should create a valid CNPJ with special characters removed', () => { const valueObject = CNPJValueObject.create('43.909.299/0001-04'); expect(valueObject.isOk()).toBeTruthy(); expect(valueObject.value()?.value()).toBe('43909299000104'); }); it('should create a valid CNPJ with numbers only', () => { const valueObject = CNPJValueObject.create('60105617000101'); expect(valueObject.isOk()).toBeTruthy(); expect(valueObject.value()?.value()).toBe('60105617000101'); }); it('should initialize an instance without error', () => { const init = () => CNPJValueObject.init('27729251000168'); expect(init).not.toThrowError(); }); it('should throw an error when initializing with an invalid value', () => { const init = () => CNPJValueObject.init('invalid'); expect(init).toThrowError(); }); }); describe('Validation', () => { it('should return true for a valid CNPJ', () => { const isValid = CNPJValueObject.isValid('43.909.299/0001-04'); expect(isValid).toBeTruthy(); }); it('should return false for an invalid CNPJ', () => { const isValid = CNPJValueObject.isValid('invalid'); expect(isValid).toBeFalsy(); }); it('should fail for an invalid CNPJ', () => { const valueObject = CNPJValueObject.create('53.462.048/0000-99'); expect(valueObject.isFail()).toBeTruthy(); }); it('should fail for an invalid CNPJ (digit sum)', () => { const valueObject = CNPJValueObject.create('93.118.559/0001-1'); expect(valueObject.isFail()).toBeTruthy(); }); it('should fail for an invalid CNPJ (digit sum)', () => { const valueObject = CNPJValueObject.create('93.118.559/0001-100'); expect(valueObject.isFail()).toBeTruthy(); }); }); describe('Formatting', () => { it('should format a CNPJ with special characters', () => { const valueObject = CNPJValueObject.create('20.798.751/0001-02').value(); expect(valueObject?.toPattern()).toBe('20.798.751/0001-02'); }); it('should format a CNPJ with special characters', () => { const valueObject = CNPJValueObject.create('65.389.009/0001-81').value(); expect(valueObject?.toPattern()).toBe('65.389.009/0001-81'); }); it('should format a CNPJ with special characters', () => { const valueObject = CNPJValueObject.create('02.470.431/0001-47').value(); expect(valueObject?.toPattern()).toBe('02.470.431/0001-47'); }); it('should format a CNPJ with special characters and remove them later', () => { const valueObject = CNPJValueObject.create('62.412.404/0001-40').value(); expect(valueObject?.toPattern()).toBe('62.412.404/0001-40'); }); }); describe('Comparison', () => { it('should correctly compare the value in the instance with the provided value', () => { const validCNPJ = '22.606.062/0001-84'; const valueObject = CNPJValueObject.create(validCNPJ).value(); // Compare with invalid CNPJ let isEqual = valueObject?.compare('invalid'); expect(isEqual).toBeFalsy(); // Compare with different valid CNPJ isEqual = valueObject?.compare('22.606.062/0001-20'); expect(isEqual).toBeFalsy(); // Compare with the same valid CNPJ isEqual = valueObject?.compare(validCNPJ); expect(isEqual).toBeTruthy(); // Compare with a valid CNPJ with different format isEqual = valueObject?.compare('22606062000155'); expect(isEqual).toBeFalsy(); // Compare with a valid CNPJ with the same value but different format isEqual = valueObject?.compare('22606062000184'); expect(isEqual).toBeTruthy(); // Compare with the same valid CNPJ isEqual = valueObject?.compare('22.606.062/0001-84'); expect(isEqual).toBeTruthy(); }); it('should compare two instances', () => { const instanceA = CNPJValueObject.init('22.606.062/0001-84'); const instanceB = CNPJValueObject.init('22.606.062/0001-84'); expect(instanceA.compare(instanceB)).toBeTruthy(); }); it('should return false when comparing with null', () => { const instanceA = CNPJValueObject.init('22.606.062/0001-84'); expect(instanceA.compare(null as any)).toBeFalsy(); }); }); describe('Special Character Removal', () => { it('should remove special characters from a string', () => { const value = CNPJValueObject.removeSpecialChars('93.118.559/0001-10'); expect(value).toBe('93118559000110'); }); it('should remove special characters from a string', () => { const value = CNPJValueObject.addMask('93118559000110'); expect(value).toBe('93.118.559/0001-10'); }); }); }); ================================================ FILE: packages/cnpj/__tests__/is-valid-cpf-digit.util.spec.ts ================================================ import { formatValueToCnpjPattern, isValidCnpjDigit, removeSpecialCharsFromCnpj } from '../util'; describe('is-valid-cnpj-digits', () => { it('should be defined', () => { const isValidCnpjDigitFn = isValidCnpjDigit; expect(isValidCnpjDigitFn).toBeDefined(); }); it('should return false if provide a value less than 11 char', () => { const isValidCnpjDigitFn = isValidCnpjDigit('506595900001'); expect(isValidCnpjDigitFn).toBe(false); }); it('should return false if provide word instead numbers', () => { const isValidCnpjDigitFn = isValidCnpjDigit('invalid_val'); expect(isValidCnpjDigitFn).toBe(false); }); it('should return false if provide a value with invalid sum', () => { const isValidCnpjDigitFn = isValidCnpjDigit('39.060.118/0001-41'); expect(isValidCnpjDigitFn).toBe(false); }); it('should return false if provide a value with invalid sum', () => { const isValidCnpjDigitFn = isValidCnpjDigit('50659590000140'); expect(isValidCnpjDigitFn).toBe(false); }); it('should return true and validate with success', () => { const isValidCnpjDigitFn = isValidCnpjDigit('50659590000137'); expect(isValidCnpjDigitFn).toBe(true); }); it('should return true and validate with success', () => { const isValidCnpjDigitFn = isValidCnpjDigit('05.718.081/0001-83'); expect(isValidCnpjDigitFn).toBe(true); }); it('should return true and validate with success', () => { const isValidCnpjDigitFn = isValidCnpjDigit('39.434.735/0001-69'); expect(isValidCnpjDigitFn).toBe(true); }); it('should return true and validate with success', () => { const isValidCnpjDigitFn = isValidCnpjDigit('47.091.193/0001-05'); expect(isValidCnpjDigitFn).toBe(true); }); it('should format cnpj from pattern to only numbers', () => { const isValidCnpjDigitFn = removeSpecialCharsFromCnpj('39.604.284/0001-60'); expect(isValidCnpjDigitFn).toBe('39604284000160'); }); it('should format cnpj from pattern to only numbers', () => { const isValidCnpjDigitFn = removeSpecialCharsFromCnpj('39604284000160'); expect(isValidCnpjDigitFn).toBe('39604284000160'); }); it('should format cnpj from pattern to only numbers', () => { const isValidCnpjDigitFn = removeSpecialCharsFromCnpj('val.cnpj.str-d0'); expect(isValidCnpjDigitFn).toBe('valcnpjstrd0'); }); it('should format cnpj from only numbers to pattern', () => { const isValidCnpjDigitFn = formatValueToCnpjPattern('39604284000160'); expect(isValidCnpjDigitFn).toBe('39.604.284/0001-60'); }); it('should format cnpj from only numbers to pattern', () => { const isValidCnpjDigitFn = formatValueToCnpjPattern('39604284000160'); expect(isValidCnpjDigitFn).toBe('39.604.284/0001-60'); }); it('should format cnpj from only numbers to pattern', () => { const isValidCnpjDigitFn = formatValueToCnpjPattern('vacnppj00strd0'); expect(isValidCnpjDigitFn).toBe('va.cnp.pj0/0str-d0'); }); }); ================================================ FILE: packages/cnpj/index.ts ================================================ import { Result, ValueObject } from 'rich-domain'; import isValidCnpjDigit, { formatValueToCnpjPattern, removeSpecialCharsFromCnpj } from './util'; const regexCnpj = /^([0-9]{2})[\.]([0-9]{3})[\.]((?!\2)[0-9]{3})[\/]([0-9]{4})[-]([0-9]{2})$|^[0-9]{14}$/; export class CNPJ extends ValueObject { protected static readonly REGEX = regexCnpj; protected static readonly MESSAGE: string = 'Invalid value for cnpj'; private constructor(props: string) { super(props); } /** * @description return a cnpj value (only numbers). * @example example "22398345000188". * @summary If you want cnpj as pattern use `formatToCnpjPattern` before get value. */ value(): string { return this.props; } /** * @description add hyphen and dot to cnpj value. * @example before "22398345000188" * @example after "22.398.345/0001-88" */ toPattern(): string { return formatValueToCnpjPattern(this.props); } /** * @description add hyphen and dot to cnpj value. * @example before "22398345000188" * @example after "22.398.345/0001-88" */ public static addMask(cnpj: string): string { return formatValueToCnpjPattern(cnpj); } /** * @description remove hyphen and dot from cnpj value. * @example before "22.398.345/0001-88" * @example after "22398345000188" */ public static removeSpecialChars(cnpj: string): string { return removeSpecialCharsFromCnpj(cnpj); } /** * * @param cnpj value as string only number or pattern. * @returns true if cnpj match with instance value and false if not. * @example param "22398345000188" * @example param "22.398.345/0001-88" */ compare(cnpj: string | CNPJ): boolean { if (typeof cnpj === 'string') { const formattedCnpj = removeSpecialCharsFromCnpj(cnpj); const instanceValue = this.props; return instanceValue === formattedCnpj; } if (cnpj instanceof CNPJ) { return cnpj.isEqual(this); } return false; } /** * @description check if cnpj value is a valid pattern and has a valid digit sum. * @param value cnpj as string * @returns true if value is valid and false if not. * @example "22.398.345/0001-88" * @example "22398345000188" */ public static isValidProps(value: string): boolean { const isValidPattern = CNPJ.REGEX.test(value); const isValidDigits = isValidCnpjDigit(value); return isValidDigits && isValidPattern; } /** * @description check if cnpj value is a valid pattern and has a valid digit sum. * @param value cnpj as string * @returns true if value is valid and false if not. * @example "22.398.345/0001-88" * @example "22398345000188" */ public static isValid(value: string): boolean { return this.isValidProps(value); } /** * * @param value value as string * @returns instance of CNPJ or throw an error */ public static init(value: string): CNPJ { const isValidValue = CNPJ.isValidProps(value); if (!isValidValue) throw new Error(CNPJ.MESSAGE); return new CNPJ(value); } /** * @description create a cnpj value object * @param value cnpj numbers as string * @returns instance of Result with cnpj value * @example "22.398.345/0001-88" * @example "22398345000188" * @summary fails if provide an invalid pattern or a cnpj with invalid digit sum */ public static create(value: string): Result { const isValidValue = CNPJ.isValidProps(value); if (!isValidValue) { return Result.fail(CNPJ.MESSAGE); } return Result.Ok(new CNPJ(removeSpecialCharsFromCnpj(value))); } } export default CNPJ; ================================================ FILE: packages/cnpj/package.json ================================================ { "name": "@type-ddd/cnpj", "description": "Library that provides TypeScript type definitions for handling CNPJ (Cadastro Nacional da Pessoa Jurídica) in Domain-Driven Design contexts. It facilitates the validation and manipulation of CNPJ numbers, ensuring they adhere to the Brazilian legal standards.", "version": "0.1.0", "main": "index.js", "types": "index.d.ts", "author": "Alessandro Dev", "license": "MIT", "publishConfig": { "access": "public" }, "keywords": [ "Complexity", "Business Logic", "DDD", "Domain Driving Design", "DDD-Utils", "Base Value Object", "Domain Events", "Clean Architecture", "Validation", "Formatting", "Value Object", "Utility", "Security", "Standards", "Brazil", "CNPJ", "Cadastro Nacional Pessoa Jurídica", "Pessoa Jurídica" ], "scripts": { "build": "tsc" }, "peerDependencies": { "rich-domain": "^1.25.0" }, "files": [ "index.js", "index.d.ts", "util.d.ts", "util.js" ], "repository": { "type": "git", "url": "git+https://github.com/4lessandrodev/type-ddd.git" }, "bugs": { "url": "https://github.com/4lessandrodev/type-ddd/issues" }, "homepage": "https://github.com/4lessandrodev/type-ddd/tree/main/packages/cnpj", "gitHead": "4cb9159bde8d6fc951e9d902feed2ad25da49fa4" } ================================================ FILE: packages/cnpj/tsconfig.build.json ================================================ { "extends": "../tsconfig.build.json", "compilerOptions": { "outDir": ".", "rootDir": ".", "paths": {} }, "exclude": [ "node_modules", "dist", "__tests__/**/*", "*.spec.ts" ], "references": [] } ================================================ FILE: packages/cnpj/tsconfig.json ================================================ { "extends": "../tsconfig.build.json", "compilerOptions": { "types": [ "node" ] }, "files": [], "include": [], "references": [ { "path": "./tsconfig.build.json" } ] } ================================================ FILE: packages/cnpj/util.ts ================================================ interface CnpjDigits { penultimateDigit: number; ultimateDigit: number; } const removeSpecialCharsFromCnpjRegex = /[\.]|[-]|[\/]/g; export const formatValueToCnpjPattern = (cnpj: string): string => { const cnpjValue = removeSpecialCharsFromCnpj(cnpj); let formattedValue: string = ''; let index: number = 0; while (formattedValue.length < 18 && index < 17) { if (index === 2 || index === 5) { formattedValue += '.'; } else if (index === 8) { formattedValue += '/'; } else if (index === 12) { formattedValue += '-'; } formattedValue += cnpjValue[index]; index++; } return formattedValue; }; export const removeSpecialCharsFromCnpj = (cnpj: string): string => { return cnpj.replace(removeSpecialCharsFromCnpjRegex, ''); }; const getCnpjDigitsNumbers = (cnpj: string): CnpjDigits => { const lastTwoNumbers = cnpj.slice(cnpj.length - 2); const penultimateDigit = parseInt(lastTwoNumbers[0]); const ultimateDigit = parseInt(lastTwoNumbers[1]); return { penultimateDigit, ultimateDigit, }; }; const transformCnpjInArrNumber = (cnpj: string): number[] => { var arr: number[] = []; let index = 0; while (index < 12) { arr.push(parseInt(cnpj[index])); index++; } return arr; }; const calculateCnpjDigits = (cnpjNumbers: number[]): CnpjDigits => { const factor = 11; let index = cnpjNumbers.length - 1; let startAuxValue = 2; let totalForDigit = 0; while (index >= 0) { totalForDigit = totalForDigit + cnpjNumbers[index] * startAuxValue; startAuxValue = startAuxValue === 9 ? 2 : startAuxValue + 1; index--; } const calcPDigit = totalForDigit % factor; const resultPDigit = factor - calcPDigit; const zeroIfPGreaterThanNine = resultPDigit >= 9 ? 0 : resultPDigit; const penultimateDigit = zeroIfPGreaterThanNine; cnpjNumbers.push(penultimateDigit); index = cnpjNumbers.length - 1; startAuxValue = 2; totalForDigit = 0; while (index >= 0) { totalForDigit = totalForDigit + cnpjNumbers[index] * startAuxValue; startAuxValue = startAuxValue === 9 ? 2 : startAuxValue + 1; index--; } const calcUDigit = totalForDigit % factor; const resultUDigit = factor - calcUDigit; const zeroIfGreaterThanNine = resultUDigit > 9 ? 0 : resultUDigit; const ultimateDigit = zeroIfGreaterThanNine; return { penultimateDigit, ultimateDigit, }; }; export const isValidCnpjDigit = (cnpj: string): boolean => { const onlyNumbers = removeSpecialCharsFromCnpj(cnpj); if (onlyNumbers.length !== 14) { return false; } const digits = getCnpjDigitsNumbers(onlyNumbers); const arrNumbers = transformCnpjInArrNumber(onlyNumbers); const validDigits = calculateCnpjDigits(arrNumbers); return ( digits.penultimateDigit === validDigits.penultimateDigit && digits.ultimateDigit === validDigits.ultimateDigit ); }; export default isValidCnpjDigit; ================================================ FILE: packages/cpf/CHANGELOG.md ================================================ # Change Log All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. ## [0.0.4-alpha.0](https://github.com/4lessandrodev/type-ddd/compare/@type-ddd/cpf@0.0.2...@type-ddd/cpf@0.0.4-alpha.0) (2024-12-16) ## 4.0.5 (2024-11-28) ### Bug Fixes * change type create method return null [#194](https://github.com/4lessandrodev/type-ddd/issues/194) ([2cd03bf](https://github.com/4lessandrodev/type-ddd/commit/2cd03bf34387f4889a0a292ba350f2c0cfc753b7)) ## 4.0.3 (2024-07-26) # Changelog All notable changes to this project will be documented in this file. ## Unreleased --- ## Released --- ### [0.0.3] - 2024-11-28 ### Fix - update rich-domain lib to check nullish type: now 'create' return a possibly null value in Result instance. ### [0.0.2] - 2024-05-31 ### Docs - Doc: update documentation --- ### [0.0.1] - 2024-05-14 ### Base - Create base value object as single pack ================================================ FILE: packages/cpf/README.md ================================================ # `@type-ddd/cpf` > The @type-ddd/cpf library provides TypeScript type definitions for handling CPF (Cadastro de Pessoa Física) in Domain-Driven Design contexts. It facilitates the validation and manipulation of CPF numbers, ensuring they adhere to the Brazilian legal standards. --- ## Installation Install `rich-domain` and `@type-ddd/cpf` with your favorite package manager ```sh npm i rich-domain @type-ddd/cpf # OR yarn add rich-domain @type-ddd/cpf ``` ## Usage Don't worry about removing special characters; they are automatically stripped from all instances. ```ts import { CPF } from '@type-ddd/cpf' // Instance of cpf or throws an error if provide an invalid value const cpf = CPF.init('54097792000193'); // OR // Result of cpf (Check Result pattern docs) const result = CPF.create('54097792000193'); result.isOk(); // true // cpf instance or null if provide an invalid value const cpf = result.value(); ``` ## Compare values or instances Method to compare two instances or values. ```ts // value as string const isEqual = cpf.compare('54097792000194') // Output: false // OR // value as instance of cpf const isEqual = cpf.compare(cpf2) // Output: false ``` ## Check string is valid cpf Don't worry about removing special characters; they are automatically stripped from all instances. ```ts const result = CPF.isValid('54097792000193'); // Output: true ``` ## Special chars If you need the value with the mask, you can use the `toPattern` method: ```ts cpf.toPattern(); // Output: 54.097.792/0001-93 ``` Or if you need to apply mask from a string value you may use `addMask` method ```ts CPF.addMask('54097792000193'); // Output: 54.097.792/0001-93 ``` ================================================ FILE: packages/cpf/__tests__/cpf.value-object.util.spec.ts ================================================ import CPFValueObject from "../index"; describe('cpf.value-object', () => { it('should be defined', () => { const valueObject = CPFValueObject.create; expect(valueObject).toBeDefined(); }); it('should create a valid cpf with special chars and remove special chars on get value', () => { const valueObject = CPFValueObject.create('667.324.914-58'); expect(valueObject.isOk()).toBeTruthy(); expect(valueObject.value()?.value()).toBe('66732491458'); }); it('should create a valid cpf with special chars and remove special chars on get value', () => { const valueObject = CPFValueObject.create('934.665.143-12'); expect(valueObject.isOk()).toBeTruthy(); expect(valueObject.value()?.value()).toBe('93466514312'); }); it('should create a valid cpf with special chars and remove special chars on get value', () => { const valueObject = CPFValueObject.create('690.574.738-60'); expect(valueObject.isOk()).toBeTruthy(); expect(valueObject.value()?.value()).toBe('69057473860'); }); it('should create a valid cpf with special chars and remove special chars on get value', () => { const valueObject = CPFValueObject.create('324.123.359-66'); expect(valueObject.isOk()).toBeTruthy(); expect(valueObject.value()?.value()).toBe('32412335966'); }); it('should create a valid cpf with special chars and remove special chars on get value', () => { const valueObject = CPFValueObject.create('673.761.543-02'); expect(valueObject.isOk()).toBeTruthy(); expect(valueObject.value()?.value()).toBe('67376154302'); }); it('should create a valid cpf with special chars and remove special chars on get value', () => { const valueObject = CPFValueObject.create('024.815.901-12'); expect(valueObject.isOk()).toBeTruthy(); expect(valueObject.value()?.value()).toBe('02481590112'); }); it('should create a valid cpf with special chars and remove special chars on get value', () => { const valueObject = CPFValueObject.create('754.179.880-06'); expect(valueObject.isOk()).toBeTruthy(); expect(valueObject.value()?.value()).toBe('75417988006'); }); it('should format a cpf to add special chars', () => { const valueObject = CPFValueObject.create('667.324.914-58').value(); expect(valueObject?.toPattern()).toBe('667.324.914-58'); }); it('should format a cpf to add special chars', () => { const valueObject = CPFValueObject.create('578.363.883-87').value(); expect(valueObject?.toPattern()).toBe('578.363.883-87'); }); it('should format a cpf to add special chars', () => { const valueObject = CPFValueObject.create('844.676.543-80').value(); expect(valueObject?.toPattern()).toBe('844.676.543-80'); }); it('should format a cpf to add special chars and remove it later', () => { const valueObject = CPFValueObject.create('667.324.914-58').value(); expect(valueObject?.toPattern()).toBe('667.324.914-58'); expect(valueObject?.value()).toBe('66732491458'); }); it('should compare value on instance and provided value', () => { const valueObject = CPFValueObject.create('549.777.281-14').value(); let isEqual = valueObject?.compare('invalid'); expect(isEqual).toBeFalsy(); isEqual = valueObject?.compare('549.777.281-15'); expect(isEqual).toBeFalsy(); isEqual = valueObject?.compare('549.777.281-14'); expect(isEqual).toBeTruthy(); isEqual = valueObject?.compare('54977728314'); expect(isEqual).toBeFalsy(); isEqual = valueObject?.compare('54977728114'); expect(isEqual).toBeTruthy(); isEqual = valueObject?.compare('54977728114'); expect(isEqual).toBeTruthy(); isEqual = valueObject?.compare('549.777.281-14'); expect(isEqual).toBeTruthy(); }); it('should create a valid cpf only numbers', () => { const valueObject = CPFValueObject.create('53534317661'); expect(valueObject.isOk()).toBeTruthy(); expect(valueObject.value()?.value()).toBe('53534317661'); expect(CPFValueObject.isValid('53534317661')).toBeTruthy(); }); it('should fail if provide an invalid value', () => { const valueObject = CPFValueObject.create('754.466.282-920'); expect(valueObject.isFail()).toBeTruthy(); }); it('should fail if provide an invalid value (digit sum)', () => { const valueObject = CPFValueObject.create('754.466.282-01'); expect(valueObject.isFail()).toBeTruthy(); }); it('should fail if provide an invalid value (digit sum)', () => { const valueObject = CPFValueObject.create('75446628201'); expect(valueObject.isFail()).toBeTruthy(); }); it('should create a valid cpf only numbers', () => { const valueObject = CPFValueObject.create('53534317661'); expect(valueObject.isOk()).toBeTruthy(); expect(valueObject.value()?.value()).toBe('53534317661'); }); it('should create a valid cpf only numbers', () => { const valueObject = CPFValueObject.create('98614591039'); expect(valueObject.isOk()).toBeTruthy(); expect(valueObject.value()?.value()).toBe('98614591039'); }); it('should init an instance with success', () => { const init = () => CPFValueObject.init('53534317661'); expect(init).not.toThrowError(); }); it('should throw an error on init an instance with invalid value', () => { const init = () => CPFValueObject.init('invalid'); expect(init).toThrowError(); }); it('should add mask with success', () => { const result = CPFValueObject.addMask('53534317661'); expect(result).toBe('535.343.176-61'); }); it('should remove mask with success', () => { const result = CPFValueObject.removeSpecialChars('535.343.176-61'); expect(result).toBe('53534317661'); }); it('should compare cpf instances with success', () => { const cpfA = CPFValueObject.init('535.343.176-61'); const cpfB = CPFValueObject.init('53534317661'); const cpfC = CPFValueObject.init('89926097014'); expect(cpfA.compare(cpfB)).toBeTruthy(); expect(cpfA.compare(cpfC)).toBeFalsy(); expect(cpfC.compare(123 as any)).toBeFalsy(); }); }); ================================================ FILE: packages/cpf/__tests__/is-valid-cpf-digit.util.spec.ts ================================================ import isValidCpfDigit, { formatValueToCpfPattern, removeSpecialCharsFromCpf } from "../util"; describe('is-valid-cpf-digits', () => { it('should be defined', () => { const isValidCpfDigitFn = isValidCpfDigit; expect(isValidCpfDigitFn).toBeDefined(); }); it('should return false if provide a value less than 11 char', () => { const isValidCpfDigitFn = isValidCpfDigit('727254778'); expect(isValidCpfDigitFn).toBe(false); }); it('should return false if provide word instead numbers', () => { const isValidCpfDigitFn = isValidCpfDigit('invalid_val'); expect(isValidCpfDigitFn).toBe(false); }); it('should return false if provide a value with invalid sum', () => { const isValidCpfDigitFn = isValidCpfDigit('766.682.694-01'); expect(isValidCpfDigitFn).toBe(false); }); it('should return false if provide a value with invalid sum', () => { const isValidCpfDigitFn = isValidCpfDigit('76668269401'); expect(isValidCpfDigitFn).toBe(false); }); it('should return true and validate with success', () => { const isValidCpfDigitFn = isValidCpfDigit('76668269400'); expect(isValidCpfDigitFn).toBe(true); }); it('should return true and validate with success', () => { const isValidCpfDigitFn = isValidCpfDigit('730.208.487-41'); expect(isValidCpfDigitFn).toBe(true); }); it('should return true and validate with success', () => { const isValidCpfDigitFn = isValidCpfDigit('641.482.734-79'); expect(isValidCpfDigitFn).toBe(true); }); it('should return true and validate with success', () => { const isValidCpfDigitFn = isValidCpfDigit('48153676474'); expect(isValidCpfDigitFn).toBe(true); }); it('should format cpf from pattern to only numbers', () => { const isValidCpfDigitFn = removeSpecialCharsFromCpf('641.482.734-79'); expect(isValidCpfDigitFn).toBe('64148273479'); }); it('should format cpf from pattern to only numbers', () => { const isValidCpfDigitFn = removeSpecialCharsFromCpf('64148273479'); expect(isValidCpfDigitFn).toBe('64148273479'); }); it('should format cpf from pattern to only numbers', () => { const isValidCpfDigitFn = removeSpecialCharsFromCpf('val.cpf.str-d0'); expect(isValidCpfDigitFn).toBe('valcpfstrd0'); }); it('should format cpf from only numbers to pattern', () => { const isValidCpfDigitFn = formatValueToCpfPattern('64148273479'); expect(isValidCpfDigitFn).toBe('641.482.734-79'); }); it('should format cpf from only numbers to pattern', () => { const isValidCpfDigitFn = formatValueToCpfPattern('64148273479'); expect(isValidCpfDigitFn).toBe('641.482.734-79'); }); it('should format cpf from only numbers to pattern', () => { const isValidCpfDigitFn = formatValueToCpfPattern('valcpfstrd0'); expect(isValidCpfDigitFn).toBe('val.cpf.str-d0'); }); it('should to be valid cpf', () => { const isValid = isValidCpfDigit('15173713097'); expect(isValid).toBeTruthy(); }); }); ================================================ FILE: packages/cpf/index.ts ================================================ import { Result, ValueObject } from "rich-domain"; import isValidCpfDigit, { formatValueToCpfPattern } from "./util"; export class CPF extends ValueObject { protected static readonly REGEX = /^([0-9]{3})[\.]((?!\1)[0-9]{3})[\.]([0-9]{3})[-]([0-9]{2})$|^[0-9]{11}$/; protected static readonly MESSAGE: string = 'Invalid value for cpf'; private constructor(value: string) { super(value); } /** * @description return a cpf value (only numbers). * @example example "52734865211". * @summary If you want cpf as pattern use `formatToCpfPattern` before get value. */ value(): string { return this.props; } /** * @description add hyphen and dot to cpf value. * @example before "52734865211" * @example after "527.348.652-11" */ toPattern(): string { return formatValueToCpfPattern(this.props); } /** * @description add hyphen and dot to cpf value. * @example before "52734865211" * @example after "527.348.652-11" */ public static addMask(cpf: string): string { return formatValueToCpfPattern(cpf); } /** * @description remove hyphen and dot from cpf value. * @example before "527.348.652-11" * @example after "52734865211" */ public static removeSpecialChars(cpf: string): string { return this.util.string(cpf).removeSpecialChars(); } /** * * @param cpf value as string only number or pattern Or instance of CPF. * @returns true if cpf match with instance value and false if not. * @example param "52734865211" * @example param "527.348.652-11" */ compare(cpf: string | CPF): boolean { if (typeof cpf === 'string') { const valueA = this.util.string(cpf).removeSpecialChars(); const valueB = this.util .string(this.props) .removeSpecialChars(); return valueA === valueB; } if (cpf instanceof CPF) return cpf.isEqual(this); return false; } /** * @description check if cpf value is a valid pattern and has a valid digit sum. * @param value cpf as string * @returns true if value is valid and false if not. * @example "527.348.652-11" * @example "72725477824" */ public static isValidProps(value: string): boolean { const isValidPattern = CPF.REGEX.test(value); const isValidDigits = isValidCpfDigit(value); return isValidDigits && isValidPattern; } /** * @description check if cpf value is a valid pattern and has a valid digit sum. * @param value cpf as string * @returns true if value is valid and false if not. * @example "527.348.652-11" * @example "72725477824" */ public static isValid(value: string): boolean { return this.isValidProps(value); } /** * * @param value value as string * @returns instance of CPF or throw an error */ public static init(value: string): CPF { const isValidValue = CPF.isValidProps(value); if (!isValidValue) throw new Error(CPF.MESSAGE); return new CPF(this.util.string(value).removeSpecialChars()); } /** * @description create a cpf value object * @param value cpf numbers as string * @returns instance of Result with cpf value * @example "527.348.652-11" * @example "72725477824" * @summary fails if provide an invalid pattern or a cpf with invalid digit sum */ public static create(value: string): Result { const isValidValue = CPF.isValidProps(value); if (!isValidValue) { return Result.fail(CPF.MESSAGE); } return Result.Ok(new CPF(this.util.string(value).removeSpecialChars())); } } export default CPF; ================================================ FILE: packages/cpf/package.json ================================================ { "name": "@type-ddd/cpf", "description": "This package provides TypeScript type definitions for handling CPF (Cadastro de Pessoa Física) in Domain-Driven Design contexts", "version": "0.1.0", "main": "index.js", "types": "index.d.ts", "author": "Alessandro Dev", "license": "MIT", "publishConfig": { "access": "public" }, "keywords": [ "Complexity", "Business Logic", "DDD", "Domain Driving Design", "DDD-Utils", "Base Value Object", "Domain Events", "CPF", "Validation", "Formatting", "Value Object", "Utility", "Standards", "Brazil", "Cadastro de Pessoa Física" ], "scripts": { "build": "tsc" }, "peerDependencies": { "rich-domain": "^1.25.0" }, "files": [ "index.js", "index.d.ts", "util.d.ts", "util.js" ], "repository": { "type": "git", "url": "git+https://github.com/4lessandrodev/type-ddd.git" }, "bugs": { "url": "https://github.com/4lessandrodev/type-ddd/issues" }, "homepage": "https://github.com/4lessandrodev/type-ddd/tree/main/packages/cpf", "gitHead": "4cb9159bde8d6fc951e9d902feed2ad25da49fa4" } ================================================ FILE: packages/cpf/tsconfig.build.json ================================================ { "extends": "../tsconfig.build.json", "compilerOptions": { "outDir": ".", "rootDir": ".", "paths": {} }, "exclude": [ "node_modules", "dist", "__tests__/**/*", "*.spec.ts" ], "references": [] } ================================================ FILE: packages/cpf/tsconfig.json ================================================ { "extends": "../tsconfig.build.json", "compilerOptions": { "types": [ "node" ] }, "files": [], "include": [], "references": [ { "path": "./tsconfig.build.json" } ] } ================================================ FILE: packages/cpf/util.ts ================================================ interface CpfDigits { penultimateDigit: number; ultimateDigit: number; } const removeSpecialCharsFromCpfRegex = /[\.]|[-]/g; export const formatValueToCpfPattern = (cpf: string): string => { const cpfValue = removeSpecialCharsFromCpf(cpf); let formattedValue: string = ''; let index: number = 0; while (formattedValue.length < 14 && index < 11) { if (index === 3 || index === 6) { formattedValue += '.'; } else if (index === 9) { formattedValue += '-'; } formattedValue += cpfValue[index]; index++; } return formattedValue; }; export const removeSpecialCharsFromCpf = (cpf: string): string => { return cpf.replace(removeSpecialCharsFromCpfRegex, ''); }; const getCpfDigitsNumbers = (cpf: string): CpfDigits => { const lastTwoNumbers = cpf.slice(cpf.length - 2); const penultimateDigit = parseInt(lastTwoNumbers[0]); const ultimateDigit = parseInt(lastTwoNumbers[1]); return { penultimateDigit, ultimateDigit, }; }; const transformCpfInArrNumber = (cpf: string): number[] => { var arr: number[] = []; let index = 0; while (index < 9) { arr.push(parseInt(cpf[index])); index++; } return arr; }; export const calculateCpfDigits = (cpfNumbers: number[]): CpfDigits => { const factor = 11; let index = 0; let startAuxValue = 10; let totalForDigit = 0; while (index < 9) { totalForDigit = totalForDigit + cpfNumbers[index] * startAuxValue; startAuxValue--; index++; } const calcPDigit = totalForDigit % factor; const resultPDigit = factor - calcPDigit; const zeroIfPGreaterThanNine = resultPDigit > 9 ? 0 : resultPDigit; const penultimateDigit = zeroIfPGreaterThanNine; index = 0; startAuxValue = 11; totalForDigit = 0; cpfNumbers.push(penultimateDigit); while (index < 10) { totalForDigit = totalForDigit + cpfNumbers[index] * startAuxValue; startAuxValue--; index++; } const calcUDigit = totalForDigit % factor; const resultUDigit = factor - calcUDigit; const zeroIfGreaterThanNine = resultUDigit > 9 ? 0 : resultUDigit; const ultimateDigit = zeroIfGreaterThanNine; return { penultimateDigit, ultimateDigit, }; }; export const isValidCpfDigit = (cpf: string): boolean => { const onlyNumbers = removeSpecialCharsFromCpf(cpf); if (onlyNumbers.length !== 11) { return false; } const digits = getCpfDigitsNumbers(onlyNumbers); const arrNumbers = transformCpfInArrNumber(onlyNumbers); const validDigits = calculateCpfDigits(arrNumbers); return ( digits.penultimateDigit === validDigits.penultimateDigit && digits.ultimateDigit === validDigits.ultimateDigit ); }; export default isValidCpfDigit; ================================================ FILE: packages/date/CHANGELOG.md ================================================ # Change Log All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. ## [0.0.4-alpha.0](https://github.com/4lessandrodev/type-ddd/compare/@type-ddd/date@0.0.2...@type-ddd/date@0.0.4-alpha.0) (2024-12-16) ## 4.0.5 (2024-11-28) ### Bug Fixes * change type create method return null [#194](https://github.com/4lessandrodev/type-ddd/issues/194) ([2cd03bf](https://github.com/4lessandrodev/type-ddd/commit/2cd03bf34387f4889a0a292ba350f2c0cfc753b7)) ## 4.0.3 (2024-07-26) # Changelog All notable changes to this project will be documented in this file. ## Unreleased --- ## Released --- ### [0.0.3] - 2024-11-28 ### Fix - update rich-domain lib to check nullish type: now 'create' return a possibly null value in Result instance. ### [0.0.2] - 2024-05-31 ### Docs - Doc: update documentation --- ### [0.0.1] - 2024-05-31 ### Base - Create base value object as single pack ================================================ FILE: packages/date/README.md ================================================ # `@type-ddd/date` > The @type-ddd/date library provides a class Dates for handling date and time operations in TypeScript. It offers various methods for manipulating dates, calculating differences, formatting dates, and checking validity. This library aims to simplify date and time management in Domain-Driven Design contexts. --- ## Installation Install `rich-domain` and `@type-ddd/date` with your favorite package manager: ```sh npm i rich-domain @type-ddd/date # OR yarn add rich-domain @type-ddd/date ``` ## Usage ```ts import { Dates } from '@type-ddd/dates'; // Check if is valid value const isValid = Dates.isValid('2020-02-31'); // false // Initialize Dates instance with current date and time const date = Dates.init(); // OR // Create Dates instance from provided date or timestamp const result = Dates.create('2024-05-24'); // Add days, months, hours, minutes, weeks, or years const newDate = date.addDays(5).addMonths(2); // Format date according to various patterns const formattedDate = date.format('DD/MM/YYYY hh:mm:ss'); // Check if date is weekday or weekend const isWeekday = date.isWeekday(); const isWeekend = date.isWeekend(); ``` ================================================ FILE: packages/date/__tests__/date.value-object.spec.ts ================================================ import Dates from '../index'; describe('Date', () => { describe('create', () => { it('should create an instance with success', () => { const instance = Dates.create(); expect(instance.value()).toBeInstanceOf(Dates); }); it('should create an instance with success', () => { const instance = Dates.create('2020-12-25'); expect(instance.value()).toBeInstanceOf(Dates); }); it('should create an instance with success', () => { const instance = Dates.create(Date.now()); expect(instance.value()).toBeInstanceOf(Dates); }); it('should create an instance with success', () => { const instance = Dates.create(new Date(2020, 1, 1, 1, 1, 1)); expect(instance.value()?.value().toISOString()).toBe('2020-02-01T01:01:01.000Z'); }); it('should return result fail if provide an invalid value', () => { const instance = Dates.create({} as any); expect(instance.isFail()).toBeTruthy(); }); it('should return result fail if provide an invalid date string', () => { const instance = Dates.create('invalid'); expect(instance.isFail()).toBeTruthy(); }); it('should init a valid v.o', () => { const instance = Dates.init(); expect(instance).toBeInstanceOf(Dates); }); it('should init a date with provided value', () => { const instance = Dates.init(new Date(2020, 1, 1, 1, 1, 1)); expect(instance.value().toISOString()).toBe('2020-02-01T01:01:01.000Z'); }); it('should throw an error if provide an invalid value', () => { const build = () => Dates.init({} as any); expect(build).toThrowError(); }); it('should throw if provide an invalid date string', () => { const build = () => Dates.init('invalid'); expect(build).toThrowError(); }); it('should throw if provide an invalid date string', () => { const build = () => Dates.init('2020-12-25'); expect(build).not.toThrowError(); }); it('should throw if provide an invalid date string', () => { const build = () => Dates.init(Date.now()); expect(build).not.toThrowError(); }); }); describe('addDays', () => { it('should add 1 day with success', () => { const date = Dates.init(new Date('2020-01-01T08:00:00')); expect(date.addDays(1).value().toISOString()).toBe('2020-01-02T08:00:00.000Z'); }); it('should add 7 days with success', () => { const date = Dates.init(new Date('2020-01-01T08:00:00')); expect(date.addDays(7).value().toISOString()).toBe('2020-01-08T08:00:00.000Z'); }); }); describe('addMonths', () => { it('should add 1 month with success', () => { const date = Dates.init(new Date('2020-01-01T08:00:00')); expect(date.addMonths(1).value().toISOString()).toBe('2020-01-31T08:00:00.000Z'); }); it('should add 7 months with success', () => { const date = Dates.init(new Date('2020-01-01T08:00:00')); expect(date.addMonths(7).value().toISOString()).toBe('2020-07-29T08:00:00.000Z'); }); }); describe('addHours', () => { it('should add 1 hour with success', () => { const date = Dates.init(new Date('2020-01-01T08:00:00')); expect(date.addHours(1).value().toISOString()).toBe('2020-01-01T09:00:00.000Z'); }); it('should add 7 hours with success', () => { const date = Dates.init(new Date('2020-01-01T08:00:00')); expect(date.addHours(7).value().toISOString()).toBe('2020-01-01T15:00:00.000Z'); }); }); describe('addMinutes', () => { it('should add 1 minute with success', () => { const date = Dates.init(new Date('2020-01-01T08:00:00')); expect(date.addMinutes(1).value().toISOString()).toBe('2020-01-01T08:01:00.000Z'); }); it('should add 7 minutes with success', () => { const date = Dates.init(new Date('2020-01-01T08:00:00')); expect(date.addMinutes(7).value().toISOString()).toBe('2020-01-01T08:07:00.000Z'); }); }); describe('addWeeks', () => { it('should add 1 week with success', () => { const date1 = Dates.init(new Date('2020-01-01T08:00:00')); const date2 = date1.addWeeks(1); expect(date2.value().toISOString()).toBe('2020-01-08T08:00:00.000Z'); expect(date2.differenceInDays(date1)).toBe(7); }); it('should add 7 weeks with success', () => { const date1 = Dates.init(new Date('2020-01-01T08:00:00')); const date2 = date1.addWeeks(7); expect(date2.value().toISOString()).toBe('2020-02-19T08:00:00.000Z'); expect(date2.differenceInDays(date1)).toBe(49); }); }); describe('addYears', () => { it('should add 1 year with success', () => { const date1 = Dates.init(new Date('2020-01-01T08:00:00')); const date2 = date1.addYears(1); expect(date2.value().toISOString()).toBe('2020-12-31T08:00:00.000Z'); expect(date2.differenceInDays(date1)).toBe(365); }); it('should add 7 years with success', () => { const date1 = Dates.init(new Date('2020-01-01T08:00:00')); const date2 = date1.addYears(7); expect(date2.value().toISOString()).toBe('2026-12-30T08:00:00.000Z'); expect(date2.differenceInDays(date1)).toBe(2555); }); }); describe('subtractDays', () => { it('should subtract 1 day with success', () => { const date1 = Dates.init(new Date('2020-01-01T08:00:00')); const date2 = date1.subtractDays(1); expect(date2.value().toISOString()).toBe('2019-12-31T08:00:00.000Z'); expect(date1.differenceInDays(date2)).toBe(1); }); it('should subtract 7 days with success', () => { const date1 = Dates.init(new Date('2020-01-01T08:00:00')); const date2 = date1.subtractDays(7); expect(date2.value().toISOString()).toBe('2019-12-25T08:00:00.000Z'); expect(date1.differenceInDays(date2)).toBe(7); }); }); describe('subtractMonths', () => { it('should subtract 1 month with success', () => { const date1 = Dates.init(new Date('2020-01-01T08:00:00')); const date2 = date1.subtractMonths(1); expect(date2.value().toISOString()).toBe('2019-12-02T08:00:00.000Z'); expect(date1.differenceInDays(date2)).toBe(30); }); it('should subtract 7 months with success', () => { const date1 = Dates.init(new Date('2020-01-01T08:00:00')); const date2 = date1.subtractMonths(7); expect(date2.value().toISOString()).toBe('2019-06-05T08:00:00.000Z'); expect(date1.differenceInDays(date2)).toBe(210); }); }); describe('subtractHours', () => { it('should subtract 1 hour with success', () => { const date1 = Dates.init(new Date('2020-01-01T08:00:00')); const date2 = date1.subtractHours(1); expect(date2.value().toISOString()).toBe('2020-01-01T07:00:00.000Z'); expect(date1.differenceInHours(date2)).toBe(1); }); it('should subtract 7 hours with success', () => { const date1 = Dates.init(new Date('2020-01-01T08:00:00')); const date2 = date1.subtractHours(7); expect(date2.value().toISOString()).toBe('2020-01-01T01:00:00.000Z'); expect(date1.differenceInHours(date2)).toBe(7); }); }); describe('subtractMinutes', () => { it('should subtract 1 minute with success', () => { const date1 = Dates.init(new Date('2020-01-01T08:00:00')); const date2 = date1.subtractMinutes(1); expect(date2.value().toISOString()).toBe('2020-01-01T07:59:00.000Z'); expect(date1.differenceInMinutes(date2)).toBe(1); }); it('should subtract 7 minutes with success', () => { const date1 = Dates.init(new Date('2020-01-01T08:00:00')); const date2 = date1.subtractMinutes(7); expect(date2.value().toISOString()).toBe('2020-01-01T07:53:00.000Z'); expect(date1.differenceInMinutes(date2)).toBe(7); }); }); describe('subtractWeeks', () => { it('should subtract 1 week with success', () => { const date1 = Dates.init(new Date('2020-01-01T08:00:00')); const date2 = date1.subtractWeeks(1); expect(date2.value().toISOString()).toBe('2019-12-25T08:00:00.000Z'); expect(date1.differenceInDays(date2)).toBe(7); }); it('should subtract 7 weeks with success', () => { const date1 = Dates.init(new Date('2020-01-01T08:00:00')); const date2 = date1.subtractWeeks(7); expect(date2.value().toISOString()).toBe('2019-11-13T08:00:00.000Z'); expect(date1.differenceInDays(date2)).toBe(49); }); }); describe('subtractYears', () => { it('should subtract 1 year with success', () => { const date1 = Dates.init(new Date('2020-01-01T08:00:00')); const date2 = date1.subtractYears(1); expect(date2.value().toISOString()).toBe('2019-01-01T08:00:00.000Z'); expect(date1.differenceInDays(date2)).toBe(365); }); it('should subtract 7 years with success', () => { const date1 = Dates.init(new Date('2020-01-01T08:00:00')); const date2 = date1.subtractYears(7); expect(date2.value().toISOString()).toBe('2013-01-02T08:00:00.000Z'); expect(date1.differenceInDays(date2)).toBe(2555); }); }); describe('differenceInHours', () => { it('should subtract 1 hour with success', () => { const date1 = Dates.init(new Date('2020-01-01T08:00:00')); const date2 = date1.subtractHours(1); expect(date2.value().toISOString()).toBe('2020-01-01T07:00:00.000Z'); expect(date1.differenceInHours(date2)).toBe(1); }); it('should subtract 7 hours with success', () => { const date1 = Dates.init(new Date('2020-01-01T08:00:00')); const date2 = date1.subtractHours(7); expect(date2.value().toISOString()).toBe('2020-01-01T01:00:00.000Z'); expect(date1.differenceInHours(date2)).toBe(7); }); it('should return 0 if provide null', () => { const date = Dates.init(new Date('2020-01-01T08:00:00')); expect(date.differenceInHours(null as any)).toBe(0); }) }); describe('differenceInMinutes', () => { it('should subtract 1 minute with success', () => { const date1 = Dates.init(new Date('2020-01-01T08:00:00')); const date2 = date1.subtractMinutes(1); expect(date2.value().toISOString()).toBe('2020-01-01T07:59:00.000Z'); expect(date1.differenceInMinutes(date2)).toBe(1); }); it('should subtract 7 minutes with success', () => { const date1 = Dates.init(new Date('2020-01-01T08:00:00')); const date2 = date1.subtractMinutes(7); expect(date2.value().toISOString()).toBe('2020-01-01T07:53:00.000Z'); expect(date1.differenceInMinutes(date2)).toBe(7); }); it('should return 0 if provide null value', () => { const date = Dates.init(new Date('2020-01-01T08:00:00')); expect(date.differenceInMinutes(null as any)).toBe(0); }); }); describe('differenceInMonths', () => { it('should subtract 1 month with success', () => { const date1 = Dates.init(new Date('2020-01-01T08:00:00')); const date2 = date1.subtractMonths(1); expect(date2.value().toISOString()).toBe('2019-12-02T08:00:00.000Z'); expect(date1.differenceInMonths(date2)).toBe(0.97); }); it('should subtract 7 months with success', () => { const date1 = Dates.init(new Date('2020-01-01T08:00:00')); const date2 = date1.subtractMonths(7); expect(date2.value().toISOString()).toBe('2019-06-05T08:00:00.000Z'); expect(date1.differenceInMonths(date2)).toBe(6.77); }); it('should return 0 if provide null value', () => { const date = Dates.init(new Date('2020-01-01T08:00:00')); expect(date.differenceInMonths(null as any)).toBe(0); }); }); describe('differenceInYears', () => { it('should subtract 1 year with success', () => { const date1 = Dates.init(new Date('2020-01-01T08:00:00')); const date2 = date1.subtractYears(1); expect(date2.value().toISOString()).toBe('2019-01-01T08:00:00.000Z'); expect(date1.differenceInYears(date2)).toBe(1); }); it('should subtract 7 years with success', () => { const date1 = Dates.init(new Date('2020-01-01T08:00:00')); const date2 = date1.subtractYears(7); expect(date2.value().toISOString()).toBe('2013-01-02T08:00:00.000Z'); expect(date1.differenceInYears(date2)).toBe(6.98); }); it('should return 0 if provide null value', () => { const date = Dates.init(new Date('2020-01-01T08:00:00')); expect(date.differenceInYears(null as any)).toBe(0); }); }); describe('differenceInWeeks', () => { it('should subtract 1 week with success', () => { const date1 = Dates.init(new Date('2020-01-01T08:00:00')); const date2 = date1.subtractWeeks(1); expect(date2.value().toISOString()).toBe('2019-12-25T08:00:00.000Z'); expect(date1.differenceInWeeks(date2)).toBe(1); }); it('should subtract 7 weeks with success', () => { const date1 = Dates.init(new Date('2020-01-01T08:00:00')); const date2 = date1.subtractWeeks(7); expect(date2.value().toISOString()).toBe('2019-11-13T08:00:00.000Z'); expect(date1.differenceInWeeks(date2)).toBe(7); }); it('should return 0 if provide null value', () => { const date = Dates.init(new Date('2020-01-01T08:00:00')); expect(date.differenceInWeeks(null as any)).toBe(0); }); }); describe('differenceInDays', () => { it('should return 0 if provide null value', () => { const date = Dates.init(new Date('2020-01-01T08:00:00')); expect(date.differenceInDays(null as any)).toBe(0); }); }); describe('validation', () => { it('should return true if is valid date', () => { const isValid = Dates.isValidProps(new Date()); expect(isValid).toBeTruthy(); }); it('should return false if is valid date', () => { const isValid = Dates.isValid(new Date()); expect(isValid).toBeTruthy(); }); it('should return false if is not valid date', () => { const isValid = Dates.isValid({} as any); expect(isValid).toBeFalsy(); }); it('should return false if is not valid date', () => { const isValid = Dates.isValidProps({} as any); expect(isValid).toBeFalsy(); }); }); describe('weekend', () => { it('should return true if is weekend', () => { const date = new Date('2024-05-18T00:00:00.000Z'); const isWeekend = Dates.init(date).isWeekend(); expect(isWeekend).toBeTruthy(); expect(Dates.isWeekend(date)); }); it('should return true if is weekend', () => { const date = new Date('2024-05-19T00:00:00.000Z'); const isWeekend = Dates.init(date).isWeekend(); expect(isWeekend).toBeTruthy(); expect(Dates.isWeekend(date)).toBeTruthy(); }); it('should return false if is not weekend', () => { const date = new Date('2024-05-20T00:00:00.000Z'); const isWeekend = Dates.init(date).isWeekend(); expect(isWeekend).toBeFalsy(); expect(Dates.isWeekend(date)).toBeFalsy(); }); }); describe('weekDay', () => { it('should return false if is weekend', () => { const date = new Date('2024-05-18T00:00:00.000Z'); const isWeekend = Dates.init(date).isWeekday(); expect(isWeekend).toBeFalsy(); expect(Dates.isWeekday(date)).toBeFalsy(); }); it('should return false if is weekend', () => { const date = new Date('2024-05-19T00:00:00.000Z'); const isWeekend = Dates.init(date).isWeekday(); expect(isWeekend).toBeFalsy(); expect(Dates.isWeekday(date)).toBeFalsy(); }); it('should return true if is not weekend', () => { const date = new Date('2024-05-20T00:00:00.000Z'); const isWeekend = Dates.init(date).isWeekday(); expect(isWeekend).toBeTruthy(); expect(Dates.isWeekday(date)).toBeTruthy(); }); }); describe('isAfter', () => { it('should return true if date2 is after date1', () => { const date1 = Dates.init(new Date('2024-05-18T00:00:00.000Z')); const date2 = date1.addDays(1); expect(date2.isAfter(date1)).toBeTruthy(); }); it('should return false if date2 is not after date1', () => { const date1 = Dates.init(new Date('2024-05-18T00:00:00.000Z')); const date2 = date1.subtractDays(1); expect(date2.isAfter(date1)).toBeFalsy(); }); it('should return false if provide an invalid value', () => { const date = Dates.init(new Date('2024-05-18T00:00:00.000Z')); expect(date.isAfter(null as any)).toBeFalsy(); }); }); describe('isBefore', () => { it('should return true if date2 is before date1', () => { const date1 = Dates.init(new Date('2024-05-18T00:00:00.000Z')); const date2 = date1.addDays(1); expect(date2.isBefore(date1)).toBeFalsy(); }); it('should return false if date2 is not before date1', () => { const date1 = Dates.init(new Date('2024-05-18T00:00:00.000Z')); const date2 = date1.subtractDays(1); expect(date2.isBefore(date1)).toBeTruthy(); }); it('should return false if provide an invalid value', () => { const date = Dates.init(new Date('2024-05-18T00:00:00.000Z')); expect(date.isBefore(null as any)).toBeFalsy(); }); }); describe('isEqualDate', () => { it('should return true if date2 is equal date1', () => { const date1 = Dates.init(new Date('2024-05-18T00:00:00.000Z')); expect(date1.isEqualDate(date1)).toBeTruthy(); }); it('should return false if date2 is not equal date1', () => { const date1 = Dates.init(new Date('2024-05-18T00:00:00.000Z')); const date2 = date1.subtractDays(1); expect(date2.isEqualDate(date1)).toBeFalsy(); }); it('should return false if provide an invalid value', () => { const date = Dates.init(new Date('2024-05-18T00:00:00.000Z')); expect(date.isEqualDate(null as any)).toBeFalsy(); }); }); describe('format', () => { it('should format to DD-MM-YY', () => { const date = new Date('2024-12-25T00:00:00.000Z'); const format = Dates.init(date).format('DD-MM-YY'); expect(format).toBe('25-12-24'); }); it('should format to DD-MM-YY HH:mm:ss', () => { const date = new Date('2024-12-25T08:10:00.000Z'); const format = Dates.init(date).format('DD-MM-YY HH:mm:ss'); expect(format).toBe('25-12-24 08:10:00'); }); it('should format to DD-MM-YY hh:mm:ss', () => { const date = new Date('2024-12-25T16:10:00.000Z'); const format = Dates.init(date).format('DD-MM-YY hh:mm:ss'); expect(format).toBe('25-12-24 04:10:00 PM'); }); it('should format to DD-MM-YYYY', () => { const date = new Date('2024-12-25T00:00:00.000Z'); const format = Dates.init(date).format('DD-MM-YYYY'); expect(format).toBe('25-12-2024'); }); it('should format to DD-MM-YYYY HH:mm:ss', () => { const date = new Date('2024-12-25T16:10:00.000Z'); const format = Dates.init(date).format('DD-MM-YYYY HH:mm:ss'); expect(format).toBe('25-12-2024 16:10:00'); }); it('should format to DD-MM-YYYY HH:mm:ss', () => { const date = new Date('2024-12-25T16:10:00.000Z'); const format = Dates.init(date).format('DD-MM-YYYY hh:mm:ss'); expect(format).toBe('25-12-2024 04:10:00 PM'); }); it('should format to DD/MM/YY', () => { const date = new Date('2024-12-25T16:10:00.000Z'); const format = Dates.init(date).format('DD/MM/YY'); expect(format).toBe('25/12/24'); }); it('should format to DD/MM/YY', () => { const date = new Date('2024-12-25T16:10:00.000Z'); const format = Dates.init(date).format('DD/MM/YY'); expect(format).toBe('25/12/24'); }); it('should format to DD/MM/YY HH:mm:ss', () => { const date = new Date('2024-12-25T16:10:00.000Z'); const format = Dates.init(date).format('DD/MM/YY HH:mm:ss'); expect(format).toBe('25/12/24 16:10:00'); }); it('should format to DD/MM/YY hh:mm:ss', () => { const date = new Date('2024-12-25T16:10:00.000Z'); const format = Dates.init(date).format('DD/MM/YY hh:mm:ss'); expect(format).toBe('25/12/24 04:10:00 PM'); }); it('should format to DD/MM/YYYY', () => { const date = new Date('2024-12-25T16:10:00.000Z'); const format = Dates.init(date).format('DD/MM/YYYY'); expect(format).toBe('25/12/2024'); }); it('should format to DD/MM/YYYY HH:mm:ss', () => { const date = new Date('2024-12-25T16:10:00.000Z'); const format = Dates.init(date).format('DD/MM/YYYY HH:mm:ss'); expect(format).toBe('25/12/2024 16:10:00'); }); it('should format to DD/MM/YYYY hh:mm:ss', () => { const date = new Date('2024-12-25T16:10:00.000Z'); const format = Dates.init(date).format('DD/MM/YYYY hh:mm:ss'); expect(format).toBe('25/12/2024 04:10:00 PM'); }); it('should format to MM-DD-YY', () => { const date = new Date('2024-12-25T16:10:00.000Z'); const format = Dates.init(date).format('MM-DD-YY'); expect(format).toBe('12-25-24'); }); it('should format to MM-DD-YY HH:mm:ss', () => { const date = new Date('2024-12-25T16:10:00.000Z'); const format = Dates.init(date).format('MM-DD-YY HH:mm:ss'); expect(format).toBe('12-25-24 16:10:00'); }); it('should format to MM-DD-YY hh:mm:ss', () => { const date = new Date('2024-12-25T16:10:00.000Z'); const format = Dates.init(date).format('MM-DD-YY hh:mm:ss'); expect(format).toBe('12-25-24 04:10:00 PM'); }); it('should format to MM-DD-YYYY', () => { const date = new Date('2024-12-25T16:10:00.000Z'); const format = Dates.init(date).format('MM-DD-YYYY'); expect(format).toBe('12-25-2024'); }); it('should format to MM-DD-YYYY HH:mm:ss', () => { const date = new Date('2024-12-25T16:10:00.000Z'); const format = Dates.init(date).format('MM-DD-YYYY HH:mm:ss'); expect(format).toBe('12-25-2024 16:10:00'); }); it('should format to MM-DD-YYYY hh:mm:ss', () => { const date = new Date('2024-12-25T16:10:00.000Z'); const format = Dates.init(date).format('MM-DD-YYYY hh:mm:ss'); expect(format).toBe('12-25-2024 04:10:00 PM'); }); it('should format to MM/DD/YY', () => { const date = new Date('2024-12-25T16:10:00.000Z'); const format = Dates.init(date).format('MM/DD/YY'); expect(format).toBe('12/25/24'); }); it('should format to MM/DD/YY HH:mm:ss', () => { const date = new Date('2024-12-25T16:10:00.000Z'); const format = Dates.init(date).format('MM/DD/YY HH:mm:ss'); expect(format).toBe('12/25/24 16:10:00'); }); it('should format to MM/DD/YY hh:mm:ss', () => { const date = new Date('2024-12-25T16:10:00.000Z'); const format = Dates.init(date).format('MM/DD/YY hh:mm:ss'); expect(format).toBe('12/25/24 04:10:00 PM'); }); it('should format to MM/DD/YYYY', () => { const date = new Date('2024-12-25T16:10:00.000Z'); const format = Dates.init(date).format('MM/DD/YYYY'); expect(format).toBe('12/25/2024'); }); it('should format to MM/DD/YYYY HH:mm:ss', () => { const date = new Date('2024-12-25T16:10:00.000Z'); const format = Dates.init(date).format('MM/DD/YYYY HH:mm:ss'); expect(format).toBe('12/25/2024 16:10:00'); }); it('should format to MM/DD/YYYY hh:mm:ss', () => { const date = new Date('2024-12-25T16:10:00.000Z'); const format = Dates.init(date).format('MM/DD/YYYY hh:mm:ss'); expect(format).toBe('12/25/2024 04:10:00 PM'); }); it('should format to YYYY-MM-DD', () => { const date = new Date('2024-12-25T16:10:00.000Z'); const format = Dates.init(date).format('YYYY-MM-DD'); expect(format).toBe('2024-12-25'); }); it('should format to YYYY-MM-DD HH:mm:ss', () => { const date = new Date('2024-12-25T16:10:00.000Z'); const format = Dates.init(date).format('YYYY-MM-DD HH:mm:ss'); expect(format).toBe('2024-12-25 16:10:00'); }); it('should format to YYYY-MM-DD hh:mm:ss', () => { const date = new Date('2024-12-25T16:10:00.000Z'); const format = Dates.init(date).format('YYYY-MM-DD hh:mm:ss'); expect(format).toBe('2024-12-25 04:10:00 PM'); }); it('should format to DD.MM.YYYY', () => { const date = new Date('2024-12-25T16:10:00.000Z'); const format = Dates.init(date).format('DD.MM.YY'); expect(format).toBe('25.12.24'); }); it('should format to MM.DD.YYYY', () => { const date = new Date('2024-12-25T16:10:00.000Z'); const format = Dates.init(date).format('MM.DD.YYYY'); expect(format).toBe('12.25.2024'); }); it('should format to DD.MM.YY', () => { const date = new Date('2024-12-25T16:10:00.000Z'); const format = Dates.init(date).format('DD.MM.YY'); expect(format).toBe('25.12.24'); }); it('should format to MM.DD.YY', () => { const date = new Date('2024-12-25T16:10:00.000Z'); const format = Dates.init(date).format('MM.DD.YY'); expect(format).toBe('12.25.24'); }); it('should format to YYYY.MM.DD', () => { const date = new Date('2024-12-25T16:10:00.000Z'); const format = Dates.init(date).format('YYYY.MM.DD'); expect(format).toBe('2024.12.25'); }); it('should format to DD.MM.YYYY hh:mm:ss', () => { const date = new Date('2024-12-25T16:10:00.000Z'); const format = Dates.init(date).format('DD.MM.YYYY hh:mm:ss'); expect(format).toBe('25.12.2024 04:10:00 PM'); }); it('should format to DD.MM.YYYY HH:mm:ss', () => { const date = new Date('2024-12-25T16:10:00.000Z'); const format = Dates.init(date).format('DD.MM.YYYY HH:mm:ss'); expect(format).toBe('25.12.2024 16:10:00'); }); it('should format to DD.MM.YY hh:mm:ss', () => { const date = new Date('2024-12-25T16:10:00.000Z'); const format = Dates.init(date).format('DD.MM.YY hh:mm:ss'); expect(format).toBe('25.12.24 04:10:00 PM'); }); it('should format to DD.MM.YY HH:mm:ss', () => { const date = new Date('2024-12-25T16:10:00.000Z'); const format = Dates.init(date).format('DD.MM.YY HH:mm:ss'); expect(format).toBe('25.12.24 16:10:00'); }); it('should format to MM.DD.YYYY hh:mm:ss', () => { const date = new Date('2024-12-25T16:10:00.000Z'); const format = Dates.init(date).format('MM.DD.YYYY hh:mm:ss'); expect(format).toBe('12.25.2024 04:10:00 PM'); }); it('should format to MM.DD.YYYY HH:mm:ss', () => { const date = new Date('2024-12-25T16:10:00.000Z'); const format = Dates.init(date).format('MM.DD.YYYY HH:mm:ss'); expect(format).toBe('12.25.2024 16:10:00'); }); it('should format to MM.DD.YY hh:mm:ss', () => { const date = new Date('2024-12-25T16:10:00.000Z'); const format = Dates.init(date).format('MM.DD.YY hh:mm:ss'); expect(format).toBe('12.25.24 04:10:00 PM'); }); it('should format to MM.DD.YY HH:mm:ss', () => { const date = new Date('2024-12-25T16:10:00.000Z'); const format = Dates.init(date).format('MM.DD.YY HH:mm:ss'); expect(format).toBe('12.25.24 16:10:00'); }); it('should format to YYYY.MM.DD hh:mm:ss', () => { const date = new Date('2024-12-25T16:10:00.000Z'); const format = Dates.init(date).format('YYYY.MM.DD hh:mm:ss'); expect(format).toBe('2024.12.25 04:10:00 PM'); }); it('should format to YYYY.MM.DD HH:mm:ss', () => { const date = new Date('2024-12-25T16:10:00.000Z'); const format = Dates.init(date).format('YYYY.MM.DD HH:mm:ss'); expect(format).toBe('2024.12.25 16:10:00'); }); it('should format to YYYY-MM.DD HH:mm:ss', () => { const date = new Date('2024-12-25T16:10:00.000Z'); const format = Dates.init(date).format('YYYY_MM_DD' as any); expect(format).toBe('2024-12-25'); }); }); }); ================================================ FILE: packages/date/index.ts ================================================ import { Result, ValueObject } from 'rich-domain'; import { TimeZones } from './types'; const timeFormat12h = { hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: true }; const timeFormat24h = { hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false }; function commonDateFormat(timeFormat = null) { const date = { year: 'numeric', month: '2-digit', day: '2-digit' }; const time = timeFormat ? { ...timeFormat } : null; return { date, time }; } const DateFormats = { // only dates 'DD-MM-YYYY': commonDateFormat(), 'MM-DD-YYYY': commonDateFormat(), 'DD-MM-YY': commonDateFormat(), 'MM-DD-YY': commonDateFormat(), 'MM/DD/YYYY': commonDateFormat(), 'MM/DD/YY': commonDateFormat(), 'DD/MM/YYYY': commonDateFormat(), 'DD/MM/YY': commonDateFormat(), 'YYYY-MM-DD': commonDateFormat(), 'YYYY/MM/DD': commonDateFormat(), // separate with dot 'DD.MM.YYYY': commonDateFormat(), 'MM.DD.YYYY': commonDateFormat(), 'DD.MM.YY': commonDateFormat(), 'MM.DD.YY': commonDateFormat(), 'YYYY.MM.DD': commonDateFormat(), 'DD.MM.YYYY hh:mm:ss': commonDateFormat(timeFormat12h), 'DD.MM.YY hh:mm:ss': commonDateFormat(timeFormat12h), 'MM.DD.YYYY hh:mm:ss': commonDateFormat(timeFormat12h), 'MM.DD.YY hh:mm:ss': commonDateFormat(timeFormat12h), 'DD.MM.YYYY HH:mm:ss': commonDateFormat(timeFormat24h), 'DD.MM.YY HH:mm:ss': commonDateFormat(timeFormat24h), 'MM.DD.YYYY HH:mm:ss': commonDateFormat(timeFormat24h), 'MM.DD.YY HH:mm:ss': commonDateFormat(timeFormat24h), 'YYYY.MM.DD hh:mm:ss': commonDateFormat(timeFormat12h), 'YYYY.MM.DD HH:mm:ss': commonDateFormat(timeFormat24h), // with time 12h 'DD/MM/YYYY hh:mm:ss': commonDateFormat(timeFormat12h), 'DD-MM-YYYY hh:mm:ss': commonDateFormat(timeFormat12h), 'DD/MM/YY hh:mm:ss': commonDateFormat(timeFormat12h), 'DD-MM-YY hh:mm:ss': commonDateFormat(timeFormat12h), 'MM/DD/YYYY hh:mm:ss': commonDateFormat(timeFormat12h), 'MM-DD-YYYY hh:mm:ss': commonDateFormat(timeFormat12h), 'MM/DD/YY hh:mm:ss': commonDateFormat(timeFormat12h), 'MM-DD-YY hh:mm:ss': commonDateFormat(timeFormat12h), // with time 24h 'DD/MM/YYYY HH:mm:ss': commonDateFormat(timeFormat24h), 'DD-MM-YYYY HH:mm:ss': commonDateFormat(timeFormat24h), 'DD/MM/YY HH:mm:ss': commonDateFormat(timeFormat24h), 'DD-MM-YY HH:mm:ss': commonDateFormat(timeFormat24h), 'MM/DD/YYYY HH:mm:ss': commonDateFormat(timeFormat24h), 'MM-DD-YYYY HH:mm:ss': commonDateFormat(timeFormat24h), 'MM/DD/YY HH:mm:ss': commonDateFormat(timeFormat24h), 'MM-DD-YY HH:mm:ss': commonDateFormat(timeFormat24h), // manual 'YYYY/MM/DD hh:mm:ss': commonDateFormat(timeFormat12h), 'YYYY-MM-DD hh:mm:ss': commonDateFormat(timeFormat12h), 'YYYY/MM/DD HH:mm:ss': commonDateFormat(timeFormat24h), 'YYYY-MM-DD HH:mm:ss': commonDateFormat(timeFormat24h), }; type DateFormat = keyof typeof DateFormats; type FormatParams = { date: Intl.DateTimeFormatOptions; time: Intl.DateTimeFormatOptions | null }; export class Dates extends ValueObject { protected static readonly MESSAGE: string = 'Invalid date value'; private readonly ONE_DAY: number = 86400000; private readonly ONE_HOUR: number = 3600000; private readonly ONE_MINUTE: number = 60000; private readonly ONE_MONTH: number = 2678400000; private readonly ONE_WEEK: number = 604800000; private readonly ONE_YEAR: number = 31622400000; private constructor(props: Date) { super(props); } /** * @returns instance value as Date */ value(): Date { return this.props; } /** * * @param days as number to be added * @returns instance of Dates with updated value */ addDays(days: number): Dates { return new Dates(this.util.date(this.props).add(days).days()); } /** * * @param months as number to be added * @returns instance of Dates with updated value */ addMonths(months: number): Dates { return new Dates(this.util.date(this.props).add(months).months()); } /** * * @param hours as number to be added * @returns instance of Dates with updated value */ addHours(hours: number): Dates { return new Dates(this.util.date(this.props).add(hours).hours()); } /** * * @param minutes as number to be added * @returns instance of Dates with updated value */ addMinutes(minutes: number): Dates { return new Dates(this.util.date(this.props).add(minutes).minutes()); } /** * * @param weeks as number to be added * @returns instance of Dates with updated value */ addWeeks(weeks: number): Dates { return new Dates(this.util.date(this.props).add(weeks).weeks()); } /** * * @param years as number to be added * @returns instance of Dates with updated value */ addYears(years: number): Dates { return new Dates(this.util.date(this.props).add(years).years()); } /** * * @param days as number to be subtracted * @returns instance of Dates with updated value */ subtractDays(days: number): Dates { return new Dates(this.util.date(this.props).remove(days).days()); } /** * * @param months as number to be subtracted * @returns instance of Dates with updated value */ subtractMonths(months: number): Dates { return new Dates(this.util.date(this.props).remove(months).months()); } /** * * @param hours as number to be subtracted * @returns instance of Dates with updated value */ subtractHours(hours: number): Dates { return new Dates(this.util.date(this.props).remove(hours).hours()); } /** * * @param minutes as number to be subtracted * @returns instance of Dates with updated value */ subtractMinutes(minutes: number): Dates { return new Dates(this.util.date(this.props).remove(minutes).minutes()); } /** * * @param weeks as number to be subtracted * @returns instance of Dates with updated value */ subtractWeeks(weeks: number): Dates { return new Dates(this.util.date(this.props).remove(weeks).weeks()); } /** * * @param years as number to be subtracted * @returns instance of Dates with updated value */ subtractYears(years: number): Dates { return new Dates(this.util.date(this.props).remove(years).years()); } /** * * @param date as Date to be compared or instance of Dates * @returns result as number of days * @summary returns positive result if instance value is greater than provided value */ differenceInDays(date: Date | Dates): number { if (date instanceof Date) { const currentDays = this.props.getTime() / this.ONE_DAY; const dateTime = date.getTime() / this.ONE_DAY; const calc = (currentDays * 100 - dateTime * 100) / 100; return parseFloat(calc.toFixed(2)); } if (date instanceof Dates) { return this.differenceInDays(date.value()); } return 0; } /** * * @param date to be compared or instance of Dates * @returns result as number of hours * @summary returns positive result if instance value is greater than provided value */ differenceInHours(date: Date | Dates): number { if (date instanceof Date) { const currentDays = this.props.getTime() / this.ONE_HOUR; const dateTime = date.getTime() / this.ONE_HOUR; const calc = (currentDays * 100 - dateTime * 100) / 100; return parseFloat(calc.toFixed(2)); } if (date instanceof Dates) { return this.differenceInHours(date.value()); } return 0; } /** * * @param date to be compared or instance of Dates * @returns result as number of minutes * @summary returns positive result if instance value is greater than provided value */ differenceInMinutes(date: Date | Dates): number { if (date instanceof Date) { const currentDays = this.props.getTime() / this.ONE_MINUTE; const dateTime = date.getTime() / this.ONE_MINUTE; const calc = (currentDays * 100 - dateTime * 100) / 100; return parseFloat(calc.toFixed(2)); } if (date instanceof Dates) { return this.differenceInMinutes(date.value()); } return 0; } /** * * @param date to be compared or Dates * @returns result as number of months * @summary returns positive result if instance value is greater than provided value */ differenceInMonths(date: Date | Dates): number { if (date instanceof Date) { const currentDays = this.props.getTime() / this.ONE_MONTH; const dateTime = date.getTime() / this.ONE_MONTH; const calc = (currentDays * 100 - dateTime * 100) / 100; return parseFloat(calc.toFixed(2)); } if (date instanceof Dates) { return this.differenceInMonths(date.value()); } return 0; } /** * * @param date to be compared or Dates * @returns result as number of years * @summary returns positive result if instance value is greater than provided value */ differenceInYears(date: Date | Dates): number { if (date instanceof Date) { const currentDays = this.props.getTime() / this.ONE_YEAR; const dateTime = date.getTime() / this.ONE_YEAR; const calc = (currentDays * 100 - dateTime * 100) / 100; return parseFloat(calc.toFixed(2)); } if (date instanceof Dates) { return this.differenceInYears(date.value()); } return 0; } /** * * @param date to be compared or instance of Dates * @returns result as number of weeks * @summary returns positive result if instance value is greater than provided value */ differenceInWeeks(date: Date | Dates): number { if (date instanceof Date) { const currentDays = this.props.getTime() / this.ONE_WEEK; const dateTime = date.getTime() / this.ONE_WEEK; const calc = (currentDays * 100 - dateTime * 100) / 100; return parseFloat(calc.toFixed(2)); } if (date instanceof Dates) { return this.differenceInWeeks(date.value()); } return 0; } /** * * @param format pattern to be applied * @param timeZone as TimeZone name as string to be considered * @returns formatted date as string */ format(format: DateFormat, timeZone?: TimeZones): string { const firstChars = format.slice(0, 2); const dateLocale = firstChars === 'DD' ? 'pt-BR' : 'en-US'; const formatDate = (date: Date, formats: FormatParams) => { const locale = (format.slice(0, 2) === 'YY') ? 'sv-SE' : undefined; const year = (format.includes('YYYY')) ? 'numeric' : '2-digit'; const dateFn = new Intl.DateTimeFormat(locale || dateLocale, { month: '2-digit', day: '2-digit', ...(formats?.date ?? {}), year, timeZone }).format(date); if (formats?.time) { const timeFn = new Intl.DateTimeFormat(dateLocale, { ...formats.time, timeZone }).format(date); return { dateFn, timeFn } } return { dateFn, timeFn: '' }; }; const result = formatDate(this.props, DateFormats[format] as FormatParams); const applySeparator = (date: string): string => { if (format.includes('/')) return date.replace(/-/g, '/'); if (format.includes('-')) return date.replace(/\//g, '-'); if (format.includes('.')) return date.replace(/\/|\-/g, '.'); return date; } return applySeparator(`${result.dateFn} ${result.timeFn}`.trim()); } /** * @param value as Date or date string * @returns true if provided value is instance of date, and false if not. */ public static isValidProps(value: Date | string | number): boolean { if(typeof value === 'number') { const date = new Date(value); return date instanceof Date; } if (typeof value === 'string') { const date = new Date(value); return ( !isNaN(date as unknown as number) && date.toISOString().slice(0, 10) === value && !isNaN(date.getFullYear()) ); } return this.validator.isDate(value); } /** * @param value as Date or date string or number as timestamp * @returns true if provided value is instance of date, and false if not. */ public static isValid(value: Date | string): boolean { return this.isValidProps(value); } /** * * @returns true if date day is week day [Monday-Friday] */ isWeekday(): boolean { return !this.isWeekend() } /** * * @returns true if date value day is weekend day [Saturday-Sunday] */ isWeekend(): boolean { return this.validator.date(this.props).isWeekend(); } /** * * @param date value as date. * @returns true if date day is week day [Monday-Friday] */ public static isWeekday(date: Date): boolean { return !this.isWeekend(date); } /** * * @param date value as date. * @returns true if date day is weekend day [Sat-Sunday] */ public static isWeekend(date: Date): boolean { return this.validator.date(date).isWeekend(); } /** * * @param date as Date or instance of Dates * @returns true or false. True if instance date is greater than provided value * @example * * const date = new Date("1989-05-31 11:42:00"); * * const vo = Dates.create(date).value(); * * const isAfter = vo.isAfter(new Date()); * * console.log(isAfter); * * > false * * ... */ isAfter(date: Date | Dates): boolean { if (date instanceof Date) { return this.validator.date(this.props).isAfterThan(date); } if (date instanceof Dates) { return this.isAfter(date.value()); } return false; } /** * * @param date as Date or instance of Dates * @returns true or false. True if instance date is less than provided value * @example * * const date = new Date("1989-05-31 11:42:00"); * * const vo = Dates.create(date).value(); * * const isBefore = vo.isBefore(new Date()); * * console.log(isBefore); * * > true * * ... */ isBefore(date: Date | Dates): boolean { if (date instanceof Date) { return this.validator.date(this.props).isBeforeThan(date); } if (date instanceof Dates) { return this.isBefore(date.value()); } return false; } /** * * @param date as Date or instance of Dates * @returns true or false. True if instance date is equal to provided value */ isEqualDate(date: Date | Dates): boolean { if (date instanceof Date) { const time = date.getTime(); const instanceTime = this.props.getTime(); return this.validator.number(time).isEqualTo(instanceTime); } if (date instanceof Dates) { return this.isEqualDate(date.value()); } return false; } /** * * @param value value as Date or date string or number as timestamp * @returns instance of Dates or throw an error */ public static init(value?: Date | string | number): Dates { if (!value) return new Dates(new Date()); const isValidValue = Dates.isValidProps(value); if (!isValidValue) throw new Error(Dates.MESSAGE); if (value instanceof Date) return new Dates(value); return new Dates(new Date(value)); } /** * * @param value value as Date or date string or number as timestamp * @returns Result of Dates */ public static create(date?: Date | string | number): Result { const value = date ?? new Date(); const isValid = Dates.isValidProps(value); if (!isValid) return Result.fail(Dates.MESSAGE); if (value instanceof Date) return Result.Ok(new Dates(value)); return Result.Ok(new Dates(new Date(value))); } } export default Dates; ================================================ FILE: packages/date/package.json ================================================ { "name": "@type-ddd/date", "description": "Library that provides TypeScript type definitions for handling Dates in Domain-Driven Design contexts. It facilitates the validation and manipulation of Dates.", "version": "0.1.0", "main": "index.js", "types": "index.d.ts", "author": "Alessandro Dev", "license": "MIT", "publishConfig": { "access": "public" }, "keywords": [ "Complexity", "Business Logic", "DDD", "Domain Driving Design", "DDD-Utils", "Base Value Object", "Clean Architecture", "Date", "Validation", "Formatting", "Value Object", "Utility", "Security", "Standards" ], "scripts": { "build": "tsc" }, "peerDependencies": { "rich-domain": "^1.25.0" }, "files": [ "index.js", "index.d.ts", "util.js", "util.d.ts", "types.js", "types.d.ts" ], "repository": { "type": "git", "url": "git+https://github.com/4lessandrodev/type-ddd.git" }, "bugs": { "url": "https://github.com/4lessandrodev/type-ddd/issues" }, "homepage": "https://github.com/4lessandrodev/type-ddd/tree/main/packages/date", "gitHead": "4cb9159bde8d6fc951e9d902feed2ad25da49fa4" } ================================================ FILE: packages/date/tsconfig.build.json ================================================ { "extends": "../tsconfig.build.json", "compilerOptions": { "outDir": ".", "rootDir": ".", "paths": {} }, "exclude": [ "node_modules", "dist", "__tests__/**/*", "*.spec.ts" ], "references": [] } ================================================ FILE: packages/date/tsconfig.json ================================================ { "extends": "../tsconfig.build.json", "compilerOptions": { "types": [ "node" ] }, "files": [], "include": [], "references": [ { "path": "./tsconfig.build.json" } ] } ================================================ FILE: packages/date/types.ts ================================================ export type TimeZones = "Africa/Abidjan" | "Africa/Accra" | "Africa/Addis_Ababa" | "Africa/Algiers" | "Africa/Asmara" | "Africa/Asmera" | "Africa/Bamako" | "Africa/Bangui" | "Africa/Banjul" | "Africa/Bissau" | "Africa/Blantyre" | "Africa/Brazzaville" | "Africa/Bujumbura" | "Africa/Cairo" | "Africa/Casablanca" | "Africa/Ceuta" | "Africa/Conakry" | "Africa/Dakar" | "Africa/Dar_es_Salaam" | "Africa/Djibouti" | "Africa/Douala" | "Africa/El_Aaiun" | "Africa/Freetown" | "Africa/Gaborone" | "Africa/Harare" | "Africa/Johannesburg" | "Africa/Juba" | "Africa/Kampala" | "Africa/Khartoum" | "Africa/Kigali" | "Africa/Kinshasa" | "Africa/Lagos" | "Africa/Libreville" | "Africa/Lome" | "Africa/Luanda" | "Africa/Lubumbashi" | "Africa/Lusaka" | "Africa/Malabo" | "Africa/Maputo" | "Africa/Maseru" | "Africa/Mbabane" | "Africa/Mogadishu" | "Africa/Monrovia" | "Africa/Nairobi" | "Africa/Ndjamena" | "Africa/Niamey" | "Africa/Nouakchott" | "Africa/Ouagadougou" | "Africa/Porto-Novo" | "Africa/Sao_Tome" | "Africa/Timbuktu" | "Africa/Tripoli" | "Africa/Tunis" | "Africa/Windhoek" | "America/Adak" | "America/Anchorage" | "America/Anguilla" | "America/Antigua" | "America/Araguaina" | "America/Argentina/Buenos_Aires" | "America/Argentina/Catamarca" | "America/Argentina/ComodRivadavia" | "America/Argentina/Cordoba" | "America/Argentina/Jujuy" | "America/Argentina/La_Rioja" | "America/Argentina/Mendoza" | "America/Argentina/Rio_Gallegos" | "America/Argentina/Salta" | "America/Argentina/San_Juan" | "America/Argentina/San_Luis" | "America/Argentina/Tucuman" | "America/Argentina/Ushuaia" | "America/Aruba" | "America/Asuncion" | "America/Atikokan" | "America/Atka" | "America/Bahia" | "America/Bahia_Banderas" | "America/Barbados" | "America/Belem" | "America/Belize" | "America/Blanc-Sablon" | "America/Boa_Vista" | "America/Bogota" | "America/Boise" | "America/Buenos_Aires" | "America/Cambridge_Bay" | "America/Campo_Grande" | "America/Cancun" | "America/Caracas" | "America/Catamarca" | "America/Cayenne" | "America/Cayman" | "America/Chicago" | "America/Chihuahua" | "America/Coral_Harbour" | "America/Cordoba" | "America/Costa_Rica" | "America/Creston" | "America/Cuiaba" | "America/Curacao" | "America/Danmarkshavn" | "America/Dawson" | "America/Dawson_Creek" | "America/Denver" | "America/Detroit" | "America/Dominica" | "America/Edmonton" | "America/Eirunepe" | "America/El_Salvador" | "America/Ensenada" | "America/Fort_Nelson" | "America/Fort_Wayne" | "America/Fortaleza" | "America/Glace_Bay" | "America/Godthab" | "America/Goose_Bay" | "America/Grand_Turk" | "America/Grenada" | "America/Guadeloupe" | "America/Guatemala" | "America/Guayaquil" | "America/Guyana" | "America/Halifax" | "America/Havana" | "America/Hermosillo" | "America/Indiana/Indianapolis" | "America/Indiana/Knox" | "America/Indiana/Marengo" | "America/Indiana/Petersburg" | "America/Indiana/Tell_City" | "America/Indiana/Vevay" | "America/Indiana/Vincennes" | "America/Indiana/Winamac" | "America/Indianapolis" | "America/Inuvik" | "America/Iqaluit" | "America/Jamaica" | "America/Jujuy" | "America/Juneau" | "America/Kentucky/Louisville" | "America/Kentucky/Monticello" | "America/Knox_IN" | "America/Kralendijk" | "America/La_Paz" | "America/Lima" | "America/Los_Angeles" | "America/Louisville" | "America/Lower_Princes" | "America/Maceio" | "America/Managua" | "America/Manaus" | "America/Marigot" | "America/Martinique" | "America/Matamoros" | "America/Mazatlan" | "America/Mendoza" | "America/Menominee" | "America/Merida" | "America/Metlakatla" | "America/Mexico_City" | "America/Miquelon" | "America/Moncton" | "America/Monterrey" | "America/Montevideo" | "America/Montreal" | "America/Montserrat" | "America/Nassau" | "America/New_York" | "America/Nipigon" | "America/Nome" | "America/Noronha" | "America/North_Dakota/Beulah" | "America/North_Dakota/Center" | "America/North_Dakota/New_Salem" | "America/Ojinaga" | "America/Panama" | "America/Pangnirtung" | "America/Paramaribo" | "America/Phoenix" | "America/Port-au-Prince" | "America/Port_of_Spain" | "America/Porto_Acre" | "America/Porto_Velho" | "America/Puerto_Rico" | "America/Punta_Arenas" | "America/Rainy_River" | "America/Rankin_Inlet" | "America/Recife" | "America/Regina" | "America/Resolute" | "America/Rio_Branco" | "America/Rosario" | "America/Santa_Isabel" | "America/Santarem" | "America/Santiago" | "America/Santo_Domingo" | "America/Sao_Paulo" | "America/Scoresbysund" | "America/Shiprock" | "America/Sitka" | "America/St_Barthelemy" | "America/St_Johns" | "America/St_Kitts" | "America/St_Lucia" | "America/St_Thomas" | "America/St_Vincent" | "America/Swift_Current" | "America/Tegucigalpa" | "America/Thule" | "America/Thunder_Bay" | "America/Tijuana" | "America/Toronto" | "America/Tortola" | "America/Vancouver" | "America/Virgin" | "America/Whitehorse" | "America/Winnipeg" | "America/Yakutat" | "America/Yellowknife" | "Antarctica/Casey" | "Antarctica/Davis" | "Antarctica/DumontDUrville" | "Antarctica/Macquarie" | "Antarctica/Mawson" | "Antarctica/McMurdo" | "Antarctica/Palmer" | "Antarctica/Rothera" | "Antarctica/South_Pole" | "Antarctica/Syowa" | "Antarctica/Troll" | "Antarctica/Vostok" | "Arctic/Longyearbyen" | "Asia/Aden" | "Asia/Almaty" | "Asia/Amman" | "Asia/Anadyr" | "Asia/Aqtau" | "Asia/Aqtobe" | "Asia/Ashgabat" | "Asia/Ashkhabad" | "Asia/Atyrau" | "Asia/Baghdad" | "Asia/Bahrain" | "Asia/Baku" | "Asia/Bangkok" | "Asia/Barnaul" | "Asia/Beirut" | "Asia/Bishkek" | "Asia/Brunei" | "Asia/Calcutta" | "Asia/Chita" | "Asia/Choibalsan" | "Asia/Chongqing" | "Asia/Chungking" | "Asia/Colombo" | "Asia/Dacca" | "Asia/Damascus" | "Asia/Dhaka" | "Asia/Dili" | "Asia/Dubai" | "Asia/Dushanbe" | "Asia/Famagusta" | "Asia/Gaza" | "Asia/Harbin" | "Asia/Hebron" | "Asia/Ho_Chi_Minh" | "Asia/Hong_Kong" | "Asia/Hovd" | "Asia/Irkutsk" | "Asia/Istanbul" | "Asia/Jakarta" | "Asia/Jayapura" | "Asia/Jerusalem" | "Asia/Kabul" | "Asia/Kamchatka" | "Asia/Karachi" | "Asia/Kashgar" | "Asia/Kathmandu" | "Asia/Katmandu" | "Asia/Khandyga" | "Asia/Kolkata" | "Asia/Krasnoyarsk" | "Asia/Kuala_Lumpur" | "Asia/Kuching" | "Asia/Kuwait" | "Asia/Macao" | "Asia/Macau" | "Asia/Magadan" | "Asia/Makassar" | "Asia/Manila" | "Asia/Muscat" | "Asia/Nicosia" | "Asia/Novokuznetsk" | "Asia/Novosibirsk" | "Asia/Omsk" | "Asia/Oral" | "Asia/Phnom_Penh" | "Asia/Pontianak" | "Asia/Pyongyang" | "Asia/Qatar" | "Asia/Qostanay" | "Asia/Qyzylorda" | "Asia/Rangoon" | "Asia/Riyadh" | "Asia/Saigon" | "Asia/Sakhalin" | "Asia/Samarkand" | "Asia/Seoul" | "Asia/Shanghai" | "Asia/Singapore" | "Asia/Srednekolymsk" | "Asia/Taipei" | "Asia/Tashkent" | "Asia/Tbilisi" | "Asia/Tehran" | "Asia/Tel_Aviv" | "Asia/Thimbu" | "Asia/Thimphu" | "Asia/Tokyo" | "Asia/Tomsk" | "Asia/Ujung_Pandang" | "Asia/Ulaanbaatar" | "Asia/Ulan_Bator" | "Asia/Urumqi" | "Asia/Ust-Nera" | "Asia/Vientiane" | "Asia/Vladivostok" | "Asia/Yakutsk" | "Asia/Yangon" | "Asia/Yekaterinburg" | "Asia/Yerevan" | "Atlantic/Azores" | "Atlantic/Bermuda" | "Atlantic/Canary" | "Atlantic/Cape_Verde" | "Atlantic/Faeroe" | "Atlantic/Faroe" | "Atlantic/Jan_Mayen" | "Atlantic/Madeira" | "Atlantic/Reykjavik" | "Atlantic/South_Georgia" | "Atlantic/St_Helena" | "Atlantic/Stanley" | "Australia/ACT" | "Australia/Adelaide" | "Australia/Brisbane" | "Australia/Broken_Hill" | "Australia/Canberra" | "Australia/Currie" | "Australia/Darwin" | "Australia/Eucla" | "Australia/Hobart" | "Australia/LHI" | "Australia/Lindeman" | "Australia/Lord_Howe" | "Australia/Melbourne" | "Australia/NSW" | "Australia/North" | "Australia/Perth" | "Australia/Queensland" | "Australia/South" | "Australia/Sydney" | "Australia/Tasmania" | "Australia/Victoria" | "Australia/West" | "Australia/Yancowinna" | "Brazil/Acre" | "Brazil/DeNoronha" | "Brazil/East" | "Brazil/West" | "CET" | "CST6CDT" | "Canada/Atlantic" | "Canada/Central" | "Canada/Eastern" | "Canada/Mountain" | "Canada/Newfoundland" | "Canada/Pacific" | "Canada/Saskatchewan" | "Canada/Yukon" | "Chile/Continental" | "Chile/EasterIsland" | "Cuba" | "EET" | "EST" | "EST5EDT" | "Egypt" | "Eire" | "Etc/GMT" | "Etc/GMT+0" | "Etc/GMT+1" | "Etc/GMT+10" | "Etc/GMT+11" | "Etc/GMT+12" | "Etc/GMT+2" | "Etc/GMT+3" | "Etc/GMT+4" | "Etc/GMT+5" | "Etc/GMT+6" | "Etc/GMT+7" | "Etc/GMT+8" | "Etc/GMT+9" | "Etc/GMT-0" | "Etc/GMT-1" | "Etc/GMT-10" | "Etc/GMT-11" | "Etc/GMT-12" | "Etc/GMT-13" | "Etc/GMT-14" | "Etc/GMT-2" | "Etc/GMT-3" | "Etc/GMT-4" | "Etc/GMT-5" | "Etc/GMT-6" | "Etc/GMT-7" | "Etc/GMT-8" | "Etc/GMT-9" | "Etc/GMT0" | "Etc/Greenwich" | "Etc/UCT" | "Etc/UTC" | "Etc/Universal" | "Etc/Zulu" | "Europe/Amsterdam" | "Europe/Andorra" | "Europe/Astrakhan" | "Europe/Athens" | "Europe/Belfast" | "Europe/Belgrade" | "Europe/Berlin" | "Europe/Bratislava" | "Europe/Brussels" | "Europe/Bucharest" | "Europe/Budapest" | "Europe/Busingen" | "Europe/Chisinau" | "Europe/Copenhagen" | "Europe/Dublin" | "Europe/Gibraltar" | "Europe/Guernsey" | "Europe/Helsinki" | "Europe/Isle_of_Man" | "Europe/Istanbul" | "Europe/Jersey" | "Europe/Kaliningrad" | "Europe/Kiev" | "Europe/Kirov" | "Europe/Lisbon" | "Europe/Ljubljana" | "Europe/London" | "Europe/Luxembourg" | "Europe/Madrid" | "Europe/Malta" | "Europe/Mariehamn" | "Europe/Minsk" | "Europe/Monaco" | "Europe/Moscow" | "Europe/Nicosia" | "Europe/Oslo" | "Europe/Paris" | "Europe/Podgorica" | "Europe/Prague" | "Europe/Riga" | "Europe/Rome" | "Europe/Samara" | "Europe/San_Marino" | "Europe/Sarajevo" | "Europe/Saratov" | "Europe/Simferopol" | "Europe/Skopje" | "Europe/Sofia" | "Europe/Stockholm" | "Europe/Tallinn" | "Europe/Tirane" | "Europe/Tiraspol" | "Europe/Ulyanovsk" | "Europe/Uzhgorod" | "Europe/Vaduz" | "Europe/Vatican" | "Europe/Vienna" | "Europe/Vilnius" | "Europe/Volgograd" | "Europe/Warsaw" | "Europe/Zagreb" | "Europe/Zaporozhye" | "Europe/Zurich" | "GB" | "GB-Eire" | "GMT" | "GMT+0" | "GMT-0" | "GMT0" | "Greenwich" | "HST" | "Hongkong" | "Iceland" | "Indian/Antananarivo" | "Indian/Chagos" | "Indian/Christmas" | "Indian/Cocos" | "Indian/Comoro" | "Indian/Kerguelen" | "Indian/Mahe" | "Indian/Maldives" | "Indian/Mauritius" | "Indian/Mayotte" | "Indian/Reunion" | "Iran" | "Israel" | "Jamaica" | "Japan" | "Kwajalein" | "Libya" | "MET" | "MST" | "MST7MDT" | "Mexico/BajaNorte" | "Mexico/BajaSur" | "Mexico/General" | "NZ" | "NZ-CHAT" | "Navajo" | "PRC" | "PST8PDT" | "Pacific/Apia" | "Pacific/Auckland" | "Pacific/Bougainville" | "Pacific/Chatham" | "Pacific/Chuuk" | "Pacific/Easter" | "Pacific/Efate" | "Pacific/Enderbury" | "Pacific/Fakaofo" | "Pacific/Fiji" | "Pacific/Funafuti" | "Pacific/Galapagos" | "Pacific/Gambier" | "Pacific/Guadalcanal" | "Pacific/Guam" | "Pacific/Honolulu" | "Pacific/Johnston" | "Pacific/Kiritimati" | "Pacific/Kosrae" | "Pacific/Kwajalein" | "Pacific/Majuro" | "Pacific/Marquesas" | "Pacific/Midway" | "Pacific/Nauru" | "Pacific/Niue" | "Pacific/Norfolk" | "Pacific/Noumea" | "Pacific/Pago_Pago" | "Pacific/Palau" | "Pacific/Pitcairn" | "Pacific/Pohnpei" | "Pacific/Ponape" | "Pacific/Port_Moresby" | "Pacific/Rarotonga" | "Pacific/Saipan" | "Pacific/Samoa" | "Pacific/Tahiti" | "Pacific/Tarawa" | "Pacific/Tongatapu" | "Pacific/Truk" | "Pacific/Wake" | "Pacific/Wallis" | "Pacific/Yap" | "Poland" | "Portugal" | "ROC" | "ROK" | "Singapore" | "Turkey" | "UCT" | "US/Alaska" | "US/Aleutian" | "US/Arizona" | "US/Central" | "US/East-Indiana" | "US/Eastern" | "US/Hawaii" | "US/Indiana-Starke" | "US/Michigan" | "US/Mountain" | "US/Pacific" | "US/Samoa" | "UTC" | "Universal" | "W-SU" | "WET" | "Zulu" | "America/Rio_Branco" | "America/Manaus" | "America/Cuiaba" | "America/Porto_Velho" | "America/Boa_Vista" | "America/Recife" | "America/Fortaleza" | "America/Noronha"; ================================================ FILE: packages/date/util.ts ================================================ interface CpfDigits { penultimateDigit: number; ultimateDigit: number; } const removeSpecialCharsFromCpfRegex = /[\.]|[-]/g; export const formatValueToCpfPattern = (cpf: string): string => { const cpfValue = removeSpecialCharsFromCpf(cpf); let formattedValue: string = ''; let index: number = 0; while (formattedValue.length < 14 && index < 11) { if (index === 3 || index === 6) { formattedValue += '.'; } else if (index === 9) { formattedValue += '-'; } formattedValue += cpfValue[index]; index++; } return formattedValue; }; export const removeSpecialCharsFromCpf = (cpf: string): string => { return cpf.replace(removeSpecialCharsFromCpfRegex, ''); }; const getCpfDigitsNumbers = (cpf: string): CpfDigits => { const lastTwoNumbers = cpf.slice(cpf.length - 2); const penultimateDigit = parseInt(lastTwoNumbers[0]); const ultimateDigit = parseInt(lastTwoNumbers[1]); return { penultimateDigit, ultimateDigit, }; }; const transformCpfInArrNumber = (cpf: string): number[] => { var arr: number[] = []; let index = 0; while (index < 9) { arr.push(parseInt(cpf[index])); index++; } return arr; }; export const calculateCpfDigits = (cpfNumbers: number[]): CpfDigits => { const factor = 11; let index = 0; let startAuxValue = 10; let totalForDigit = 0; while (index < 9) { totalForDigit = totalForDigit + cpfNumbers[index] * startAuxValue; startAuxValue--; index++; } const calcPDigit = totalForDigit % factor; const resultPDigit = factor - calcPDigit; const zeroIfPGreaterThanNine = resultPDigit > 9 ? 0 : resultPDigit; const penultimateDigit = zeroIfPGreaterThanNine; index = 0; startAuxValue = 11; totalForDigit = 0; cpfNumbers.push(penultimateDigit); while (index < 10) { totalForDigit = totalForDigit + cpfNumbers[index] * startAuxValue; startAuxValue--; index++; } const calcUDigit = totalForDigit % factor; const resultUDigit = factor - calcUDigit; const zeroIfGreaterThanNine = resultUDigit > 9 ? 0 : resultUDigit; const ultimateDigit = zeroIfGreaterThanNine; return { penultimateDigit, ultimateDigit, }; }; export const isValidCpfDigit = (cpf: string): boolean => { const onlyNumbers = removeSpecialCharsFromCpf(cpf); if (onlyNumbers.length !== 11) { return false; } const digits = getCpfDigitsNumbers(onlyNumbers); const arrNumbers = transformCpfInArrNumber(onlyNumbers); const validDigits = calculateCpfDigits(arrNumbers); return ( digits.penultimateDigit === validDigits.penultimateDigit && digits.ultimateDigit === validDigits.ultimateDigit ); }; export default isValidCpfDigit; ================================================ FILE: packages/email/CHANGELOG.md ================================================ # Change Log All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. ## [0.0.5-alpha.0](https://github.com/4lessandrodev/type-ddd/compare/@type-ddd/email@0.0.2...@type-ddd/email@0.0.5-alpha.0) (2024-12-16) ## 4.0.5 (2024-11-28) ### Bug Fixes * change type create method return null [#194](https://github.com/4lessandrodev/type-ddd/issues/194) ([2cd03bf](https://github.com/4lessandrodev/type-ddd/commit/2cd03bf34387f4889a0a292ba350f2c0cfc753b7)) * solve error module not found [#449](https://github.com/4lessandrodev/type-ddd/issues/449) ([e9d14f6](https://github.com/4lessandrodev/type-ddd/commit/e9d14f694cafc9c2123cc31055a4561f460a82d3)) ## 4.0.3 (2024-07-26) # Changelog All notable changes to this project will be documented in this file. ## Unreleased --- ## Released --- ### [0.0.4] - 2024-11-28 ### Fix - update rich-domain lib to check nullish type: now 'create' return a possibly null value in Result instance. --- ### [0.0.3] - 2024-09-26 ### Fix - Corrected `"files"` in `package.json` to include `utils.js` and `utils.d.ts`, resolving module not found errors during compilation. --- ### [0.0.2] - 2024-05-31 ### Docs - Doc: update documentation --- ### [0.0.1] - 2024-05-31 ### Base - Create base value object as single pack ================================================ FILE: packages/email/README.md ================================================ # `@type-ddd/email` > The @type-ddd/email module provides a class Email for handling email addresses in TypeScript. It includes methods for validating email addresses, extracting parts of the email (such as nickname and domain), and creating instances of validated emails. ## Installation Install `rich-domain` and `@type-ddd/email` with your favorite package manager: ```sh npm i rich-domain @type-ddd/email #OR yarn add rich-domain @type-ddd/email ``` ## Usage ```ts import { Email } from '@type-ddd/email'; // Check if is valid value const isValid = Email.isValid('sample@domain.com'); // true // Initialize Email instance with a valid email address const email = Email.init('example@example.com'); // OR // Create Email instance from provided email address const result = Email.create('example@example.com'); // Get parts of the email address const nickname = email.nick(); const domain = email.domain(); ``` ### Block some domains If you want to block some specifics domains ```ts import { Email } from '@type-ddd/email'; const list = ['hack.com']; Reflect.set(Email, 'BLOCKED_DOMAINS', list); const isValid = Email.isValid('user@hack.com'); // false const isValid = Email.isValid('user@gmail.com'); // true ``` ### Allowed some domains If you want to allow only some specifics domains ```ts import { Email } from '@type-ddd/email'; const list = ['my-company.com']; Reflect.set(Email, 'VALID_DOMAINS', list); const isValid = Email.isValid('user@my-company.com'); // true const isValid = Email.isValid('user@gmail.com'); // false ``` ================================================ FILE: packages/email/__tests__/email-validator.spec.ts ================================================ import IsValidEmail, { GetCharCode, IsSpecialChar } from '../email.validator.util'; describe('email-validator', () => { it('should return 0 if is not string', () => { const code = GetCharCode(3 as any); expect(code).toBe(0); }); it('should return true', () => { const is = IsSpecialChar('+'); expect(is).toBeTruthy(); }); it('should to be false', () => { const invalid = 'invalid-log-email-value'.repeat(10); const long = invalid + '@gmail.com'; expect(IsValidEmail(long)).toBeFalsy(); }); it('should to be false', () => { const invalid = 'invalid-log-email-value'.repeat(20); const long = invalid + '@gmail.com'; expect(IsValidEmail(long)).toBeFalsy(); }); it('should to be false', () => { expect(IsValidEmail(0 as any)).toBeFalsy(); }); }); ================================================ FILE: packages/email/__tests__/email.value-object.spec.ts ================================================ import EmailValueObject from '../index'; describe('email-value-object.util', () => { it('should init an instance with success', () => { const init = () => EmailValueObject.init('sample@live.com'); expect(init).not.toThrowError(); }); it('should throw an error on init an instance with invalid value', () => { const init = () => EmailValueObject.init('invalid'); expect(init).toThrowError(); }); it('should validate', () => { expect(EmailValueObject.isValid('invalid')).toBeFalsy(); expect(EmailValueObject.isValid('sample@gmail.com')).toBeTruthy(); }); it('should create a valid email with success', () => { const valueObject = EmailValueObject.create('john+doe@domain.com'); expect(valueObject.isOk()).toBe(true); }); it('should to be invalid email nick ends with +', () => { const valueObject = EmailValueObject.create('johndoe+@domain.com'); expect(valueObject.isFail()).toBe(true); }); it('should to be invalid email nick starts with +', () => { const valueObject = EmailValueObject.create('+johndoe@domain.com'); expect(valueObject.isFail()).toBe(true); }); it('should to be invalid email domain starts with +', () => { const valueObject = EmailValueObject.create('johndoe@+domain.com'); expect(valueObject.isFail()).toBe(true); }); it('should to be invalid email domain ends with +', () => { const valueObject = EmailValueObject.create('johndoe@domain.com+'); expect(valueObject.isFail()).toBe(true); }); it('should be defined', () => { const valueObject = EmailValueObject.create; expect(valueObject).toBeDefined(); }); it('should create a valid email with success', () => { const valueObject = EmailValueObject.create('valid_email@domain.com'); expect(valueObject.isOk()).toBe(true); }); it('should transform value to lower on create', () => { const valueObject = EmailValueObject.create( 'Valid_EmaiL@Domain.Com', ).value(); expect(valueObject?.value()).toBe('valid_email@domain.com'); }); it('should create and get value from a valid email with success', () => { const valueObject = EmailValueObject.create( 'valid_email@domain.com', ).value(); expect(valueObject?.value()).toBe('valid_email@domain.com'); }); it('should to be a valid email', () => { const valueObject = EmailValueObject.create( 'test-email-124@domain.com', ); expect(valueObject.isOk()).toBe(true); expect(valueObject.value()?.value()).toBe('test-email-124@domain.com'); }); it('should to be a valid email', () => { const valueObject = EmailValueObject.create( 'test-some-value-12@domain.name', ); expect(valueObject.isOk()).toBe(true); expect(valueObject.value()?.value()).toBe( 'test-some-value-12@domain.name', ); }); it('should fail if provide an invalid value', () => { const valueObject = EmailValueObject.create('invalid_email'); expect(valueObject.isFail()).toBe(true); expect(valueObject.error()).toBe('Invalid email'); }); it('should fail if provide an invalid value', () => { const valueObject = EmailValueObject.create( 'invalid_email@domain.long-subdomain.br', ); expect(valueObject.isFail()).toBe(true); expect(valueObject.error()).toBe('Invalid email'); }); it('should fail if provide an invalid value', () => { const valueObject = EmailValueObject.create( 'invalid_email@domain.net.long-country', ); expect(valueObject.isFail()).toBe(true); expect(valueObject.error()).toBe('Invalid email'); }); it('should fail if provide an invalid value', () => { const valueObject = EmailValueObject.create( 'invalid_email@-domain.net.tz', ); expect(valueObject.isFail()).toBe(true); expect(valueObject.error()).toBe('Invalid email'); }); it('should fail if provide an invalid value', () => { const valueObject = EmailValueObject.create( '-invalid_email@domain.net.tz', ); expect(valueObject.isFail()).toBe(true); expect(valueObject.error()).toBe('Invalid email'); }); it('should fail if provide an invalid value', () => { const valueObject = EmailValueObject.create( 'invalid_email@domain.net.tz-', ); expect(valueObject.isFail()).toBe(true); expect(valueObject.error()).toBe('Invalid email'); }); it('should fail if provide an invalid value', () => { const valueObject = EmailValueObject.create( 'invalid_email@domain.net.br8', ); expect(valueObject.isFail()).toBe(true); expect(valueObject.error()).toBe('Invalid email'); }); it('should get nick', () => { const valueObject = EmailValueObject.create( 'username@domain.com', ).value(); expect(valueObject?.nick()).toBe('username'); }); it('should get domain', () => { const valueObject = EmailValueObject.create( 'username@domain.com', ).value(); expect(valueObject?.domain()).toBe('domain.com'); }); it('should create value object with success', () => { const valueObject = EmailValueObject.create( 'username.nickname@domain.com', ); expect(valueObject.isOk()).toBeTruthy(); expect(valueObject.value()?.value()).toBe( 'username.nickname@domain.com', ); }); it('should create value object with success', () => { const valueObject = EmailValueObject.create('rocio65@gmail.com'); expect(valueObject.isOk()).toBeTruthy(); expect(valueObject.value()?.value()).toBe('rocio65@gmail.com'); }); it('should create value object with success', () => { const valueObject = EmailValueObject.create('user_nick2.0@hotmail.com'); expect(valueObject.isOk()).toBeTruthy(); expect(valueObject.value()?.value()).toBe('user_nick2.0@hotmail.com'); }); it('should create value object with success', () => { const valueObject = EmailValueObject.create('rocio_65@gmail.com'); expect(valueObject.isOk()).toBeTruthy(); expect(valueObject.value()?.value()).toBe('rocio_65@gmail.com'); }); it('should create value object with success', () => { const valueObject = EmailValueObject.create('4you@gmail.com'); expect(valueObject.isOk()).toBeTruthy(); expect(valueObject.value()?.value()).toBe('4you@gmail.com'); }); it('should fails if not provide a domain', () => { const valueObject = EmailValueObject.create('4you@gmail'); expect(valueObject.isFail()).toBeTruthy(); }); it('should fails if provide two dots', () => { const valueObject = EmailValueObject.create( 'my-email8950@hotmail..com', ); expect(valueObject.isFail()).toBeTruthy(); }); it('should fails if provide an invalid domain', () => { const valueObject = EmailValueObject.create( 'inVaLLiD_Email@MYDomain.longInvalid.br', ); expect(valueObject.isFail()).toBeTruthy(); }); it('should fails if provide an space', () => { const valueObject = EmailValueObject.create('invalid email@domain.com'); expect(valueObject.isFail()).toBeTruthy(); }); it('should get domain with success', () => { const vo = EmailValueObject.create('my-email@domain.com').value(); expect(vo?.domain()).toBe('domain.com'); }); it('should get nick with success', () => { const vo = EmailValueObject.create('my-email@domain.com').value(); expect(vo?.get('value')).toBe('my-email@domain.com'); expect(vo?.nick()).toBe('my-email'); }); it('should to be a valid domain', () => { const result = EmailValueObject.create('some-name@gmail.com'); expect(result.isOk()).toBeTruthy(); }); }); describe('email-blocked-list', () => { it('should fail if domain is on blocked list', () => { Reflect.set(EmailValueObject, 'BLOCKED_DOMAINS', [ 'gmail.com', 'hotmail.com', ]); const result = EmailValueObject.create('some-name@gmail.com'); expect(result.isFail()).toBeTruthy(); }); it('should fail is domain is not available', () => { Reflect.set(EmailValueObject, 'VALID_DOMAINS', [ 'types.com', 'finance.com', ]); const result1 = EmailValueObject.create('some-name@crypto.com'); const result2 = EmailValueObject.create('some-name@finance.com'); expect(result1.isFail()).toBeTruthy(); expect(result2.isOk()).toBeTruthy(); }); it('should fail with custom message', () => { Reflect.set(EmailValueObject, 'MESSAGE', 'Valor inválido para email'); const result = EmailValueObject.create('invalid'); expect(result.isFail()).toBeTruthy(); expect(result.error()).toBe('Valor inválido para email'); }); }); ================================================ FILE: packages/email/email.validator.util.ts ================================================ /** * a-z = 97 - 122 * 0-9 = 48 - 57 * 95 = _ * 45 = - * 64 = @ * 46 = . */ const ValidChars = { min: 97, max: 122, specials: [95, 45, 64, 46, 43] }; const ValidCharsNum = { min: 48, max: 57 }; export const GetCharCode = (char: string): number => { return typeof char === 'string' ? char.charCodeAt(0) : 0; }; export const IsSpecialChar = (char: string): boolean => { const code = GetCharCode(char); return ValidChars.specials.includes(code); }; export const IsAlphabet = (char: string): boolean => { const code = GetCharCode(char); const isGreaterThanMin = code >= ValidChars.min; const isLessThanMax = code <= ValidChars.max; return isGreaterThanMin && isLessThanMax; }; export const IsNumber = (char: string): boolean => { const charCode = GetCharCode(char); const isValid = charCode >= ValidCharsNum.min && charCode <= ValidCharsNum.max; return isValid; }; export const IsValidChar = (char: string): boolean => { const isAlphabet = IsAlphabet(char); const isSpecial = IsSpecialChar(char); const isNumber = IsNumber(char); return isAlphabet || isSpecial || isNumber; }; export const HasValidLength = (email: string): boolean => { return typeof email === 'string' && email.length <= 256; }; export const IsValidPart = (part: string): boolean => { const hifen = 45; const isValidSpecialChar = (char: string): boolean => GetCharCode(char) === hifen; const chars = part.replace(/\./g, '').split(''); let i = 0; while (chars[i]) { const isValidChar = IsAlphabet(chars[i]) || IsNumber(chars[i]) || isValidSpecialChar(chars[i]); if (!isValidChar) return false; i++; } return true; }; export const IsValidOrganizationName = (name: string): boolean => { return name.length <= 10 && name.length >= 2; }; export const IsValidCountry = (country: string): boolean => { const isValidLength = country.length > 0 && country.length <= 4; if (!isValidLength) return false; const chars = country.split(''); let i = 0; while (chars[i]) { const isChar = IsAlphabet(chars[i]); if (!isChar) return false; i++; } return true; }; export const IsValidNick = (email: string): boolean => { const nick = email.split('@')?.[0]; return ( !nick.startsWith('+') && !nick.endsWith('+') && !nick.startsWith('-') && !nick.endsWith('-') ); }; export const IsValidDomain = (email: string): boolean => { const domain = email.split('@'); const parts = domain[1].split('.'); if (parts.length === 1 || parts.length > 3) return false; const isInValidStartAndEnd = domain[1].startsWith('-') || domain[1].endsWith('-'); if (isInValidStartAndEnd) return false; const isLessThanMax = email.length <= 64; if (!isLessThanMax) return false; const isValidOrgName = IsValidOrganizationName(parts[1]); if (typeof parts[2] === 'string') { const isValidCountry = IsValidCountry(parts[2]); if (!isValidCountry) return false; } if (!isValidOrgName) return false; let i = 0; while (parts[i]) { const isValidPart = IsValidPart(parts[i]); if (!isValidPart) return false; i++; } return true; }; /** * @description validate email * @param email string with @ symbol * @returns true if is a valid email and false if not */ export const IsValidEmail = (email: string): boolean => { const isString: boolean = typeof email === 'string'; if (!isString) return false; const trimEmail = email.trim().toLowerCase(); const isValidFirsChar: boolean = IsAlphabet(trimEmail[0]) || IsNumber(trimEmail[0]); if (!isValidFirsChar) return false; const hasOnlyOneAt: boolean = trimEmail.split('') .filter((char) => char === '@').length === 1; const hasOnlyOnePlus: boolean = trimEmail.split('') .filter((char) => char === '+').length > 1; if (!hasOnlyOneAt || hasOnlyOnePlus) return false; const isValidLength = HasValidLength(trimEmail); if (!isValidLength) return false; const isValidDomain = IsValidDomain(trimEmail); if (!isValidDomain) return false; if (!IsValidNick(trimEmail)) return false; const hasInvalidChar = trimEmail .split('') .map((char): boolean => IsValidChar(char)) .includes(false); if (hasInvalidChar) return false; return true; }; export default IsValidEmail; ================================================ FILE: packages/email/index.ts ================================================ import { Result, ValueObject } from 'rich-domain'; import IsValidEmail from './email.validator.util'; export class Email extends ValueObject { protected static readonly BLOCKED_DOMAINS: Array = []; protected static readonly VALID_DOMAINS: Array = []; protected static readonly MESSAGE: string = 'Invalid email'; private constructor(props: string) { super(props); } value(): string { return this.props; } nick(): string { return this.props.slice(0, this.props.indexOf('@')); } domain(): string { return this.props .slice(this.props.indexOf('@') + 1); } public static isValid(value: string): boolean { return this.isValidProps(value); } /** * @description validate email value * @param email string * @returns true if email value is valid and returns false if not. * * @requires email not greater than 256 char. * @requires contain symbol @ * @requires contain [domain].[org].[optional country] * @requires contain [nick letters] with [hifen or numbers] * @requires starts [a-z] * @requires ends [a-z] */ public static isValidProps(email: string): boolean { const isValid = IsValidEmail(email); if (!isValid) return false; const domain = email.slice(email.indexOf('@') + 1).toLowerCase(); const isBlockedDomain = Email.BLOCKED_DOMAINS.map( (blockedDomain) => blockedDomain.toLowerCase().includes(domain), ).includes(true); if (Email.VALID_DOMAINS.length === 0) { return !isBlockedDomain; } const isAvailable = Email.VALID_DOMAINS.map((freeDomain) => freeDomain.toLowerCase().includes(domain), ).includes(true); return isAvailable && !isBlockedDomain; } /** * * @param value value as string * @returns instance of Email or throw an error */ public static init(value: string): Email { const isValidValue = Email.isValidProps(value); if (!isValidValue) throw new Error(Email.MESSAGE); return new Email(value.toLowerCase()); } public static create(value: string): Result { if (!Email.isValidProps(value)) { return Result.fail(Email.MESSAGE); } return Result.Ok(new Email(value.toLowerCase())); } } export default Email; ================================================ FILE: packages/email/package.json ================================================ { "name": "@type-ddd/email", "description": "Library that provides TypeScript type definitions for handling Email in Domain-Driven Design contexts. It facilitates the validation and manipulation of emails.", "version": "0.1.0", "main": "index.js", "types": "index.d.ts", "author": "Alessandro Dev", "license": "MIT", "publishConfig": { "access": "public" }, "keywords": [ "Complexity", "Business Logic", "DDD", "Domain Driving Design", "DDD-Utils", "Base Value Object", "Clean Architecture", "Email", "Validation", "Formatting", "Value Object", "Utility", "Security", "Standards" ], "scripts": { "build": "tsc" }, "peerDependencies": { "rich-domain": "^1.25.0" }, "files": [ "index.js", "index.d.ts", "email.validator.util.js", "email.validator.util.d.ts" ], "repository": { "type": "git", "url": "git+https://github.com/4lessandrodev/type-ddd.git" }, "bugs": { "url": "https://github.com/4lessandrodev/type-ddd/issues" }, "homepage": "https://github.com/4lessandrodev/type-ddd/tree/main/packages/email", "gitHead": "4cb9159bde8d6fc951e9d902feed2ad25da49fa4" } ================================================ FILE: packages/email/tsconfig.build.json ================================================ { "extends": "../tsconfig.build.json", "compilerOptions": { "outDir": ".", "rootDir": ".", "paths": {} }, "exclude": [ "node_modules", "dist", "__tests__/**/*", "*.spec.ts" ], "references": [] } ================================================ FILE: packages/email/tsconfig.json ================================================ { "extends": "../tsconfig.build.json", "compilerOptions": { "types": [ "node" ] }, "files": [], "include": [], "references": [ { "path": "./tsconfig.build.json" } ] } ================================================ FILE: packages/logger/CHANGELOG.md ================================================ # Change Log All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. ## [0.0.3-alpha.0](https://github.com/4lessandrodev/type-ddd/compare/@type-ddd/logger@0.0.2...@type-ddd/logger@0.0.3-alpha.0) (2024-12-16) **Note:** Version bump only for package @type-ddd/logger # Changelog All notable changes to this project will be documented in this file. ## Unreleased --- ## Released ### [0.0.2] - 2024-05-31 ### Docs - Doc: update documentation --- ### [0.0.1] - 2024-05-31 ### Base - Create base value object as single pack ================================================ FILE: packages/logger/README.md ================================================ # `types-ddd` > TODO: description ## Usage ``` const typesDdd = require('types-ddd'); // TODO: DEMONSTRATE API ``` ================================================ FILE: packages/logger/__tests__/logger.spec.ts ================================================ import Logger, { checkEnv } from '../index'; describe('Logger', () => { it('should log if is not production and log is not off', () => { process.env.NODE_ENV = undefined; process.env.TYPES_DDD_LOGS = undefined; const callback = jest.fn(() => 'Info'); checkEnv(callback); expect(callback).toHaveBeenCalled(); }); it('should not log if is production and log is not off', () => { process.env.NODE_ENV = 'production'; process.env.TYPES_DDD_LOGS = undefined; const callback = jest.fn(() => 'Info'); checkEnv(callback); expect(callback).not.toHaveBeenCalled(); }); it('should not log if is not production and log is off', () => { process.env.NODE_ENV = undefined; process.env.TYPES_DDD_LOGS = 'off'; const callback = jest.fn(() => 'Info'); checkEnv(callback); expect(callback).not.toHaveBeenCalled(); }); it('should log if is production and log is error', () => { process.env.NODE_ENV = 'production'; process.env.TYPES_DDD_LOGS = 'error'; const callback = jest.fn(() => 'Error'); checkEnv(callback, 'error'); expect(callback).toHaveBeenCalled(); }); it('should log if is production and log is error', () => { process.env.NODE_ENV = 'production'; process.env.TYPES_DDD_LOGS = 'info'; const callback = jest.fn(() => 'Info'); checkEnv(callback, 'info'); expect(callback).toHaveBeenCalled(); }); it('should log if is production and log is warn', () => { process.env.NODE_ENV = 'production'; process.env.TYPES_DDD_LOGS = 'warn'; const callback = jest.fn(() => 'Warn'); checkEnv(callback, 'warn'); expect(callback).toHaveBeenCalled(); }); it('should not log if is production and log is error, but is info log', () => { process.env.NODE_ENV = 'production'; process.env.TYPES_DDD_LOGS = 'error'; const callback = jest.fn(() => 'Error'); checkEnv(callback, 'info'); expect(callback).not.toHaveBeenCalled(); }); it('should not log if is production and log is off', () => { process.env.NODE_ENV = 'production'; process.env.TYPES_DDD_LOGS = 'off'; const callback = jest.fn(() => 'Info'); checkEnv(callback); expect(callback).not.toHaveBeenCalled(); }); it('logger must print message', () => { process.env.NODE_ENV = undefined; process.env.TYPES_DDD_LOGS = undefined; Logger.info('some success message'); }); }); ================================================ FILE: packages/logger/index.ts ================================================ import pino, { BaseLogger } from 'pino'; import { LoggerOptions } from 'pino'; class DefaultLogger { protected static pino: BaseLogger = null as unknown as BaseLogger; protected static config: LoggerOptions = { transport: { target: 'pino-pretty', options: { translateTime: 'HH:MM:ss', ignore: 'pid,hostname', prettyPrint: { colorize: true, levelFirst: true, }, }, }, }; public static instance() { if (!DefaultLogger.pino) { this.pino = pino(DefaultLogger.config); } return this.pino; } } type LogsType = 'error' | 'info' | 'warn'; export const checkEnv = (callback: Function, type?: LogsType): void => { const isProduction = process.env.NODE_ENV === 'production'; const isLogOff = process.env.TYPES_DDD_LOGS === 'off'; const errorTypeMatch = process.env.TYPES_DDD_LOGS === type; if ((!isProduction && !isLogOff) || errorTypeMatch) { callback(); } }; const loggerInstance = DefaultLogger.instance(); const Logger = { info: (message: string) => { const callback = () => loggerInstance.info(message); checkEnv(callback, 'info'); }, error: (message: string) => { const callback = () => loggerInstance.error(message); checkEnv(callback, 'error'); }, warn: (message: string) => { const callback = () => loggerInstance.warn(message); checkEnv(callback, 'warn'); }, }; export { Logger }; export default Logger; ================================================ FILE: packages/logger/package.json ================================================ { "name": "@type-ddd/logger", "description": "This package provide utils file and interfaces to assistant build a complex application with domain driving design", "version": "0.1.0", "main": "index.js", "types": "index.d.ts", "author": "Alessandro Dev", "license": "MIT", "publishConfig": { "access": "public" }, "scripts": { "build": "tsc" }, "dependencies": { "pino": "^9.0.0", "pino-pretty": "^13.0.0" }, "files": [ "index.js", "index.d.ts" ], "repository": { "type": "git", "url": "git+https://github.com/4lessandrodev/type-ddd.git" }, "bugs": { "url": "https://github.com/4lessandrodev/type-ddd/issues" }, "homepage": "https://github.com/4lessandrodev/type-ddd/tree/main/packages/logger", "gitHead": "4cb9159bde8d6fc951e9d902feed2ad25da49fa4" } ================================================ FILE: packages/logger/tsconfig.build.json ================================================ { "extends": "../tsconfig.build.json", "compilerOptions": { "outDir": ".", "rootDir": ".", "paths": {} }, "exclude": [ "node_modules", "dist", "__tests__/**/*", "*.spec.ts" ], "references": [] } ================================================ FILE: packages/logger/tsconfig.json ================================================ { "extends": "../tsconfig.build.json", "compilerOptions": { "types": [ "node" ] }, "files": [], "include": [], "references": [ { "path": "./tsconfig.build.json" } ] } ================================================ FILE: packages/money/CHANGELOG.md ================================================ # Change Log All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. ## [0.0.4-alpha.0](https://github.com/4lessandrodev/type-ddd/compare/@type-ddd/money@0.0.2...@type-ddd/money@0.0.4-alpha.0) (2024-12-16) ## 4.0.5 (2024-11-28) ### Bug Fixes * change type create method return null [#194](https://github.com/4lessandrodev/type-ddd/issues/194) ([2cd03bf](https://github.com/4lessandrodev/type-ddd/commit/2cd03bf34387f4889a0a292ba350f2c0cfc753b7)) ## 4.0.3 (2024-07-26) # Changelog All notable changes to this project will be documented in this file. ## Unreleased --- ## Released --- ### [0.0.3] - 2024-11-28 ### Fix - update rich-domain lib to check nullish type: now 'create' return a possibly null value in Result instance. ### [0.0.2] - 2024-05-31 ### Docs - Doc: update documentation --- ### [0.0.1] - 2024-05-14 ### Base - Create base value object as single pack ================================================ FILE: packages/money/README.md ================================================ # `@type-ddd/money` > The @type-ddd/money library provides TypeScript type definitions for handling Money in Domain-Driven Design contexts. It facilitates the validation, formatting, and manipulation of monetary values, adhering to DDD principles. --- ## Installation Install `rich-domain` and `@type-ddd/money` with your favorite package manager --- ```sh npm i rich-domain @type-ddd/money # OR yarn add rich-domain @type-ddd/money ``` ## Usage ```ts import { Money } from '@type-ddd/money' // Initialize a Money object with the provided value const amount = Money.init(100); // OR const result = Money.create(100); // Check if a value is a valid monetary amount const isValid = Money.isValid(100); // Sum two monetary values or Money objects const total = Money.sum(amount, 50); // Subtract one monetary value or Money object from another const difference = Money.subtract(total, 25); // Multiply two monetary values or Money objects const product = Money.multiply(difference, 2); // Divide one monetary value or Money object by another const quotient = Money.divide(product, 4); // Round down the monetary value to the nearest integer const floorValue = quotient.floor(); // Round up the monetary value to the nearest integer const ceilValue = quotient.ceil(); // Calculate compound interest based on the provided rate and periods const interest = Money.compoundInterest(5, 10); // Generate a random monetary value within the specified range const randomAmount = Money.random(10, 100); // Calculate the average value among the provided Money objects const average = Money.average([amount, total, difference]); // Convert the current Money value to another currency using the provided exchange rate const convertedAmount = amount.convertTo(2); // Generate a formatted string representing the monetary value in a specific currency and locale const formattedAmount = amount.coin('USD', 'en-US'); console.log(formattedAmount); // Output: $100.00 (assuming 100 is the amount in USD) ``` ================================================ FILE: packages/money/__tests__/money.spec.ts ================================================ import { Money } from '../index'; describe('money', () => { describe('create', () => { it('should create instance with success', () => { const result = Money.create(1); expect(result.isOk()).toBeTruthy(); }); it('should create instance with success', () => { const result = Money.create(-1); expect(result.isOk()).toBeTruthy(); }); it('should create instance with success', () => { const result = Money.init(1); expect(result.value()).toBe(1); }); it('should create instance with success', () => { const result = Money.init(-1); expect(result.value()).toBe(-1); }); it('should return fail', () => { const result = Money.create(Number.MAX_SAFE_INTEGER + 1); expect(result.isFail()).toBeTruthy(); }); it('should return fail', () => { const result = Money.create(Number.MIN_SAFE_INTEGER - 1); expect(result.isFail()).toBeTruthy(); }); it('should throws', () => { const result = () => Money.init(Number.MAX_SAFE_INTEGER + 1); expect(result).toThrowError(); }); it('should throws', () => { const result = () => Money.init(Number.MIN_SAFE_INTEGER - 1); expect(result).toThrowError(); }); it('should return 0', () => { const amount = Money.zero(); expect(amount.value()).toBe(0); }); it('should return 10', () => { const amount = Money.ten(); expect(amount.value()).toBe(10); }); it('should return 100', () => { const amount = Money.one_hundred(); expect(amount.value()).toBe(100); }); it('should return 1000', () => { const amount = Money.one_thousand(); expect(amount.value()).toBe(1000); }); }); describe('isValid', () => { it('should return false', () => { const isValid = Money.isValid(Number.MIN_SAFE_INTEGER - 1); expect(isValid).toBeFalsy(); }); it('should return false', () => { const isValid = Money.isValid(Number.MAX_SAFE_INTEGER + 1); expect(isValid).toBeFalsy(); }); it('should return true', () => { const isValid = Money.isValid(1000); expect(isValid).toBeTruthy(); }); it('should return true', () => { const isValid = Money.isValid(-1000); expect(isValid).toBeTruthy(); }); }); describe('coin', () => { it('should transform to coin', () => { const total = Money.one_hundred(); expect(total.coin('BRL', 'pt-BR')).toBe('R$ 100,00'); expect(total.coin('USD', 'pt-BR')).toBe('US$ 100,00'); expect(total.coin('USD', 'en-US')).toBe('$100.00'); expect(total.coin('BRL', 'en-US')).toBe('R$100.00'); }); it('should get default', () => { const coin = Money.one().coin(); expect(coin).toBe('R$ 1,00'); }); }); describe('sum', () => { it('should sum with success', () => { const total = Money.one_thousand(); const amount = total.sum(Money.ten()); expect(amount.value()).toBe(1010); }); it('should sum with success', () => { const total = Money.one_thousand(); const amount = total.sum(10); expect(amount.value()).toBe(1010); }); it('should sum with success', () => { const total = Money.one_thousand(); const amount = total.sum(-10); expect(amount.value()).toBe(990); }); }); describe('subtract', () => { it('should subtract with success', () => { const total = Money.one_thousand(); const amount = total.subtract(10); expect(amount.value()).toBe(990); }); it('should subtract with success', () => { const total = Money.one_thousand(); const amount = total.subtract(Money.ten()); expect(amount.value()).toBe(990); }); it('should subtract with success', () => { const total = Money.one_thousand(); const amount = total.subtract(-10); expect(amount.value()).toBe(1010); }); it('should subtract with success', () => { const total = Money.subtract(1, 10); expect(total).toBe(-9); expect(Money.subtract(Money.one(), Money.ten())).toBe(-9); }); }); describe('multiply', () => { it('should return 100', () => { const ten = Money.ten(); const total = ten.multiply(Money.ten()); expect(total.value()).toBe(100); }); it('should return 100', () => { const ten = Money.ten(); const total = ten.multiply(10); expect(total.value()).toBe(100); }); }); describe('divide', () => { it('should return 10', () => { const amount = Money.one_hundred(); const total = amount.divide(10); expect(total.value()).toBe(10); }); it('should return 10', () => { const amount = Money.one_hundred(); const total = amount.divide(Money.ten()); expect(total.value()).toBe(10); }); }); describe('addPercent', () => { it('should add percent with success', () => { const money = Money.init(200); expect(money.addPercent(20).value()).toBe(240); }); it('should discount percent with success', () => { const money = Money.init(200); expect(money.addPercent(-20).value()).toBe(160); }); }); describe('percent', () => { it('should return 10', () => { const amount = Money.one_hundred(); const total = amount.percent(10); expect(total.value()).toBe(10); }); it('should return -50', () => { const amount = Money.one_hundred().multiply(5); const total = amount.percent(-10); expect(total.value()).toBe(-50); }); }); describe('isGt', () => { it('should return true', () => { const a = Money.one_hundred(); const b = Money.ten(); expect(a.isGt(b)).toBeTruthy(); expect(a.isGt(10)).toBeTruthy(); }); it('should return false', () => { const a = Money.one_hundred(); const b = Money.ten(); expect(b.isGt(a)).toBeFalsy(); expect(Money.ten().isGt(Money.ten())).toBeFalsy(); expect(Money.ten().isGt(10)).toBeFalsy(); }); }); describe('isGte', () => { it('should return true', () => { const a = Money.one_hundred(); const b = Money.ten(); expect(a.isGte(b)).toBeTruthy(); expect(Money.one_hundred().isGte(Money.ten())).toBeTruthy(); expect(Money.ten().isGte(10)).toBeTruthy(); }); it('should return false', () => { const a = Money.one_hundred(); const b = Money.ten(); expect(b.isGte(a)).toBeFalsy(); expect(b.isGte(11)).toBeFalsy(); }); }); describe('isLt', () => { it('should return true', () => { const a = Money.ten(); const b = Money.one_hundred(); expect(a.isLt(b)).toBeTruthy(); expect(a.isLt(11)).toBeTruthy(); }); it('should return false', () => { const a = Money.ten(); const b = Money.one_hundred(); expect(b.isLt(a)).toBeFalsy(); expect(a.isLt(5)).toBeFalsy(); }); }); describe('isLte', () => { it('should return true', () => { const a = Money.ten(); const b = Money.ten(); expect(a.isLte(b)).toBeTruthy(); expect(a.isLte(b.sum(1))).toBeTruthy(); expect(a.isLte(10)).toBeTruthy(); expect(a.isLte(11)).toBeTruthy(); }); it('should return false', () => { const a = Money.ten(); const b = Money.one_hundred(); expect(b.isLte(a)).toBeFalsy(); expect(a.isLte(5)).toBeFalsy(); }); }); describe('isEq', () => { it('should return true', () => { const a = Money.ten(); const b = Money.ten(); expect(a.isEq(b)).toBeTruthy(); expect(a.isEq(10)).toBeTruthy(); }); it('should return false', () => { const a = Money.ten(); const b = Money.one_hundred(); expect(b.isEq(a)).toBeFalsy(); expect(a.isEq(5)).toBeFalsy(); }); }); describe('isPos', () => { it('should return true', () => { const money = Money.ten(); expect(money.isPos()).toBeTruthy(); }); it('should return false', () => { const money = Money.ten().makeNeg(); expect(money.isPos()).toBeFalsy(); }); }); describe('isNeg', () => { it('should return true', () => { const money = Money.ten().makeNeg(); expect(money.isNeg()).toBeTruthy(); }); it('should return false', () => { const money = Money.ten(); expect(money.isNeg()).toBeFalsy(); }); }); describe('isZero', () => { it('should return true', () => { const money = Money.zero(); expect(money.isZero()).toBeTruthy(); }); it('should return false', () => { const money = Money.ten(); expect(money.isZero()).toBeFalsy(); }); }); describe('makePos', () => { it('should return true', () => { const money = Money.init(-10).makePos(); expect(money.isPos()).toBeTruthy(); }); it('should return false', () => { const money = Money.init(10).makeNeg(); expect(money.isPos()).toBeFalsy(); }); it('should continue positive', () => { const money = Money.init(10).makePos(); expect(money.isPos()).toBeTruthy(); }); }); describe('makeNeg', () => { it('should return true', () => { const money = Money.init(10).makeNeg(); expect(money.isNeg()).toBeTruthy(); }); it('should return false', () => { const money = Money.init(-10).makePos(); expect(money.isNeg()).toBeFalsy(); }); it('should return true', () => { const money = Money.init(-10).makeNeg(); expect(money.isNeg()).toBeTruthy(); }); }); describe('sum', () => { it('should return 200', () => { const amount = Money.one_hundred(); const total = amount.sum(amount); expect(total.value()).toBe(200); expect(amount.sum(100).value()).toBe(200); expect(Money.sum(amount, amount)).toBe(200); expect(Money.sum(amount, amount)).toBe(200); }); it('should return 0', () => { const amount = Money.one_hundred(); const total = amount.sum(amount.makeNeg()); expect(total.value()).toBe(0); expect(amount.sum(-100).value()).toBe(0); expect(Money.sum(-100, 100)).toBe(0); }); }); describe('divide', () => { it('should return 50', () => { const amount = Money.one_hundred(); const total = amount.divide(2); expect(total.value()).toBe(50); expect(amount.divide(2).value()).toBe(50); expect(Money.divide(amount, 2)).toBe(50); }); it('should return 1', () => { const amount = Money.one_hundred(); const total = amount.divide(amount); expect(total.value()).toBe(1); expect(amount.divide(100).value()).toBe(1); expect(Money.divide(amount, amount)).toBe(1) }); }); describe('multiply', () => { it('should return 81', () => { const amount = Money.ten().subtract(1); expect(amount.multiply(9).value()).toBe(81); expect(amount.multiply(amount).value()).toBe(81); expect(Money.multiply(9, 9)).toBe(81); }); it('should return 81', () => { const amount = Money.ten().subtract(1).makeNeg(); expect(amount.multiply(9).value()).toBe(-81); expect(amount.multiply(amount).value()).toBe(81); expect(Money.multiply(amount, amount)).toBe(81); }); }); describe('subtract', () => { it('should return 50', () => { const amount = Money.one_hundred(); const total = amount.subtract(amount.divide(2)); expect(total.value()).toBe(50); expect(amount.subtract(50).value()).toBe(50); expect(Money.subtract(amount, 50)).toBe(50); }); }); describe('floor', () => { it('should return 101', () => { const amount = Money.one_hundred().sum(1.5); expect(amount.floor().value()).toBe(101); }); }); describe('ceil', () => { it('should return 102', () => { const amount = Money.one_hundred().sum(1.5); expect(amount.ceil().value()).toBe(102); }); }); describe('closure', () => { it('should calc', () => { const closure = Money.closure(100); expect(closure.sum(20)).toBe(120); expect(closure.subtract(20)).toBe(80); expect(closure.divide(2)).toBe(50); expect(closure.multiply(2)).toBe(200); }); }); describe('calcInterest', () => { it('should calculate interest', () => { const total = Money.one_hundred(); const amount = total.interest(1.5, 36); expect(amount.value()).toBe(54); }); it('should throw', () => { const total = Money.one_hundred(); const calc = (rate: number, period: number) => total.interest(rate, period); expect(() => calc(-1, 36)).toThrowError(); expect(() => calc(1, -36)).toThrowError(); }); }); describe('calcCompoundInterest', () => { it('should calculate interest', () => { const total = Money.one_hundred(); const amount = total.compoundInterest(1.5, 36); expect(amount.value()).toBe(70.913); }); it('should throw', () => { const total = Money.one_hundred(); const calc = (rate: number, period: number) => total.compoundInterest(rate, period); expect(() => calc(-1, 36)).toThrowError(); expect(() => calc(1, -36)).toThrowError(); }); }); describe('max', () => { it('should return 1000', () => { const arr = [Money.ten(), Money.one_hundred(), Money.zero(), Money.one_thousand()]; expect(Money.max(arr).value()).toBe(1000); }); }); describe('min', () => { it('should return 0', () => { const arr = [Money.ten(), Money.one_hundred(), Money.zero(), Money.one_thousand()]; expect(Money.min(arr).value()).toBe(0); }); }); describe('random', () => { it('should return value between 100 and 1000', () => { const random = Money.random(100, 1000); expect(random.isGte(100) && random.isLte(1000)).toBeTruthy(); }); it('should return 0', () => { const random = Money.random(10, 10); expect(random.value()).toBe(0); }); }); describe('average', () => { it('should', () => { const cl = Money.closure(100); const a = Money.init(cl.sum(20)); const b = Money.init(cl.sum(10)); const c = Money.init(cl.subtract(5)); expect(Money.average([a, b, c]).value()).toBe(108.333); }); }); describe('convertTo', () => { it('should convert to real', () => { const dollar = Money.one_hundred(); const reals = dollar.convertTo(5); expect(reals.value()).toBe(500); }); it('should convert to real', () => { const dollar = Money.one_hundred(); const reals = dollar.convertTo(Money.one().multiply(5)); expect(reals.value()).toBe(500); }); it('should convert to dollars', () => { const reals = Money.one_hundred(); const dollars = reals.convertTo(0.2); expect(dollars.value()).toBe(20); }); it('should convert to dollars', () => { const reals = Money.one_hundred(); const dollars = reals.convertTo(Money.init(0.2)); expect(dollars.value()).toBe(20); }); }); }); ================================================ FILE: packages/money/index.ts ================================================ import { Result, ValueObject } from 'rich-domain'; export enum Currencies { USD = 'USD', // Dólar Americano EUR = 'EUR', // Euro JPY = 'JPY', // Iene Japonês GBP = 'GBP', // Libra Esterlina AUD = 'AUD', // Dólar Australiano CAD = 'CAD', // Dólar Canadense CHF = 'CHF', // Franco Suíço CNY = 'CNY', // Renminbi Chinês SEK = 'SEK', // Coroa Sueca NZD = 'NZD', // Dólar da Nova Zelândia KRW = 'KRW', // Won Sul-Coreano SGD = 'SGD', // Dólar de Singapura NOK = 'NOK', // Coroa Norueguesa MXN = 'MXN', // Peso Mexicano INR = 'INR', // Rúpia Indiana BRL = 'BRL', // Real Brasileiro RUB = 'RUB', // Rublo Russo ZAR = 'ZAR', // Rand Sul-Africano TRY = 'TRY', // Lira Turca HKD = 'HKD', // Dólar de Hong Kong } export enum Locales { 'en-US' = 'en-US', // Inglês (Estados Unidos) 'en-GB' = 'en-GB', // Inglês (Reino Unido) 'en-CA' = 'en-CA', // Inglês (Canadá) 'en-AU' = 'en-AU', // Inglês (Austrália) 'en-NZ' = 'en-NZ', // Inglês (Nova Zelândia) 'fr-FR' = 'fr-FR', // Francês (França) 'fr-CA' = 'fr-CA', // Francês (Canadá) 'es-ES' = 'es-ES', // Espanhol (Espanha) 'es-MX' = 'es-MX', // Espanhol (México) 'pt-BR' = 'pt-BR', // Português (Brasil) 'pt-PT' = 'pt-PT', // Português (Portugal) 'de-DE' = 'de-DE', // Alemão (Alemanha) 'it-IT' = 'it-IT', // Italiano (Itália) 'ja-JP' = 'ja-JP', // Japonês (Japão) 'ko-KR' = 'ko-KR', // Coreano (Coreia do Sul) 'zh-CN' = 'zh-CN', // Chinês (China) 'zh-TW' = 'zh-TW', // Chinês (Taiwan) 'ru-RU' = 'ru-RU', // Russo (Rússia) } type Locale = keyof typeof Locales; type Currency = keyof typeof Currencies; class Money extends ValueObject { protected static readonly MESSAGE: string = 'Invalid money value'; private constructor(prop: number) { super(prop); } value(): number { return this.props; } /** * @description Validate if provided value is a valid money value. * @param value The value to validate. * @returns A boolean indicating whether the provided value is a valid money value. */ public static isValid(value: number): boolean { return this.isValidProps(value); } /** * @description Validate if provided value is a valid money value. * @param value The value to validate. * @returns A boolean indicating whether the provided value is a valid money value. */ public static isValidProps(value: number): boolean { return this.validator.number(value).isSafeInteger(); } /** * @description Formats the money value as currency according to the specified currency and locale. * @param currency The currency code or enum representing the currency. Defaults to Brazilian Real (BRL). * @param locale The locale code or enum representing the locale. Defaults to Brazilian Portuguese (pt-BR). * @returns A string representing the money value formatted as currency. */ coin(currency?: Currency | Currencies, locale?: Locale | Locales): string { return Intl.NumberFormat(locale ?? Locales['pt-BR'], { currency: currency ?? Currencies.BRL, style: 'currency' }).format(this.value()); } /** * @description Performs addition with another Money object or a numeric value. * @param money The Money object or numeric value to add. * @returns A new Money object representing the result of the addition. */ sum(money: Money | number): Money { const current = this.value(); const target = money instanceof Money ? money.value() : money; const total = this.util.number(current).sum(target, { fractionDigits: 3 }); return new Money(total); } /** * @description Performs subtraction with another Money object or a numeric value. * @param money The Money object or numeric value to subtract. * @returns A new Money object representing the result of the subtraction. */ subtract(money: Money | number): Money { const current = this.value(); const target = money instanceof Money ? money.value() : money; const total = this.util.number(current).subtract(target, { fractionDigits: 3 }); return new Money(total); } /** * @description Performs multiplication with another Money object or a numeric value. * @param money The Money object or numeric value to multiply by. * @returns A new Money object representing the result of the multiplication. */ multiply(money: Money | number): Money { const current = this.value(); const target = money instanceof Money ? money.value() : money; const total = this.util.number(current).multiplyBy(target, { fractionDigits: 3 }); return new Money(total); } /** * @description Performs division with another Money object or a numeric value. * @param money The Money object or numeric value to divide by. * @returns A new Money object representing the result of the division. */ divide(money: Money | number): Money { const current = this.value(); const target = money instanceof Money ? money.value() : money; const total = this.util.number(current).divideBy(target, { fractionDigits: 3 }); return new Money(total); } /** * @description Adds a percentage to the money value. * @param percent The percentage to add. * @returns A new Money object representing the result of adding the percentage. */ addPercent(percent: number): Money { const current = this.value(); const multiply = this.util.number(current).multiplyBy(percent); const percents = this.util.number(multiply).divideBy(100); const total = this.util.number(percents).sum(current, { fractionDigits: 3 }); return new Money(total); } /** * @description Calculates the percentage of the money value. * @param percent The percentage to calculate. * @returns A new Money object representing the calculated percentage. */ percent(percent: number): Money { const current = this.value(); const multiply = this.util.number(current).multiplyBy(percent); const percents = this.util.number(multiply).divideBy(100, { fractionDigits: 3 }); return new Money(percents); } /** * @description Checks if the money value is greater than the provided value. * @param value The value to compare. * @returns A boolean indicating whether the money value is greater than the provided value. */ isGt(value: Money | number): boolean { const target = value instanceof Money ? value.value() : value; const current = this.value(); return this.validator.number(current).isGreaterThan(target); } /** * @description Checks if the money value is greater than or equal to the provided value. * @param value The value to compare. * @returns A boolean indicating whether the money value is greater than or equal to the provided value. */ isGte(value: Money | number): boolean { const target = value instanceof Money ? value.value() : value; const current = this.value(); return this.validator.number(current).isGreaterOrEqualTo(target); } /** * @description Checks if the money value is less than the provided value. * @param value The value to compare. * @returns A boolean indicating whether the money value is less than the provided value. */ isLt(value: Money | number): boolean { const target = value instanceof Money ? value.value() : value; const current = this.value(); return this.validator.number(current).isLessThan(target); } /** * @description Checks if the money value is less than or equal to the provided value. * @param value The value to compare. * @returns A boolean indicating whether the money value is less than or equal to the provided value. */ isLte(value: Money | number): boolean { const target = value instanceof Money ? value.value() : value; const current = this.value(); return this.validator.number(current).isLessOrEqualTo(target); } /** * @description Checks if the money value is equal to the provided value. * @param value The value to compare. * @returns A boolean indicating whether the money value is equal to the provided value. */ isEq(value: Money | number): boolean { const target = value instanceof Money ? value.value() : value; const current = this.value(); return this.validator.number(current).isEqualTo(target); } /** * @description Checks if the money value is positive. * @returns A boolean indicating whether the money value is positive. */ isPos(): boolean { return this.validator.number(this.props).isPositive(); } /** * @description Checks if the money value is negative. * @returns A boolean indicating whether the money value is negative. */ isNeg(): boolean { return this.validator.number(this.props).isNegative(); } /** * @description Checks if the money value is zero. * @returns A boolean indicating whether the money value is zero. */ isZero(): boolean { return this.validator.number(this.props).isEqualTo(0); } /** * @description Makes the money value positive. * @returns A new Money object with a positive value. */ makePos(): Money { const value = this.value(); if (this.validator.number(value).isNegative()) { return new Money(value * -1); } return new Money(value); } /** * @description Makes the money value negative. * @returns A new Money object with a negative value. */ makeNeg(): Money { const value = this.value(); if (this.validator.number(value).isPositive()) { return new Money(value * -1); } return new Money(value); } /** * @description Creates a new Money object with a value of zero. * @returns A new Money object initialized with a value of zero. */ public static zero(): Money { return new Money(0); } /** * @description Creates a new Money object with a value of one. * @returns A new Money object initialized with a value of one. */ public static one(): Money { return new Money(1); } /** * @description Creates a new Money object with a value of ten. * @returns A new Money object initialized with a value of ten. */ public static ten(): Money { return new Money(10); } /** * @description Creates a new Money object with a value of one hundred. * @returns A new Money object initialized with a value of one hundred. */ public static one_hundred(): Money { return new Money(100); } /** * @description Creates a new Money object with a value of one thousand. * @returns A new Money object initialized with a value of one thousand. */ public static one_thousand(): Money { return new Money(1000); } /** * @description Performs summation of two values. * @param a The first value to sum, which can be a number or a Money object. * @param b The second value to sum, which can be a number or a Money object. * @returns The result of the summation as a number. */ public static sum(a: number | Money, b: number | Money): number { const valueA = a instanceof Money ? a.value() : a; const valueB = b instanceof Money ? b.value() : b; return this.util.number(valueA).sum(valueB, { fractionDigits: 3 }); } /** * @description Performs division of two values. * @param a The numerator value, which can be a number or a Money object. * @param b The denominator value, which can be a number or a Money object. * @returns The result of the division as a number. */ public static divide(a: number | Money, b: number | Money): number { const valueA = a instanceof Money ? a.value() : a; const valueB = b instanceof Money ? b.value() : b; return this.util.number(valueA).divideBy(valueB, { fractionDigits: 3 }); } /** * @description Performs multiplication of two values. * @param a The first value to multiply, which can be a number or a Money object. * @param b The second value to multiply, which can be a number or a Money object. * @returns The result of the multiplication as a number. */ public static multiply(a: number | Money, b: number | Money): number { const valueA = a instanceof Money ? a.value() : a; const valueB = b instanceof Money ? b.value() : b; return this.util.number(valueA).multiplyBy(valueB, { fractionDigits: 3 }); } /** * @description Performs subtraction of two values. * @param a The minuend value, which can be a number or a Money object. * @param b The subtrahend value, which can be a number or a Money object. * @returns The result of the subtraction as a number. */ public static subtract(a: number | Money, b: number | Money): number { const valueA = a instanceof Money ? a.value() : a; const valueB = b instanceof Money ? b.value() : b; return this.util.number(valueA).subtract(valueB, { fractionDigits: 3 }); } /** * @description Rounds down the money value to the nearest integer. * @returns A new Money object representing the rounded-down value. */ floor(): Money { const ceil = Math.floor(this.props); return new Money(ceil); }; /** * @description Rounds up the money value to the nearest integer. * @returns A new Money object representing the rounded-up value. */ ceil(): Money { const ceil = Math.ceil(this.props); return new Money(ceil); } /** * @description Generates a closure that performs arithmetic operations with a predefined initial value. * @param initial The initial value for arithmetic operations. * @returns An object containing functions for performing `summation`, `division`, `multiplication`, and `subtraction` with the initial value. */ public static closure(initial: number) { return { sum: (value: number): number => Money.sum(initial, value), divide: (value: number): number => Money.divide(initial, value), multiply: (value: number): number => Money.multiply(initial, value), subtract: (value: number): number => Money.subtract(initial, value) }; } /** * @description Calculates the simple interest based on the provided rate and periods. * @param rate The interest rate as a percentage. * @param periods The number of periods over which the interest is applied. * @returns A new Money object representing the calculated interest. * @throws {Error} If either the rate or periods is negative. */ interest(rate: number, periods: number): Money { if ( this.validator.number(rate).isNegative() || this.validator.number(periods).isNegative() ) { throw new Error('period and rate must be greater than zero'); } const rateBy100 = this.util.number(rate).divideBy(100); const amount = this.util.number(this.props).multiplyBy(rateBy100); const interest = this.util.number(amount).multiplyBy(periods); return new Money(interest); } /** * @description Finds the maximum value among the provided Money objects. * @param values An array of Money objects. * @returns A new Money object representing the maximum value found. */ public static max(values: Money[]): Money { const arr = values.map((item): number => item.value()); const max = Math.max(...arr); return new Money(max); } /** * @description Finds the minimum value among the provided Money objects. * @param values An array of Money objects. * @returns A new Money object representing the minimum value found. */ public static min(values: Money[]): Money { const arr = values.map((item): number => item.value()); const max = Math.min(...arr); return new Money(max); } /** * @description Calculates the compound interest based on the provided rate and periods. * @param rate The interest rate as a percentage. * @param periods The number of periods over which the interest is applied. * @returns A new Money object representing the calculated compound interest. * @throws {Error} If either the rate or periods is negative. */ compoundInterest(rate: number, periods: number): Money { if ( this.validator.number(rate).isNegative() || this.validator.number(periods).isNegative() ) { throw new Error('period and rate must be greater than zero'); }; const rateBy100 = this.util.number(rate).divideBy(100); const base = this.util.number(rateBy100).sum(1); const pow = Math.pow(base, periods); const amount = this.util.number(this.props).multiplyBy(pow); const compoundInterest = this.util.number(amount).subtract(this.props, { fractionDigits: 3 }); return new Money(compoundInterest); } /** * @description Generates a random Money value within the specified range. * @param min The minimum value of the range. * @param max The maximum value of the range. * @returns A new Money object representing a random value within the specified range. */ public static random(min: number, max: number): Money { if (min >= max) return Money.zero(); const randomValue = Math.random() * (max - min) + min; const roundedValue = Math.round(randomValue * 100) / 100; return new Money(roundedValue); } /** * @description Calculates the average value among the provided Money objects. * @param values An array of Money objects. * @returns A new Money object representing the average value of the Money objects. */ public static average(values: Money[]): Money { const items = values.length; const calc = (prev: Money, current: Money): Money => current.sum(prev); const total = values.reduce(calc, Money.zero()); return total.divide(items); } /** * @description Converts the current Money value to another currency using the provided exchange rate. * @param exchangeRate The exchange rate, which can be a Money object representing the rate or a number. * @returns A new Money object representing the converted value. */ convertTo(exchangeRate: Money | number): Money { const current = this.value(); const rate = exchangeRate instanceof Money ? exchangeRate.value() : exchangeRate; const value = this.util.number(current).multiplyBy(rate, { fractionDigits: 3 }); return new Money(value); } /** * @description Initializes a Money object with the provided value. * @param value The initial value for the Money object. * @returns A new Money object initialized with the provided value. * @throws {Error} If the provided value is invalid. */ public static init(value: number): Money { const isValidValue = Money.isValidProps(value); if (!isValidValue) throw new Error(Money.MESSAGE); return new Money(value); } /** * @description Creates a Result object containing a Money object initialized with the provided value. * @param value The initial value for the Money object. * @returns A Result object containing either a Money object or an error message. */ public static create(value: number): Result { if (!Money.isValidProps(value)) { return Result.fail(Money.MESSAGE); } return Result.Ok(new Money(value)); }; } export { Money }; export default Money; ================================================ FILE: packages/money/package.json ================================================ { "name": "@type-ddd/money", "description": "This package provides TypeScript type definitions for handling Money in Domain-Driven Design contexts", "version": "0.1.0", "main": "index.js", "types": "index.d.ts", "author": "Alessandro Dev", "license": "MIT", "publishConfig": { "access": "public" }, "keywords": [ "Complexity", "Business Logic", "DDD", "Domain Driving Design", "DDD-Utils", "Base Value Object", "Domain Events", "Clean Architecture", "Money", "Validation", "Formatting", "Value Object", "Utility", "Security", "Standards" ], "scripts": { "build": "tsc" }, "peerDependencies": { "rich-domain": "^1.25.0" }, "files": [ "index.js", "index.d.ts" ], "repository": { "type": "git", "url": "git+https://github.com/4lessandrodev/type-ddd.git" }, "bugs": { "url": "https://github.com/4lessandrodev/type-ddd/issues" }, "homepage": "https://github.com/4lessandrodev/type-ddd/tree/main/packages/money", "gitHead": "4cb9159bde8d6fc951e9d902feed2ad25da49fa4" } ================================================ FILE: packages/money/tsconfig.build.json ================================================ { "extends": "../tsconfig.build.json", "compilerOptions": { "outDir": ".", "rootDir": ".", "paths": {} }, "exclude": [ "node_modules", "dist", "__tests__/**/*", "*.spec.ts" ], "references": [] } ================================================ FILE: packages/money/tsconfig.json ================================================ { "extends": "../tsconfig.build.json", "compilerOptions": { "types": [ "node" ] }, "files": [], "include": [], "references": [ { "path": "./tsconfig.build.json" } ] } ================================================ FILE: packages/password/CHANGELOG.md ================================================ # Change Log All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. ## [0.0.5-alpha.0](https://github.com/4lessandrodev/type-ddd/compare/@type-ddd/password@0.0.2...@type-ddd/password@0.0.5-alpha.0) (2024-12-16) ## 4.0.5 (2024-11-28) ### Bug Fixes * change type create method return null [#194](https://github.com/4lessandrodev/type-ddd/issues/194) ([2cd03bf](https://github.com/4lessandrodev/type-ddd/commit/2cd03bf34387f4889a0a292ba350f2c0cfc753b7)) * solve error module not found [#449](https://github.com/4lessandrodev/type-ddd/issues/449) ([e9d14f6](https://github.com/4lessandrodev/type-ddd/commit/e9d14f694cafc9c2123cc31055a4561f460a82d3)) * solve error module not found [#449](https://github.com/4lessandrodev/type-ddd/issues/449) ([7c85419](https://github.com/4lessandrodev/type-ddd/commit/7c85419376860b9b94fbee3f16bc2b10592221f6)) ## 4.0.3 (2024-07-26) # Changelog All notable changes to this project will be documented in this file. ## Unreleased --- ## Released --- ### [0.0.4] - 2024-11-28 ### Fix - update rich-domain lib to check nullish type: now 'create' return a possibly null value in Result instance. ### [0.0.3] - 2024-09-26 ### Fix - Corrected `"files"` in `package.json` to include `utils.js` and `utils.d.ts`, resolving module not found errors during compilation. --- ### [0.0.2] - 2024-05-31 ### Docs - Doc: update documentation --- ### [0.0.1] - 2024-05-31 ### Base - Create base value object as single pack ================================================ FILE: packages/password/README.md ================================================ # `@type-ddd/password` > The @type-ddd/password module provides a class Password for handling password in TypeScript. It includes methods for validating password, encrypt and compare. ## Installation Install `rich-domain`, `@type-ddd/password` and `bcrypt` with your favorite package manager: ```sh npm i rich-domain @type-ddd/password bcrypt #OR yarn add rich-domain @type-ddd/password bcrypt ``` ## Usage ```ts import { Password } from '@type-ddd/password'; // Initialize Password instance with a valid value const password = Password.init('Y8237FNB@'); // OR // Create Password instance from provided value const result = Password.create('Y8237FNB@'); // Or create a strong password const pass = Password.random(); ``` ### Compare password You may compare password with plain text to check if is equal ```ts const password = Password.init('#$89ABC_v'); // check if password is encrypted password.isEncrypted(); // false const encrypted = password.encrypt(); // compare encrypted.compare('#$89ABC_v'); // true ``` ================================================ FILE: packages/password/__tests__/password.spec.ts ================================================ import PasswordValueObject from '../index' describe('password.value-object', () => { describe('default', () => { it('should be defined', () => { const valueObject = PasswordValueObject.create('12345'); expect(valueObject).toBeDefined(); }); it('should create a not encrypted pass', () => { const valueObject = PasswordValueObject.create('12345'); expect(valueObject.isOk()).toBe(true); }); it('should fail if try to create a password less than 5 char', () => { const valueObject = PasswordValueObject.create('1234'); expect(valueObject.isOk()).toBe(false); expect(valueObject.error()).toBe( 'Password must has min 5 and max 22 chars', ); }); it('should fail if try to create a password greater than 22 char', () => { const valueObject = PasswordValueObject.create( '123456789101112131415161718', ); expect(valueObject.isOk()).toBe(false); expect(valueObject.error()).toBe( 'Password must has min 5 and max 22 chars', ); }); it('should validate password with success', () => { const result = PasswordValueObject.isValidProps('123456'); expect(result).toBe(true); }); it('should validate password with success', () => { const result = PasswordValueObject.isValidProps('1234'); expect(result).toBe(false); }); it('should validate encrypted password with success', () => { const result = PasswordValueObject.create('123456').value(); const encrypted = result?.encrypt(); const isEncrypted = encrypted?.isEncrypted(); expect(isEncrypted).toBe(true); expect(PasswordValueObject.isValid(encrypted?.value() as string)).toBe(true); }); it('should encrypt password with success', () => { const result = PasswordValueObject.create('123456').value(); expect(result?.isEncrypted()).toBe(false); const encrypted = result?.encrypt(); const isEncrypted = encrypted?.isEncrypted(); expect(isEncrypted).toBe(true); }); it('should get value result with success', () => { const result = PasswordValueObject.create('123456').value(); expect(result?.value()).toBe('123456'); }); it('should generate a valid random password not encrypted', () => { const result = PasswordValueObject.random(8); const isEncrypted = result.isEncrypted(); expect(isEncrypted).toBe(false); }); it('should not generate equal password', () => { const passA = PasswordValueObject.random(12); const passB = PasswordValueObject.random(12); const isEqual = passA.isEqual(passB); expect(isEqual).toBe(false); }); it('should password to be equal', () => { const passA = PasswordValueObject.create('123456abc!').value(); const passC = PasswordValueObject.create('123456abc!').value(); const passB = passA?.clone(); const isEqual1 = passA?.isEqual(passB as PasswordValueObject); const isEqual2 = passA?.isEqual(passC as PasswordValueObject); expect(isEqual1).toBe(true); expect(isEqual2).toBe(true); }); it('should generate a valid random password not encrypted default 12 chars', () => { const result = PasswordValueObject.random(); const isEncrypted = result.isEncrypted(); expect(isEncrypted).toBe(false); expect(result.value()).toHaveLength(12); }); it('should compare password not encrypted', () => { const result = PasswordValueObject.create('123456').value(); const match = result?.compare('123456'); expect(match).toBeTruthy(); }); it('should compare encrypted password', () => { const result = PasswordValueObject.create('123456').value(); const encrypted = result?.encrypt(); const match = encrypted?.compare('123456'); expect(match).toBeTruthy(); }); it('should compare password not encrypted', () => { const result = PasswordValueObject.create('123456').value(); const match = result?.compare('abcdef'); expect(match).toBeFalsy(); }); it('should compare encrypted password', () => { const result = PasswordValueObject.create('123456').value(); const encrypted = result?.encrypt(); const match = encrypted?.compare('abcdef'); expect(match).toBeFalsy(); }); it('should encrypt many times and keep value', () => { const valueObject = PasswordValueObject.create('12345').value(); expect(valueObject?.isEncrypted()).toBeFalsy(); const encrypted1 = valueObject?.encrypt(); expect(encrypted1?.isEncrypted()).toBeTruthy(); expect(valueObject?.compare('12345')).toBeTruthy(); expect(encrypted1?.compare('12345')).toBe(true); expect(encrypted1?.isEncrypted()).toBeTruthy(); }); }); describe('custom password', () => { beforeAll(() => { Reflect.set(PasswordValueObject, 'MAX_LENGTH', 22); Reflect.set(PasswordValueObject, 'MIN_LENGTH', 10); }); it('should validate custom password', () => { const pass = PasswordValueObject.create('123456'); expect(pass.isFail()).toBeTruthy(); expect(Reflect.get(PasswordValueObject, 'MIN_LENGTH')).toBe(10); }); }); it('should init an instance with success', () => { const init = () => PasswordValueObject.init('sample1234'); expect(init).not.toThrowError(); }); it('should throw an error on init an instance with invalid value', () => { const init = () => PasswordValueObject.init(''); expect(init).toThrowError(); }); it('should return the same password if already is encrypted', () => { const password = PasswordValueObject.random().encrypt(); const encrypted = password.encrypt(); expect(password.isEqual(encrypted)).toBeTruthy(); }); }); ================================================ FILE: packages/password/__tests__/util.spec.ts ================================================ import { passwordGenerator } from '../utils'; describe('password-generator.util', () => { it('should be defined', () => { const generator = passwordGenerator; expect(generator).toBeDefined(); }); it('should generate a random password with 8 chars', () => { const password = passwordGenerator(8); expect(password).toHaveLength(8); }); it('should generate a random password with 10 chars', () => { const password = passwordGenerator(10); expect(password).toHaveLength(10); }); it('should generate a random password with 12 chars', () => { const password = passwordGenerator(12); expect(password).toHaveLength(12); }); it('should generate a random password with 14 chars', () => { const password = passwordGenerator(14); expect(password).toHaveLength(14); }); it('should generate a random password with 16 chars', () => { const password = passwordGenerator(16); expect(password).toHaveLength(16); }); it('should generate a random password with 18 chars', () => { const password = passwordGenerator(18); expect(password).toHaveLength(18); }); }); ================================================ FILE: packages/password/index.ts ================================================ import { genSaltSync, hashSync, compareSync } from 'bcrypt'; import { Result, ValueObject } from 'rich-domain'; import passwordGenerator, { ILength } from './utils'; const regexHash = /^\$2b\$10\$.{53}$/; class Password extends ValueObject { protected static readonly MAX_LENGTH = 22; protected static readonly MIN_LENGTH = 5; protected static readonly REGEX = regexHash; protected static readonly MESSAGE: string = `Password must has min ${Password.MIN_LENGTH} and max ${Password.MAX_LENGTH} chars`; private constructor(props: string) { super(props); } /** * @returns value as string */ value(): string { return this.props; } /** * * @description compare plainText with encrypted password * @param plainText plainText not encrypted to compare with encrypted password * @returns true if match else false */ public compare(plainText: string): boolean { if (this.isEncrypted()) { return compareSync(plainText, this.props); } return plainText === this.props; } /** * * @returns true if instance value is encrypted else false */ public isEncrypted(): boolean { const isEncrypted = this.validator .string(this.props) .match(Password.REGEX); return isEncrypted; } /** * * @returns true if provided value is encrypted else false */ public static isEncrypted(value: string): boolean { return this.validator.string(value).match(Password.REGEX); } /** * * @param length password length as number 8/10/12/14/16/18 * @returns PasswordValueObject * @default 12 chars or greater is recommended for strongest password */ public static random(length?: ILength): Password { const pass = passwordGenerator(length ?? 12); return Password.create(pass).value(); } /** * @summary Encrypts the password. * @description This method encrypts the password using bcrypt hashing algorithm. If the password is already encrypted, it returns the encrypted password as it is. Otherwise, it generates a salt, hashes the password with the salt, and returns the encrypted password. * @returns A Password object representing the encrypted password. */ public encrypt(): Password { const isEncrypted = this.isEncrypted(); if (isEncrypted) { const encrypted = this.props; return new Password(encrypted); } const salt = genSaltSync(); const encrypted = hashSync(this.props, salt); return new Password(encrypted); } /** * * @param value check if password has a valid value length * @returns true if is all ok or false else not */ public static isValid(value: string): boolean { return this.isValidProps(value); } /** * * @param value check if password has a valid value length * @returns true if is all ok or false else not */ public static isValidProps(value: string): boolean { const { string } = this.validator; if (!Password.isEncrypted(value)) { const passwordHasRequiredLength = string( value, ).hasLengthBetweenOrEqual( Password.MIN_LENGTH, Password.MAX_LENGTH, ); return passwordHasRequiredLength; } return true; } /** * * @param value value as string * @returns instance of Password or throw an error */ public static init(value: string): Password { const isValidValue = Password.isValidProps(value); if (!isValidValue) throw new Error(Password.MESSAGE); return new Password(value); } /** * * @param value password to create * @returns Result of PasswordValueObject */ static create(value: string): Result { if (!Password.isValidProps(value)) { return Result.fail(Password.MESSAGE); } return Result.Ok(new Password(value)); } } export { Password }; export default Password; ================================================ FILE: packages/password/package.json ================================================ { "name": "@type-ddd/password", "description": "Library that provides TypeScript type definitions for handling Password in Domain-Driven Design contexts. It facilitates the validation and manipulation of passwords.", "version": "0.1.0", "main": "index.js", "types": "index.d.ts", "author": "Alessandro Dev", "license": "MIT", "publishConfig": { "access": "public" }, "keywords": [ "Complexity", "Business Logic", "DDD", "Domain Driving Design", "DDD-Utils", "Base Value Object", "Domain Events", "Clean Architecture", "Password", "Validation", "Formatting", "Value Object", "Utility", "Security", "Standards" ], "scripts": { "build": "tsc" }, "devDependencies": { "@types/bcrypt": "^5.0.0", "bcrypt": "^5.0.1" }, "peerDependencies": { "bcrypt": "^5.0.1", "rich-domain": "^1.25.0" }, "files": [ "index.js", "index.d.ts", "utils.d.ts", "utils.js" ], "repository": { "type": "git", "url": "git+https://github.com/4lessandrodev/type-ddd.git" }, "bugs": { "url": "https://github.com/4lessandrodev/type-ddd/issues" }, "homepage": "https://github.com/4lessandrodev/type-ddd/tree/main/packages/password", "gitHead": "4cb9159bde8d6fc951e9d902feed2ad25da49fa4" } ================================================ FILE: packages/password/tsconfig.build.json ================================================ { "extends": "../tsconfig.build.json", "compilerOptions": { "outDir": ".", "rootDir": ".", "paths": {} }, "exclude": [ "node_modules", "dist", "__tests__/**/*", "*.spec.ts" ], "references": [] } ================================================ FILE: packages/password/tsconfig.json ================================================ { "extends": "../tsconfig.build.json", "compilerOptions": { "types": [ "node" ] }, "files": [], "include": [], "references": [ { "path": "./tsconfig.build.json" } ] } ================================================ FILE: packages/password/utils.ts ================================================ const upper = [ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'X', 'Y', 'Z', 'W', ]; const lower = [ 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'x', 'y', 'z', 'w', ]; const numbers = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '0']; const special = [ '!', '-', '@', '$', '#', '%', '&', '*', '_', '+', '?' ]; const availableChars = [upper, lower, numbers, special]; export type ILength = 8 | 10 | 12 | 14 | 16 | 18; const getChar = (chars: string[]) =>{ return chars[Math.floor(Math.random() * chars.length)]; } /** * * @param length password length as number 8/10/12/14/16/18 * @returns string password as plainText * @default 14 chars is recommended for strongest password */ const passwordGenerator = (length: ILength): string => { let strongPassword = ''; while (strongPassword.length < length) { const random = Math.floor(Math.random() * availableChars.length); strongPassword += getChar(availableChars[random]); } return strongPassword; }; export { passwordGenerator }; export default passwordGenerator; ================================================ FILE: packages/patterns/CHANGELOG.md ================================================ # Change Log All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. ## [0.0.4-alpha.0](https://github.com/4lessandrodev/type-ddd/compare/@type-ddd/patterns@0.0.2...@type-ddd/patterns@0.0.4-alpha.0) (2024-12-16) ## 4.0.5 (2024-11-28) ### Bug Fixes * change type create method return null [#194](https://github.com/4lessandrodev/type-ddd/issues/194) ([2cd03bf](https://github.com/4lessandrodev/type-ddd/commit/2cd03bf34387f4889a0a292ba350f2c0cfc753b7)) ## 4.0.3 (2024-07-26) # Changelog All notable changes to this project will be documented in this file. ## Unreleased --- ## Released --- ### [0.0.3] - 2024-11-28 ### Fix - update rich-domain lib to check nullish type: now 'create' return a possibly null value in Result instance. ### [0.0.2] - 2024-05-31 ### Docs - Doc: update documentation --- ### [0.0.1] - 2024-05-31 ### Base - Create base value object as single pack ================================================ FILE: packages/patterns/README.md ================================================ # `types-ddd` > TODO: description ## Usage ``` const typesDdd = require('types-ddd'); // TODO: DEMONSTRATE API ``` ================================================ FILE: packages/patterns/__tests__/specification.value-object.spec.ts ================================================ import { Specification } from '../index'; type User = { name: string; age: number; permission: 'ADMIN' | 'CLIENT' | 'OWNER'; }; class AdultSpecification extends Specification { isSatisfiedBy(user: User): boolean { return user.age > 18; } } class AdminSpecification extends Specification { isSatisfiedBy(user: User): boolean { return user.permission === 'ADMIN'; } } class CanAccessSpecification extends Specification { isSatisfiedBy(user: User): boolean { return user.permission === 'OWNER'; } } describe('specification', () => { it('should be defined', () => { const specification = Specification; expect(specification).toBeDefined(); }); it('should be adult', () => { const IsAdult = new AdultSpecification().isSatisfiedBy({ age: 20, name: 'john stuart', permission: 'ADMIN', }); expect(IsAdult).toBeTruthy(); }); it('should be admin', () => { const IsAdult = new AdminSpecification().isSatisfiedBy({ age: 20, name: 'john stuart', permission: 'ADMIN', }); expect(IsAdult).toBeTruthy(); }); it('should be can access OWNER', () => { const IsAdult = new CanAccessSpecification().isSatisfiedBy({ age: 20, name: 'john stuart', permission: 'OWNER', }); expect(IsAdult).toBeTruthy(); }); it('should be defined 15 yrs old & OWNER', () => { const IsAdult = new CanAccessSpecification().isSatisfiedBy({ age: 15, name: 'john stuart', permission: 'OWNER', }); expect(IsAdult).toBeTruthy(); }); it('should be can access +18 & !ADMIN', () => { const IsAdult = new CanAccessSpecification().isSatisfiedBy({ age: 20, name: 'john stuart', permission: 'CLIENT', }); expect(IsAdult).toBeFalsy(); }); it('should not to have access -18 & !ADMIN', () => { const IsAdult = new CanAccessSpecification().isSatisfiedBy({ age: 12, name: 'john stuart', permission: 'CLIENT', }); expect(IsAdult).toBeFalsy(); }); it('should be NOT to be not be adult and not have access', () => { const IsAdult = new CanAccessSpecification() .or(new AdultSpecification()) .isSatisfiedBy({ age: 12, name: 'john stuart', permission: 'CLIENT', }); expect(IsAdult).toBeFalsy(); }); it('should be adult cause OR', () => { const IsAdult = new CanAccessSpecification() .or(new AdultSpecification()) .isSatisfiedBy({ age: 21, name: 'john stuart', permission: 'CLIENT', }); expect(IsAdult).toBeTruthy(); }); it('should be have access ADMIN cause OR', () => { const IsAdult = new CanAccessSpecification() .or(new AdminSpecification()) .isSatisfiedBy({ age: 12, name: 'john stuart', permission: 'ADMIN', }); expect(IsAdult).toBeTruthy(); }); it('should have access cause AND adult or ADMIN', () => { const IsAdult = new CanAccessSpecification() .or(new AdminSpecification()) .and(new AdultSpecification()) .isSatisfiedBy({ age: 25, name: 'john stuart', permission: 'ADMIN', }); expect(IsAdult).toBeTruthy(); }); it('should NOT have access cause AND is not adult', () => { const IsAdult = new CanAccessSpecification() .or(new AdminSpecification()) .and(new AdultSpecification()) .isSatisfiedBy({ age: 11, name: 'john stuart', permission: 'ADMIN', }); expect(IsAdult).toBeFalsy(); }); it('should have access cause not Adult', () => { const IsAdult = new CanAccessSpecification() .andNot(new AdultSpecification()) .or(new AdminSpecification()) .isSatisfiedBy({ age: 11, name: 'john stuart', permission: 'CLIENT', }); expect(IsAdult).toBeTruthy(); }); it('should invert condition', () => { const IsAdult = new CanAccessSpecification() .or(new AdminSpecification()) .not() .isSatisfiedBy({ age: 11, name: 'john stuart', permission: 'CLIENT', }); expect(IsAdult).toBeTruthy(); }); it('should have access cause orNot adult', () => { const IsAdult = new CanAccessSpecification() .orNot(new AdultSpecification()) .isSatisfiedBy({ age: 11, name: 'john stuart', permission: 'CLIENT', }); expect(IsAdult).toBeTruthy(); }); }); ================================================ FILE: packages/patterns/index.ts ================================================ export interface ISpecification { isSatisfiedBy: (target: T) => boolean; and: (other: ISpecification) => ISpecification; or: (other: ISpecification) => ISpecification; andNot: (other: ISpecification) => ISpecification; orNot: (other: ISpecification) => ISpecification; not: () => ISpecification; } export abstract class Specification implements ISpecification { abstract isSatisfiedBy(target: T): boolean; and(other: ISpecification): ISpecification { return new AndSpecification(this, other); } or(other: ISpecification): ISpecification { return new OrSpecification(this, other); } orNot(other: ISpecification): ISpecification { return new OrNotSpecification(this, other); } andNot(other: ISpecification): ISpecification { return new AndNotSpecification(this, other); } not(): ISpecification { return new NotSpecification(this); } } export class AndSpecification extends Specification { constructor( private readonly one: ISpecification, private readonly other: ISpecification, ) { super(); } isSatisfiedBy(target: T): boolean { return ( this.one.isSatisfiedBy(target) && this.other.isSatisfiedBy(target) ); } } export class OrSpecification extends Specification { constructor( private readonly one: ISpecification, private readonly other: ISpecification, ) { super(); } isSatisfiedBy(target: T): boolean { return ( this.one.isSatisfiedBy(target) || this.other.isSatisfiedBy(target) ); } } export class OrNotSpecification extends Specification { constructor( private readonly one: ISpecification, private readonly other: ISpecification, ) { super(); } isSatisfiedBy(target: T): boolean { return ( (this.one.isSatisfiedBy(target) || this.other.isSatisfiedBy(target)) !== true ); } } export class AndNotSpecification extends Specification { constructor( private readonly one: ISpecification, private readonly other: ISpecification, ) { super(); } isSatisfiedBy(target: T): boolean { return ( (this.one.isSatisfiedBy(target) && this.other.isSatisfiedBy(target)) !== true ); } } export class NotSpecification extends Specification { constructor(private readonly other: ISpecification) { super(); } isSatisfiedBy(target: T): boolean { return !this.other.isSatisfiedBy(target); } } export default Specification; ================================================ FILE: packages/patterns/package.json ================================================ { "name": "@type-ddd/patterns", "description": "This package provide utils file and interfaces to assistant build a complex application with domain driving design", "version": "0.1.0", "main": "index.js", "types": "index.d.ts", "author": "Alessandro Dev", "license": "MIT", "publishConfig": { "access": "public" }, "keywords": [ "Complexity", "Business Logic", "DDD", "Domain Driving Design", "DDD-Utils", "Base Value Object", "Domain Events", "Clean Architecture", "CPF", "Validation", "Formatting", "Value Object", "Utility", "Security", "Standards", "Brazil" ], "scripts": { "prebuild": "rimraf ./dist", "build": "tsc" }, "peerDependencies": { "rich-domain": "^1.25.0" }, "files": [ "index.js", "index.d.ts" ], "repository": { "type": "git", "url": "git+https://github.com/4lessandrodev/type-ddd.git" }, "bugs": { "url": "https://github.com/4lessandrodev/type-ddd/issues" }, "homepage": "https://github.com/4lessandrodev/type-ddd/tree/main/packages/patterns", "gitHead": "4cb9159bde8d6fc951e9d902feed2ad25da49fa4" } ================================================ FILE: packages/patterns/tsconfig.build.json ================================================ { "extends": "../tsconfig.build.json", "compilerOptions": { "outDir": ".", "rootDir": ".", "paths": {} }, "exclude": [ "node_modules", "dist", "__tests__/**/*", "*.spec.ts" ], "references": [] } ================================================ FILE: packages/patterns/tsconfig.json ================================================ { "extends": "../tsconfig.build.json", "compilerOptions": { "types": [ "node" ] }, "files": [], "include": [], "references": [ { "path": "./tsconfig.build.json" } ] } ================================================ FILE: packages/phone/CHANGELOG.md ================================================ # Change Log All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. ## [0.0.4-alpha.0](https://github.com/4lessandrodev/type-ddd/compare/@type-ddd/phone@0.0.2...@type-ddd/phone@0.0.4-alpha.0) (2024-12-16) ## 4.0.5 (2024-11-28) ### Bug Fixes * change type create method return null [#194](https://github.com/4lessandrodev/type-ddd/issues/194) ([2cd03bf](https://github.com/4lessandrodev/type-ddd/commit/2cd03bf34387f4889a0a292ba350f2c0cfc753b7)) ## 4.0.3 (2024-07-26) # Changelog All notable changes to this project will be documented in this file. ## Unreleased --- ## Released --- ### [0.0.3] - 2024-11-28 ### Fix - update rich-domain lib to check nullish type: now 'create' return a possibly null value in Result instance. ### [0.0.2] - 2024-05-31 ### Docs - Doc: update documentation --- ### [0.0.1] - 2024-05-31 ### Base - Create base value object as single pack ================================================ FILE: packages/phone/README.md ================================================ # `@type-ddd/phone` > The @type-ddd/phone library provides TypeScript type definitions for handling phone (Brazilian) in Domain-Driven Design contexts. It facilitates the validation and manipulation of phone numbers, ensuring they adhere to the Brazilian legal standards. --- ## Installation Install `rich-domain` and `@type-ddd/phone` with your favorite package manager ```sh npm i rich-domain @type-ddd/phone # OR yarn add rich-domain @type-ddd/phone ``` ## Usage Don't worry about removing special characters; they are automatically stripped from all instances. ```ts import { Phone } from '@type-ddd/phone' // Instance of phone or throws an error if provide an invalid value const phone = Phone.init('11994882021'); // OR // Result of phone (Check Result pattern docs) const result = Phone.create('11994882021'); result.isOk(); // true // phone instance or null if provide an invalid value const phone = result.value(); ``` ## Check phone type Method to verify instance type. ```ts // value as string const isMobile = Phone.isMobile('11994882021'); // Output: true // OR // value as string const isMobile = Phone.isHome('11994882021'); // Output: false ``` ## Special Chars Don't worry about removing special characters; they are automatically stripped from all instances. ```ts const result = Phone.isValid('(11) 99488-2021'); // Output: true ``` ## Special chars If you need the value with the mask, you can use the `toPattern` method: ```ts phone.toPattern(); // Output: '(11) 99488-2021' ``` Or if you need to apply mask from a string value you may use `addMask` method ```ts Phone.addMask('11994882021'); // Output: (11) 99488-2021 ``` Or if you need to remove mask from a string value you may use `removeSpecialChars` method ```ts Phone.removeSpecialChars('(11) 99488-2021'); // Output: 11994882021 ``` If you need to identify uf from a phone number ```ts const phone = Phone.init('(11) 99488-2021'); phone.uf(); // Output: "São Paulo" phone.code(); // 11 phone.number(); // 994882021 phone.toCall(); // 011994882021 ``` ================================================ FILE: packages/phone/__tests__/home-phone.value-object.spec.ts ================================================ import { HomePhone as HomePhoneValueObject } from '../home.value-object'; describe('home-phone.value-object', () => { it('should be defined', () => { const valueObject = HomePhoneValueObject.create; expect(valueObject).toBeDefined(); }); it('should create homePhone with success', () => { const valueObject = HomePhoneValueObject.create('(11) 3404-1111'); expect(valueObject.isOk()).toBe(true); }); it('should return true', () => { const isValid = HomePhoneValueObject.isValid('(11) 3404-1111'); expect(isValid).toBe(true); }); it('should return true', () => { const isValid = HomePhoneValueObject.isValid('1134041111'); expect(isValid).toBe(true); }); it('should return false', () => { const isValid = HomePhoneValueObject.isValid('(52) 3404-1111'); expect(isValid).toBe(false); }); it('should return only numbers', () => { const value = HomePhoneValueObject.removeSpecialChars('(11) 3404-1111'); expect(value).toBe('1134041111'); }); it('should return numbers with mask', () => { const value = HomePhoneValueObject.addMask('1134041111'); expect(value).toBe('(11) 3404-1111'); }); it('should fail if try to create an invalid home phone number', () => { const valueObject = HomePhoneValueObject.create('(11) 1111-1111'); expect(valueObject.isOk()).toBe(false); }); it('should fail if try to create an invalid home phone number', () => { const valueObject = HomePhoneValueObject.create('(11) 11111111'); expect(valueObject.isOk()).toBe(false); }); it('should fail if try to create an invalid home phone number', () => { const valueObject = HomePhoneValueObject.create('(01) 3404-1111'); expect(valueObject.isOk()).toBe(false); }); it('should fail if try to create an invalid home phone number', () => { const valueObject = HomePhoneValueObject.create('01 3404-1111'); expect(valueObject.isOk()).toBe(false); }); it('should fail if try to create an invalid home phone number', () => { const valueObject = HomePhoneValueObject.create('013404-1111'); expect(valueObject.isOk()).toBe(false); }); it('should get value', () => { const valueObject = HomePhoneValueObject.create('(71) 2254-1211').value(); expect(valueObject?.value()).toBe('7122541211'); expect(valueObject?.toPattern()).toBe('(71) 2254-1211'); }); it('should get only numbers value', () => { const valueObject = HomePhoneValueObject.create('(71) 2254-1211').value(); expect(valueObject?.number()).toBe('22541211'); expect(valueObject?.toCall()).toBe('07122541211'); }); it('should get only DDD number', () => { const valueObject = HomePhoneValueObject.create('(71) 2254-1211').value(); expect(valueObject?.code()).toBe(71); }); it('should init an instance with success', () => { const init = () => HomePhoneValueObject.init('(71) 2253-1213'); expect(init).not.toThrowError(); }); it('should throw an error on init an instance with invalid value', () => { const init = () => HomePhoneValueObject.init(''); expect(init).toThrowError(); }); it('should get uf', () => { const home = HomePhoneValueObject.init('(71) 2253-1213'); expect(home.uf()).toBe('Bahia'); }); }); ================================================ FILE: packages/phone/__tests__/mobile-phone.value-object.spec.ts ================================================ import { MobilePhone as MobilePhoneValueObject } from '../mobile.value-object'; describe('home-phone.value-object', () => { it('should be defined', () => { const valueObject = MobilePhoneValueObject.create; expect(valueObject).toBeDefined(); }); it('should create mobile phone number with success', () => { const valueObject = MobilePhoneValueObject.create('(11) 99604-1111'); expect(valueObject.isOk()).toBe(true); }); it('should create mobile phone number with success', () => { const valueObject = MobilePhoneValueObject.create('11996041111'); expect(valueObject.isOk()).toBe(true); }); it('should create mobile phone number with success', () => { const phone = MobilePhoneValueObject.init('11996041111'); expect(phone.uf()).toBe('São Paulo'); }); it('should create mobile phone number with success', () => { const valueObject = MobilePhoneValueObject.create('(52) 99604-1111'); expect(valueObject.isOk()).toBe(false); }); it('should remove special chars with success', () => { const value = MobilePhoneValueObject.removeSpecialChars('(11) 99604-1111'); expect(value).toBe('11996041111'); }); it('should add mask with success', () => { const value = MobilePhoneValueObject.addMask('11996041111'); expect(value).toBe('(11) 99604-1111'); }); it('should return true', () => { const isValid = MobilePhoneValueObject.isValid('(11) 99604-1111'); expect(isValid).toBe(true); }); it('should fail if try to create an invalid home phone number', () => { const valueObject = MobilePhoneValueObject.create('(11) 99999-9999'); expect(valueObject.isOk()).toBe(false); }); it('should fail if try to create an invalid home phone number', () => { const valueObject = MobilePhoneValueObject.create('(11) 11111111'); expect(valueObject.isOk()).toBe(false); }); it('should fail if try to create an invalid home phone number', () => { const valueObject = MobilePhoneValueObject.create('(01) 99061-1111'); expect(valueObject.isOk()).toBe(false); }); it('should fail if try to create an invalid home phone number', () => { const valueObject = MobilePhoneValueObject.create('01 99620-1111'); expect(valueObject.isOk()).toBe(false); }); it('should fail if try to create an invalid home phone number', () => { const valueObject = MobilePhoneValueObject.create('99201-1111'); expect(valueObject.isOk()).toBe(false); }); it('should get value', () => { const valueObject = MobilePhoneValueObject.create('(71) 98254-1211').value(); expect(valueObject?.value()).toBe('71982541211'); expect(valueObject?.toPattern()).toBe('(71) 98254-1211'); }); it('should get only numbers value', () => { const valueObject = MobilePhoneValueObject.create('(71) 96254-1211').value(); expect(valueObject?.number()).toBe('962541211'); expect(valueObject?.toCall()).toBe('071962541211') }); it('should get only DDD number', () => { const valueObject = MobilePhoneValueObject.create('(71) 97254-1211').value(); expect(valueObject?.code()).toBe(71); }); it('should init an instance with success', () => { const init = () => MobilePhoneValueObject.init('(62) 96556-1234'); expect(init).not.toThrowError(); }); it('should throw an error on init an instance with invalid value', () => { const init = () => MobilePhoneValueObject.init(''); expect(init).toThrowError(); }); }); ================================================ FILE: packages/phone/__tests__/phone.value-object.spec.ts ================================================ import { Phone } from '../phone.value-object'; describe('phone', () => { it('should create a valid phone', () => { const phone = Phone.init('(11) 99488-2885'); expect(phone.isMobile()).toBeTruthy(); expect(phone.isHome()).toBeFalsy(); expect(Phone.isMobile('(11) 99488-2885')).toBeTruthy(); expect(Phone.isHome('(11) 99488-2885')).toBeFalsy(); }); it('should throw error if provide invalid value', () => { const build = () => Phone.init('(52) 99488-2885'); expect(build).toThrowError(); }); it('should create a valid phone', () => { const phone = Phone.init('(11) 3404-2885'); expect(phone.isHome()).toBeTruthy(); expect(phone.isMobile()).toBeFalsy(); expect(Phone.isMobile('(11) 3404-2885')).toBeFalsy(); expect(Phone.isHome('(11) 3404-2885')).toBeTruthy(); }); it('should return false', () => { const isValid = Phone.isValid('(11) 3404-288500'); expect(isValid).toBeFalsy(); }); it('should create a valid phone', () => { const phone = Phone.create('(11) 3404-2885').value(); expect(phone?.isHome()).toBeTruthy(); expect(phone?.isMobile()).toBeFalsy(); }); it('should create a valid phone', () => { const phone = Phone.create('(11) 99488-2885').value(); expect(phone?.isHome()).toBeFalsy(); expect(phone?.isMobile()).toBeTruthy(); }); it('should return fail', () => { const result = Phone.create('(11) 99488-28850'); expect(result.isFail()).toBeTruthy(); }); it('should add mask', () => { const phone = Phone.addMask('11994885487'); expect(phone).toBe('(11) 99488-5487') }); it('should add mask', () => { const phone = Phone.addMask('1134048956'); expect(phone).toBe('(11) 3404-8956') }); it('should remove mask', () => { const phone = Phone.removeSpecialChars('(11) 3404-8956'); expect(phone).toBe('1134048956') }); it('should get ddd', () => { const ddd = Phone.ddd('(11) 3404-8956'); expect(ddd).toBe(11); }); }); ================================================ FILE: packages/phone/ddd.list.ts ================================================ export const AreaCodes = [ 61, 62, 64, 65, 66, 67, // CENTRO OESTE 82, 71, 73, 74, 75, 77, 85, 88, 98, 99, 83, 81, 87, 86, 89, 84, 79, // NORDESTE 68, 96, 92, 97, 91, 93, 94, 69, 95, 63, // NORTE 41, 42, 43, 44, 45, 46, 51, 53, 54, 55, 47, 48, 49, // SUL 27, 28, 31, 32, 33, 34, 35, 37, 38, 21, 22, 24, 11, 12, 13, 14, 15, 16, 17, 18, 19 // SUDESTE ]; export const UfForCode = { 61: "Distrito Federal", 62: "Goiás", 64: "Goiás", 65: "Mato Grosso", 66: "Mato Grosso", 67: "Mato Grosso do Sul", 82: "Alagoas", 71: "Bahia", 73: "Bahia", 74: "Bahia", 75: "Bahia", 77: "Bahia", 85: "Ceará", 88: "Ceará", 98: "Maranhão", 99: "Maranhão", 83: "Paraíba", 81: "Pernambuco", 87: "Pernambuco", 86: "Piauí", 89: "Piauí", 84: "Rio Grande do Norte", 79: "Rio Grande do Norte", 68: "Acre", 96: "Amapá", 92: "Amazonas", 97: "Amazonas", 91: "Pará", 93: "Pará", 94: "Pará", 69: "Rondônia", 95: "Roraima", 63: "Tocantins", 41: "Paraná", 42: "Paraná", 43: "Paraná", 44: "Paraná", 45: "Paraná", 46: "Paraná", 51: "Rio Grande do Sul", 53: "Rio Grande do Sul", 54: "Rio Grande do Sul", 55: "Rio Grande do Sul", 47: "Santa Catarina", 48: "Santa Catarina", 49: "Santa Catarina", 27: "Espírito Santo", 28: "Espírito Santo", 31: "Minas Gerais", 32: "Minas Gerais", 33: "Minas Gerais", 34: "Minas Gerais", 35: "Minas Gerais", 37: "Minas Gerais", 38: "Minas Gerais", 21: "Rio de Janeiro", 22: "Rio de Janeiro", 24: "Rio de Janeiro", 11: "São Paulo", 12: "São Paulo", 13: "São Paulo", 14: "São Paulo", 15: "São Paulo", 16: "São Paulo", 17: "São Paulo", 18: "São Paulo", 19: "São Paulo" } as const; export type ddd = keyof typeof UfForCode; ================================================ FILE: packages/phone/home.value-object.ts ================================================ import { Result, ValueObject } from 'rich-domain'; import { AreaCodes, UfForCode, ddd } from './ddd.list'; const regexHash = /^\([1-9]{2}\)\s[2-5][0-9]{3}\-[0-9]{4}$|^[1-9]{2}[2-5]{1}[0-9]{7}$/; const regexHashSpecialChars = /\(|\)|-|\s/g; /** * @description Brazilian Home Phone Number * @default (XX) XXXX-XXXX */ class HomePhone extends ValueObject { protected static readonly REGEX = regexHash; protected static readonly MESSAGE: string = 'Invalid Home Phone Number'; private constructor(prop: string) { super(prop); } toPattern(): string { return HomePhone.addMask(this.props); } isMobile(): boolean { return false; }; isHome(): boolean { return true; }; public static isValid(value: string): boolean { return this.isValidProps(value); } /** * * @param value Phone number (XX) XXXX-XXXX * @returns true if pattern match and false if not. */ public static isValidProps(value: string): boolean { const isValidDDD = AreaCodes.includes(this.code(value)); const matchPattern = this.validator.string(value).match(HomePhone.REGEX); return isValidDDD && matchPattern; } /** * @returns value XXXXXXXXXX as string */ value(): string { return this.props; } /** * * @returns only numbers without special chars. Includes 0 and DDD. * @example 01122502301 */ toCall(): string { const onlyNumbersAsString = this.props; return `0${onlyNumbersAsString}`; } number(): string { return this.props.slice(2); } /** * * @returns only area code (DDD) as number * @example 11 */ code(): ddd { return parseInt(this.props.slice(0, 2)) as ddd; } /** * * @returns only area code (DDD) as number * @example 11 */ public static code(phone: string): ddd { const value = this.util.string(phone).removeSpecialChars(); return parseInt(value.slice(0, 2)) as ddd; } uf() { const ddd = this.code(); return UfForCode[ddd]; } public static removeSpecialChars(cell: string): string { const value = this.util.string(cell).removeSpecialChars(); return this.util.string(value).removeSpaces(); } public static addMask(cell: string): string { const phone = this.removeSpecialChars(cell); const ddd = phone.slice(0, 2); const partA = phone.slice(2, 6); const partB = phone.slice(6, 10); return `(${ddd}) ${partA}-${partB}`; } /** * * @param value value as string * @returns instance of HomePhone or throw an error */ public static init(value: string): HomePhone { const isValidValue = HomePhone.isValidProps(value); if (!isValidValue) throw new Error(HomePhone.MESSAGE); const phone = this.removeSpecialChars(value); return new HomePhone(phone); } /** * * @param value Brazilian home phone number * @example (XX) XXXX-XXXX * @returns Result of HomePhoneValueObject */ public static create(value: string): Result { if (!HomePhone.isValidProps(value)) { return Result.fail(HomePhone.MESSAGE); } const phone = this.removeSpecialChars(value); return Result.Ok(new HomePhone(phone)); } } export { HomePhone }; export default HomePhone; ================================================ FILE: packages/phone/index.ts ================================================ export * from './home.value-object'; export * from './mobile.value-object'; export * from './phone.value-object'; ================================================ FILE: packages/phone/mobile.value-object.ts ================================================ import { Result, ValueObject } from 'rich-domain'; import { UfForCode, ddd, AreaCodes } from './ddd.list'; const regexHash = /^\([1-9]{2}\)\s[9](?!\d(?:(\d)\1{2})-(\d)\1{3})[5-9][0-9]{3}\-[0-9]{4}$|^[1-9]{2}9[0-9]{8}$/; const regexHashSpecialChars = /\(|\)|-|\s/g; /** * @description Brazilian Mobile Phone Number * @default (XX) 9XXXX-XXXX */ class MobilePhone extends ValueObject { protected static readonly REGEX = regexHash; protected static readonly MESSAGE: string = 'Invalid Mobile Phone Number'; private constructor(prop: string) { super(prop); } toPattern(): string { return MobilePhone.addMask(this.props); } /** * * @param value Phone number (XX) 9XXXX-XXXX * @returns true if pattern match and false if not. */ public static isValidProps(value: string): boolean { const isValidDDD = AreaCodes.includes(this.code(value)); const matchPattern = this.validator.string(value).match(MobilePhone.REGEX); return isValidDDD && matchPattern; } public static isValid(value: string): boolean { return MobilePhone.isValidProps(value); } isMobile(): boolean { return true; }; isHome(): boolean { return false; }; /** * @returns value XX9XXXXXXXX as string */ value(): string { return this.props; } /** * * @returns only numbers without special chars. Includes 0 and DDD. * @example 01199502301 */ toCall(): string { const onlyNumbersAsString = this.props; return `0${onlyNumbersAsString}`; } number(): string { return this.props.slice(2); } /** * * @returns only area code (DDD) as number * @example 11 */ code(): ddd { return parseInt(this.props.slice(0, 2)) as ddd; } /** * * @returns only area code (DDD) as number * @example 11 */ public static code(phone: string): ddd { const value = this.util.string(phone).removeSpecialChars(); return parseInt(value.slice(0, 2)) as ddd; } uf() { const ddd = this.code(); return UfForCode[ddd]; } public static removeSpecialChars(cell: string): string { const value = this.util.string(cell).removeSpecialChars(); return this.util.string(value).removeSpaces(); } public static addMask(cell: string): string { const phone = this.removeSpecialChars(cell); const ddd = phone.slice(0, 2); const partA = phone.slice(2, 7); const partB = phone.slice(7, 11); return `(${ddd}) ${partA}-${partB}`; } /** * * @param value value as string * @returns instance of MobilePhone or throw an error */ public static init(value: string): MobilePhone { const isValidValue = MobilePhone.isValidProps(value); if (!isValidValue) throw new Error(MobilePhone.MESSAGE); const phone = this.removeSpecialChars(value); return new MobilePhone(phone); } /** * * @param value Brazilian Mobile phone number * @example (XX) 9XXXX-XXXX * @returns Result of MobilePhoneValueObject */ public static create(value: string): Result { if (!MobilePhone.isValidProps(value)) { return Result.fail(MobilePhone.MESSAGE); } const phone = this.removeSpecialChars(value); return Result.Ok(new MobilePhone(phone)); } } export { MobilePhone }; export default MobilePhone; ================================================ FILE: packages/phone/package.json ================================================ { "name": "@type-ddd/phone", "description": "Library that provides TypeScript type definitions for handling Phone Numbers in Domain-Driven Design contexts. It facilitates the validation and manipulation of Brazilian phone numbers.", "version": "0.1.0", "main": "index.js", "types": "index.d.ts", "author": "Alessandro Dev", "license": "MIT", "publishConfig": { "access": "public" }, "keywords": [ "Complexity", "Business Logic", "DDD", "Domain Driving Design", "DDD-Utils", "Base Value Object", "Domain Events", "Clean Architecture", "Phone Numbers", "Validation", "Formatting", "Value Object", "Utility", "Security", "Standards", "Brazil" ], "scripts": { "build": "tsc" }, "peerDependencies": { "rich-domain": "^1.25.0" }, "files": [ "index.js", "index.d.ts", "home.value-object.js", "home.value-object.d.ts", "mobile.value-object.js", "mobile.value-object.d.ts", "phone.value-object.js", "phone.value-object.d.ts", "ddd.list.js", "ddd.list.d.ts" ], "repository": { "type": "git", "url": "git+https://github.com/4lessandrodev/type-ddd.git" }, "bugs": { "url": "https://github.com/4lessandrodev/type-ddd/issues" }, "homepage": "https://github.com/4lessandrodev/type-ddd/tree/main/packages/phone", "gitHead": "4cb9159bde8d6fc951e9d902feed2ad25da49fa4" } ================================================ FILE: packages/phone/phone.value-object.ts ================================================ import { Result, ValueObject } from "rich-domain"; import MobilePhone from "./mobile.value-object"; import HomePhone from "./home.value-object"; import { ddd } from "./ddd.list"; export class Phone extends ValueObject { protected static readonly MESSAGE: string = 'Invalid Phone Number'; toCall: () => string; number: () => string; value: () => string; ddd: () => ddd; isMobile: () => boolean; isHome: () => boolean; uf: () => string; toPattern: () => string; /** * * @returns DDD only as number * @example 11 */ public static ddd(phone: string): ddd { const value = this.util.string(phone).removeSpecialChars(); return parseInt(value.slice(0, 2)) as ddd; } public static removeSpecialChars(cell: string): string { const value = this.util.string(cell).removeSpecialChars(); return this.util.string(value).removeSpaces(); } public static addMask(cell: string): string { if (this.isMobile(cell)) return MobilePhone.addMask(cell); return HomePhone.addMask(cell); } public static isValid(phone: string): boolean { return this.isValidProps(phone); } public static isValidProps(phone: string): boolean { return this.isHome(phone) || this.isMobile(phone); } public static isMobile(phone: string): boolean { return MobilePhone.isValid(phone); }; public static isHome(phone: string): boolean { return HomePhone.isValid(phone); }; /** * * @param value value as string * @returns instance of MobilePhone or HomePhone or throw an error */ public static init(value: string): MobilePhone | HomePhone { const isValid = this.isValidProps(value); if (!isValid) throw new Error(this.MESSAGE); const isMobile = this.isMobile(value); if (isMobile) return MobilePhone.init(value); return HomePhone.init(value); } /** * * @param value Brazilian Mobile or Home phone number * @example (XX) 9XXXX-XXXX or (XX) 3XXX-XXXX * @returns Result of MobilePhone or HomePhone */ public static create(value: string): Result { const isValid = this.isValidProps(value); if (!isValid) return Result.fail(this.MESSAGE); const isMobile = this.isMobile(value); if (isMobile) return MobilePhone.create(value); return HomePhone.create(value); } } export default Phone; ================================================ FILE: packages/phone/tsconfig.build.json ================================================ { "extends": "../tsconfig.build.json", "compilerOptions": { "outDir": ".", "rootDir": ".", "paths": {} }, "exclude": [ "node_modules", "dist", "__tests__/**/*", "*.spec.ts" ], "references": [] } ================================================ FILE: packages/phone/tsconfig.json ================================================ { "extends": "../tsconfig.build.json", "compilerOptions": { "types": [ "node" ] }, "files": [], "include": [], "references": [ { "path": "./tsconfig.build.json" } ] } ================================================ FILE: packages/tsconfig.build.json ================================================ { "compilerOptions": { "composite": true, "module": "commonjs", "declaration": true, "noImplicitAny": false, "skipLibCheck": true, "noUnusedLocals": false, "importHelpers": true, "removeComments": false, "noLib": false, "emitDecoratorMetadata": true, "experimentalDecorators": true, "useUnknownInCatchVariables": false, "target": "ES2021", "sourceMap": false, "allowJs": false, "strict": true, "strictNullChecks": false, "types": [ "node" ] } } ================================================ FILE: packages/tsconfig.json ================================================ { "files": [], "references": [ { "path": "./cnpj/tsconfig.build.json" }, { "path": "./cpf/tsconfig.build.json" }, { "path": "./date/tsconfig.build.json" }, { "path": "./email/tsconfig.build.json" }, { "path": "./logger/tsconfig.build.json" }, { "path": "./password/tsconfig.build.json" }, { "path": "./patterns/tsconfig.build.json" }, { "path": "./phone/tsconfig.build.json" }, { "path": "./type-ddd/tsconfig.build.json" }, { "path": "./username/tsconfig.build.json" }, { "path": "./zip-code/tsconfig.build.json" }, { "path": "./money/tsconfig.build.json" } ] } ================================================ FILE: packages/type-ddd/CHANGELOG.md ================================================ # Change Log All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. ## [0.0.7-alpha.0](https://github.com/4lessandrodev/type-ddd/compare/@type-ddd/core@0.0.2...@type-ddd/core@0.0.7-alpha.0) (2024-12-16) ## 4.0.5 (2024-11-28) ### Bug Fixes * change type create method return null [#194](https://github.com/4lessandrodev/type-ddd/issues/194) ([2cd03bf](https://github.com/4lessandrodev/type-ddd/commit/2cd03bf34387f4889a0a292ba350f2c0cfc753b7)) * export money ([633577e](https://github.com/4lessandrodev/type-ddd/commit/633577eb6aeb4aa7389d747f1b076a042a5e8b22)) * solve error module not found [#449](https://github.com/4lessandrodev/type-ddd/issues/449) ([e9d14f6](https://github.com/4lessandrodev/type-ddd/commit/e9d14f694cafc9c2123cc31055a4561f460a82d3)) # Changelog All notable changes to this project will be documented in this file. ## Unreleased --- ## Released --- ### [0.0.5] - 2024-11-28 ### Fix - update rich-domain lib to check nullish type: now 'create' return a possibly null value in Result instance. ### [0.0.4] - 2024-09-26 ### Fix - Corrected `"files"` in `package.json` to include `utils.js` and `utils.d.ts`, resolving module not found errors during compilation in email and password. --- ### [0.0.3] - 2024-05-31 ### Fix - Fix: export money v.o --- ### [0.0.2] - 2024-05-31 ### Fix - Fix: add individual package version instead link --- ### [0.0.1] - 2024-05-31 ### Base - Create base value object as single pack ================================================ FILE: packages/type-ddd/README.md ================================================ # `@type-ddd/core` > The @type-ddd/core library provides TypeScript type definitions for handling utils value object in Domain-Driven Design contexts. It facilitates the validation and manipulation, ensuring they adhere to standards. --- ## Installation Install `rich-domain` and `@type-ddd/core` with your favorite package manager ```sh npm i rich-domain @type-ddd/core # OR yarn add rich-domain @type-ddd/core ``` ## Packages - "@type-ddd/cpf" - "@type-ddd/cnpj" - "@type-ddd/date" - "@type-ddd/email" - "@type-ddd/password" - "@type-ddd/patterns" - "@type-ddd/phone" - "@type-ddd/username" - "@type-ddd/zip-code" - "@type-ddd/money" ================================================ FILE: packages/type-ddd/index.ts ================================================ export * from 'rich-domain'; export * from "@type-ddd/cpf"; export * from "@type-ddd/cnpj"; export * from "@type-ddd/date"; export * from "@type-ddd/email"; export * from "@type-ddd/password"; export * from "@type-ddd/patterns"; export * from "@type-ddd/phone"; export * from "@type-ddd/username"; export * from "@type-ddd/zip-code"; export * from '@type-ddd/money'; ================================================ FILE: packages/type-ddd/package.json ================================================ { "name": "@type-ddd/core", "description": "This package provide utils file and interfaces to assistant build a complex application with domain driving design", "version": "0.1.0", "main": "index.js", "types": "index.d.ts", "author": "Alessandro Dev", "license": "MIT", "publishConfig": { "access": "public" }, "scripts": { "build": "tsc" }, "dependencies": { "@type-ddd/cnpj": "^0.1.0", "@type-ddd/cpf": "^0.1.0", "@type-ddd/date": "^0.1.0", "@type-ddd/email": "^0.1.0", "@type-ddd/money": "^0.1.0", "@type-ddd/password": "^0.1.0", "@type-ddd/patterns": "^0.1.0", "@type-ddd/phone": "^0.1.0", "@type-ddd/username": "^0.1.0", "@type-ddd/zip-code": "^0.1.0", "rich-domain": "^1.25.0" }, "files": [ "index.js", "index.d.ts" ], "repository": { "type": "git", "url": "git+https://github.com/4lessandrodev/type-ddd.git" }, "bugs": { "url": "https://github.com/4lessandrodev/type-ddd/issues" }, "homepage": "https://github.com/4lessandrodev/type-ddd/tree/main", "gitHead": "4cb9159bde8d6fc951e9d902feed2ad25da49fa4" } ================================================ FILE: packages/type-ddd/tsconfig.build.json ================================================ { "extends": "../tsconfig.build.json", "compilerOptions": { "outDir": ".", "rootDir": ".", "paths": {} }, "exclude": [ "node_modules", "dist", "__tests__/**/*", "*.spec.ts" ], "references": [] } ================================================ FILE: packages/type-ddd/tsconfig.json ================================================ { "extends": "../tsconfig.build.json", "compilerOptions": { "types": [ "node" ] }, "files": [], "include": [], "references": [ { "path": "./tsconfig.build.json" } ] } ================================================ FILE: packages/username/CHANGELOG.md ================================================ # Change Log All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. ## [0.0.4-alpha.0](https://github.com/4lessandrodev/type-ddd/compare/@type-ddd/username@0.0.2...@type-ddd/username@0.0.4-alpha.0) (2024-12-16) ## 4.0.5 (2024-11-28) ### Bug Fixes * change type create method return null [#194](https://github.com/4lessandrodev/type-ddd/issues/194) ([2cd03bf](https://github.com/4lessandrodev/type-ddd/commit/2cd03bf34387f4889a0a292ba350f2c0cfc753b7)) ## 4.0.3 (2024-07-26) # Changelog All notable changes to this project will be documented in this file. ## Unreleased --- ## Released --- ### [0.0.3] - 2024-11-28 ### Fix - update rich-domain lib to check nullish type: now 'create' return a possibly null value in Result instance. ### [0.0.2] - 2024-05-31 ### Docs - Doc: update documentation --- ### [0.0.1] - 2024-05-31 ### Base - Create base value object as single pack ================================================ FILE: packages/username/README.md ================================================ # `@type-ddd/username` > The @type-ddd/username library provides TypeScript type definitions for handling User Name in Domain-Driven Design contexts. It facilitates the validation and manipulation of Person Name standards. --- ## Installation Install `rich-domain` and `@type-ddd/username` with your favorite package manager ```sh npm i rich-domain @type-ddd/username # OR yarn add rich-domain @type-ddd/username ``` ## Usage ```ts import { UserName } from '@type-ddd/username' // Instance of name or throws an error if provide an invalid value const name = UserName.init('jane doe'); // OR // Result of name (Check Result pattern docs) const result = UserName.create('jane doe'); result.isOk(); // true // userName instance or null if provide an invalid value const name = result.value(); ``` ## Check string is valid name ```ts const result = UserName.isValid('jane doe'); // Output: true ``` ## Utils some utils methods ```ts const fullName = UserName.init('jane doe spencer') const initials = fullName.initials(); // JDS const middle = fullName.middleName(); // Doe const first = fullName.firstName(); // Jane const firstWithTitle = fullName.title('Sra.').firstName(); // Sra. Jane const last = fullName.lastName(); // Spencer const upper = fullName.upperCase(); // JANE DOE SPENCER ``` ================================================ FILE: packages/username/__tests__/user-name-value-object.util.spec.ts ================================================ import UserName from '../index' describe('user-name.value-object', () => { it('should be defined', () => { const username = UserName.create; expect(username).toBeDefined(); }); it('should create a valid user name', () => { const username = UserName.create('valid username'); expect(username.isOk()).toBeTruthy(); }); it('should get value', () => { const username = UserName.create('valid username').value(); expect(username?.value()).toBe('Valid Username'); }); it('should fail if provide a long name (41) chars', () => { const username = UserName.create( 'invalid_username'.repeat(10), ); expect(username.isFail()).toBe(true); }); it('should fail if provide a small name (1) char', () => { const username = UserName.create('i'); expect(username.isFail()).toBe(true); }); it('should get first name with success', () => { const username = UserName.create('first middle last').value(); expect(username?.firstName()).toBe('First'); }); it('should check if has last name [false]', () => { const username = UserName.create('first').value(); expect(username?.hasLastName()).toBe(false); }); it('should check if has last name [true]', () => { const username = UserName.create('first middle last').value(); expect(username?.hasLastName()).toBe(true); }); it('should check if has last name [true]', () => { const username = UserName.create('first last').value(); expect(username?.hasLastName()).toBe(true); }); it('should check if has middle name [false]', () => { const username = UserName.create('first').value(); expect(username?.hasMiddleName()).toBe(false); }); it('should check if has middle name [true]', () => { const username = UserName.create('first middle last').value(); expect(username?.hasMiddleName()).toBe(true); }); it('should get first name', () => { const username = UserName.create('first middle last').value(); expect(username?.firstName()).toBe('First'); }); it('should get first name', () => { const username = UserName.create('first middle').value(); expect(username?.firstName()).toBe('First'); }); it('should get first name', () => { const username = UserName.create('first').value(); expect(username?.firstName()).toBe('First'); }); it('should get middle name', () => { const username = UserName.create('first middle last').value(); expect(username?.middleName()).toBe('Middle'); }); it('should NOT get middle name', () => { const username = UserName.create('first last').value(); expect(username?.middleName()).toBe(''); }); it('should get last name', () => { const username = UserName.create('first last').value(); expect(username?.lastName()).toBe('Last'); }); it('should return the first if does not exist last name', () => { const username = UserName.create('FIRST').value(); expect(username?.lastName()).toBe('First'); }); it('should capitalize names', () => { const username = UserName.create('first middle last').value(); expect(username?.value()).toBe('First Middle Last'); }); it('should capitalize names', () => { const username = UserName.create('FIRST MIDDLE LAST').value(); expect(username?.value()).toBe('First Middle Last'); expect(username?.get('value')).toBe('First Middle Last'); }); it('should get initials', () => { const username = UserName.create('FIRST MIDDLE LAST').value(); expect(username?.initials()).toBe('FML'); }); it('should get initials with custom separator', () => { const separator = '-'; const username = UserName.create('FIRST MIDDLE LAST').value(); expect(username?.initials(separator)).toBe('F-M-L'); }); it('should get initials with none separator', () => { const separator = '.'; const username = UserName.create('FIRST MIDDLE LAST').value(); expect(username?.initials(separator)).toBe('F.M.L'); }); it('should get initials', () => { const username = UserName.create('FIRSt').value(); expect(username?.initials()).toBe('F'); }); it('should name with duple spaces', () => { const username = UserName.create( 'José caleb dos Santos', ).value(); expect(username?.value()).toBe('José Caleb Dos Santos'); }); it('should name with duple spaces and two character "de" ', () => { const username = UserName.create( 'José caleb de Oliveira', ).value(); expect(username?.value()).toBe('José Caleb De Oliveira'); }); it('should name with duple spaces and two character specials ', () => { const username = UserName.create( 'José caleb , de Oliveira', ).value(); expect(username?.value()).toBe('José Caleb De Oliveira'); }); it('should name with three spaces in name', () => { const username = UserName.create( 'José caleb de Oliveira', ).value(); expect(username?.value()).toBe('José Caleb De Oliveira'); }); it('should init an instance with success', () => { const init = () => UserName.init('lorem ipsum'); expect(init).not.toThrowError(); }); it('should throw an error on init an instance with invalid value', () => { const init = () => UserName.init(''); expect(init).toThrowError(); }); it('should capitalize a full name', () => { const value = UserName.capitalize('jane doe'); expect(value).toBe('Jane Doe'); }); it('should uppercase value', () => { const name = UserName.init('jane doe'); const value = name.upperCase(); expect(value).toBe('JANE DOE'); }); it('should lowercase value', () => { const name = UserName.init('JaNe DOE'); const value = name.lowerCase(); expect(value).toBe('jane doe'); }); it('should validate user name', () => { expect(UserName.isValid('jane doe')).toBeTruthy(); expect(UserName.isValid('')).toBeFalsy(); }); it('should add title', () => { const name = UserName.init('Juliana Paes Moreira'); expect(name.title('Sra.').firstName()).toBe('Sra. Juliana'); expect(name.title('Sra.').fullName()).toBe('Sra. Juliana Paes Moreira'); expect(name.title('Sra.').lastName()).toBe('Sra. Moreira'); expect(name.title('Sra.').middleName()).toBe('Sra. Paes'); }) }); ================================================ FILE: packages/username/index.ts ================================================ import { Result, ValueObject } from 'rich-domain'; export class UserName extends ValueObject { protected static readonly MAX_LENGTH: number = 82; protected static readonly MIN_LENGTH: number = 2; protected static readonly MESSAGE: string = `Invalid name length. Must has min ${UserName.MIN_LENGTH} and max ${UserName.MAX_LENGTH} chars`; private constructor(props: string) { super(props); } /** * @returns capitalized full name */ value(): string { return this.props.trim(); } /** * * @returns capitalize full name as string */ public static capitalize(fullName: string): string { const names = fullName.split(' ').filter((name): boolean => { return name.length > 1; }); const capitalizedName = (name: string): string => { return name[0].toUpperCase() + name.slice?.(1)?.toLowerCase(); }; const capitalized: string[] = []; for (const name of names) { const lowerCaseName = capitalizedName(name); capitalized.push(lowerCaseName); } return capitalized.toString().replace(/,/g, ' '); } /** * @description get upper case name from instance * @returns upperCase full name as string */ upperCase(): string { return this.props.toUpperCase(); } /** * @description get lower case name from instance * @returns lowerCase full name as string */ lowerCase(): string { return this.props.toLowerCase(); } /** * * @returns check if has a second name */ hasMiddleName(): boolean { return this.props.split(' ').length > 2; } title(title: string) { return { firstName: (): string => title + ' ' + this.firstName(), fullName: (): string => title + ' ' + this.value(), lastName: (): string => title + ' ' + this.lastName(), middleName: (): string => title + ' ' + this.middleName(), } } /** * * @returns check if has last name `first middle last` */ hasLastName(): boolean { return this.props.split(' ').length >= 2; } /** * * @returns first name */ firstName(title: string = ''): string { return (title + ' ' + this.props.split(' ')[0])?.trim(); } /** * * @returns middle name if it has more than 2 names, else returns a empty string */ middleName(): string { if (!this.hasMiddleName()) { return ''; } return this.props.split(' ')[1]?.trim(); } /** * * @returns last name if exists else return the name */ lastName(): string { const names = this.props.split(' '); return names.at(-1)?.trim(); } /** * @returns initials as string * @param separator as string char to separate letters * @default separator (empty) * @example * for a name "Thomas A. Anderson" = "TAA" */ initials(separator = ''): string { const names = this.props.split(' '); const letters = names.map((name): string => name[0]); const value = this.util.string(letters.toString()); const initials = value.replace(',').to(separator); return initials; } /** * * @param value value as string * @returns instance of UserName or throw an error */ public static init(value: string): UserName { const isValidValue = UserName.isValidProps(value); if (!isValidValue) throw new Error(UserName.MESSAGE); const capitalized = UserName.capitalize(value); return new UserName(capitalized); } /** * @description check name length min(2) max(40) * @param value name as string * @returns true if provided value is valid and false if not */ public static isValid(value: string): boolean { return this.isValidProps(value); } /** * @description check name length min(2) max(40) * @param value name as string * @returns true if provided value is valid and false if not */ public static isValidProps(value: string): boolean { const { string } = this.validator; return string(value).hasLengthBetween( UserName.MIN_LENGTH, UserName.MAX_LENGTH, ); } public static create(value: string): Result { const isValidValue = UserName.isValidProps(value); if (!isValidValue) { return Result.fail(UserName.MESSAGE); } const capitalized = UserName.capitalize(value); return Result.Ok(new UserName(capitalized)); } } export default UserName; ================================================ FILE: packages/username/package.json ================================================ { "name": "@type-ddd/username", "description": "This package provides TypeScript type definitions for handling User Name in Domain-Driven Design contexts", "version": "0.1.0", "main": "index.js", "types": "index.d.ts", "author": "Alessandro Dev", "license": "MIT", "publishConfig": { "access": "public" }, "keywords": [ "Complexity", "Business Logic", "DDD", "Domain Driving Design", "DDD-Utils", "Base Value Object", "Domain Events", "Clean Architecture", "User Name", "Validation", "Formatting", "Value Object", "Utility", "Security", "Standards" ], "scripts": { "build": "tsc" }, "peerDependencies": { "rich-domain": "^1.25.0" }, "files": [ "index.js", "index.d.ts" ], "repository": { "type": "git", "url": "git+https://github.com/4lessandrodev/type-ddd.git" }, "bugs": { "url": "https://github.com/4lessandrodev/type-ddd/issues" }, "homepage": "https://github.com/4lessandrodev/type-ddd/tree/main/packages/username", "gitHead": "4cb9159bde8d6fc951e9d902feed2ad25da49fa4" } ================================================ FILE: packages/username/tsconfig.build.json ================================================ { "extends": "../tsconfig.build.json", "compilerOptions": { "outDir": ".", "rootDir": ".", "paths": {} }, "exclude": [ "node_modules", "dist", "__tests__/**/*", "*.spec.ts" ], "references": [] } ================================================ FILE: packages/username/tsconfig.json ================================================ { "extends": "../tsconfig.build.json", "compilerOptions": { "types": [ "node" ] }, "files": [], "include": [], "references": [ { "path": "./tsconfig.build.json" } ] } ================================================ FILE: packages/zip-code/CHANGELOG.md ================================================ # Change Log All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. ## [0.0.4-alpha.0](https://github.com/4lessandrodev/type-ddd/compare/@type-ddd/zip-code@0.0.2...@type-ddd/zip-code@0.0.4-alpha.0) (2024-12-16) ## 4.0.5 (2024-11-28) ### Bug Fixes * change type create method return null [#194](https://github.com/4lessandrodev/type-ddd/issues/194) ([2cd03bf](https://github.com/4lessandrodev/type-ddd/commit/2cd03bf34387f4889a0a292ba350f2c0cfc753b7)) ## 4.0.3 (2024-07-26) # Changelog All notable changes to this project will be documented in this file. ## Unreleased --- ## Released --- ### [0.0.3] - 2024-11-28 ### Fix - update rich-domain lib to check nullish type: now 'create' return a possibly null value in Result instance. ### [0.0.2] - 2024-05-31 ### Docs - Doc: update documentation --- ### [0.0.1] - 2024-05-31 ### Base - Create base value object as single pack ================================================ FILE: packages/zip-code/README.md ================================================ # `@type-ddd/zip-code` > The @type-ddd/cpf library provides TypeScript type definitions for handling ZipCode in Domain-Driven Design contexts. It facilitates the validation and manipulation of ZipCode numbers, ensuring they adhere to the Brazilian legal standards. --- ## Installation Install `rich-domain` and `@type-ddd/zip-code` with your favorite package manager ```sh npm i rich-domain @type-ddd/zip-code # OR yarn add rich-domain @type-ddd/zip-code ``` ## Usage Don't worry about removing special characters; they are automatically stripped from all instances. ```ts import { ZipCode } from '@type-ddd/zip-code' // Instance of zipCode or throws an error if provide an invalid value const zipCode = ZipCode.init('75520140'); // OR // Result of zipCode (Check Result pattern docs) const result = ZipCode.create('75520140'); result.isOk(); // true // zipCode instance or null if provide an invalid value const zipCode = result.value(); ``` ## Check string is valid zipCode Don't worry about removing special characters; they are automatically stripped from all instances. ```ts const result = ZipCode.isValid('75520140'); // Output: true ``` ## Special chars If you need the value with the mask, you can use the `toPattern` method: ```ts zipCode.toPattern(); // Output: 75520-140 ``` Or if you need to apply mask from a string value you may use `addMask` method ```ts ZipCode.addMask('75520140'); // Output: 75520-140 ``` ================================================ FILE: packages/zip-code/__tests__/zip-code.value-object.spec.ts ================================================ import { ZipCode as ZipCodeValueObject } from '../index' describe('postal-code.value-object', () => { it('should be defined', () => { const valueObject = ZipCodeValueObject.create; expect(valueObject).toBeDefined(); }); it('should create a valid zip code', () => { const valueObject = ZipCodeValueObject.create('75520140'); expect(valueObject.isOk()).toBeTruthy(); }); it('should create a valid zip code', () => { const value = ZipCodeValueObject.init('75520140'); expect(value.toPattern()).toBe('75520-140'); }); it('should create a valid zip code', () => { const isValid = ZipCodeValueObject.isValid('75520140'); expect(isValid).toBeTruthy(); }); it('should get value', () => { const valueObject = ZipCodeValueObject.create('75520140').value(); expect(valueObject?.value()).toBe('75520140'); }); it('should get value without hyphen', () => { const valueObject = ZipCodeValueObject.create('75520-140').value(); expect(valueObject?.value()).toBe('75520140'); }); it('should fail if provide an invalid postal code', () => { const valueObject = ZipCodeValueObject.create('invalid'); expect(valueObject.isOk()).toBe(false); }); it('should init an instance with success', () => { const init = () => ZipCodeValueObject.init('05583-000'); expect(init).not.toThrowError(); }); it('should throw an error on init an instance with invalid value', () => { const init = () => ZipCodeValueObject.init(''); expect(init).toThrowError(); }); it('should add mask', () => { const masked = ZipCodeValueObject.addMask('05583000'); expect(masked).toBe('05583-000'); }); it('should add mask', () => { const masked = ZipCodeValueObject.addMask('05583-000'); expect(masked).toBe('05583-000'); }); it('should add mask', () => { const masked = ZipCodeValueObject.addMask('05583-00000'); expect(masked).toBe('05583-000'); }); it('should add mask', () => { const masked = ZipCodeValueObject.addMask('0558300000'); expect(masked).toBe('05583-000'); }); it('should add mask', () => { const masked = ZipCodeValueObject.addMask(5 as any); expect(masked).toBe(''); }); }); ================================================ FILE: packages/zip-code/index.ts ================================================ import { Result, ValueObject } from 'rich-domain'; const regexHash = /^[0-9]{5}-[0-9]{3}$|^[0-9]{8}$/; class ZipCode extends ValueObject { protected static readonly REGEX = regexHash; protected static readonly MESSAGE: string = 'Invalid zip code'; private constructor(prop: string) { super(prop); } /** * @returns value as string. always only numbers * @example 75520140 */ value(): string { return this.props; } /** * @description add hyphen and dot to cpf value. * @example before "75520140" * @example after "75520-140" */ public static addMask(zipCode: string): string { if (typeof zipCode !== 'string') return ''; if (zipCode.includes('-')) return zipCode.slice(0, 9); return zipCode.slice(0, 5) + '-' + zipCode.slice(5, 8); } /** * @description remove hyphen and dot from cpf value. * @example before "75520-140" * @example after "75520140" */ public static removeSpecialChars(zipCode: string): string { return this.util.string(zipCode).removeSpecialChars(); } /** * @description add special chars to numbers * @returns zip code as string following pattern xxxxx-xxx */ toPattern(): string { return ZipCode.addMask(this.props); } /** * * @param value PostalCode as string * @returns true if value match with pattern and false if do not. */ public static isValid(value: string): boolean { return this.isValidProps(value); } /** * * @param value PostalCode as string * @returns true if value match with pattern and false if do not. */ public static isValidProps(value: string): boolean { return this.validator.string(value).match(ZipCode.REGEX); } /** * * @param value value as string * @returns instance of ZipCode or throw an error */ public static init(value: string): ZipCode { const isValidValue = ZipCode.isValidProps(value); if (!isValidValue) throw new Error(ZipCode.MESSAGE); const numbers = ZipCode.removeSpecialChars(value); return new ZipCode(numbers); } public static create(value: string): Result { if (!ZipCode.isValidProps(value)) { return Result.fail(ZipCode.MESSAGE); } const numbers = ZipCode.removeSpecialChars(value); return Result.Ok(new ZipCode(numbers)); } } export { ZipCode }; export default ZipCode; ================================================ FILE: packages/zip-code/package.json ================================================ { "name": "@type-ddd/zip-code", "description": "This package provides TypeScript type definitions for handling Brazilian Zip Code in Domain-Driven Design contexts", "version": "0.1.0", "main": "index.js", "types": "index.d.ts", "author": "Alessandro Dev", "license": "MIT", "publishConfig": { "access": "public" }, "keywords": [ "Complexity", "Business Logic", "DDD", "Domain Driving Design", "DDD-Utils", "Base Value Object", "Domain Events", "Clean Architecture", "ZipCode", "Validation", "Formatting", "Value Object", "Utility", "Security", "Standards", "Brazil" ], "scripts": { "build": "tsc" }, "peerDependencies": { "rich-domain": "^1.25.0" }, "files": [ "index.js", "index.d.ts", "util.js", "util.d.ts" ], "repository": { "type": "git", "url": "git+https://github.com/4lessandrodev/type-ddd.git" }, "bugs": { "url": "https://github.com/4lessandrodev/type-ddd/issues" }, "homepage": "https://github.com/4lessandrodev/type-ddd/tree/main/packages/zip-code", "gitHead": "4cb9159bde8d6fc951e9d902feed2ad25da49fa4" } ================================================ FILE: packages/zip-code/tsconfig.build.json ================================================ { "extends": "../tsconfig.build.json", "compilerOptions": { "outDir": ".", "rootDir": ".", "paths": {} }, "exclude": [ "node_modules", "dist", "__tests__/**/*", "*.spec.ts" ], "references": [] } ================================================ FILE: packages/zip-code/tsconfig.json ================================================ { "extends": "../tsconfig.build.json", "compilerOptions": { "types": [ "node" ] }, "files": [], "include": [], "references": [ { "path": "./tsconfig.build.json" } ] } ================================================ FILE: packages/zip-code/util.ts ================================================ interface CpfDigits { penultimateDigit: number; ultimateDigit: number; } const removeSpecialCharsFromCpfRegex = /[\.]|[-]/g; export const formatValueToCpfPattern = (cpf: string): string => { const cpfValue = removeSpecialCharsFromCpf(cpf); let formattedValue: string = ''; let index: number = 0; while (formattedValue.length < 14 && index < 11) { if (index === 3 || index === 6) { formattedValue += '.'; } else if (index === 9) { formattedValue += '-'; } formattedValue += cpfValue[index]; index++; } return formattedValue; }; export const removeSpecialCharsFromCpf = (cpf: string): string => { return cpf.replace(removeSpecialCharsFromCpfRegex, ''); }; const getCpfDigitsNumbers = (cpf: string): CpfDigits => { const lastTwoNumbers = cpf.slice(cpf.length - 2); const penultimateDigit = parseInt(lastTwoNumbers[0]); const ultimateDigit = parseInt(lastTwoNumbers[1]); return { penultimateDigit, ultimateDigit, }; }; const transformCpfInArrNumber = (cpf: string): number[] => { var arr: number[] = []; let index = 0; while (index < 9) { arr.push(parseInt(cpf[index])); index++; } return arr; }; export const calculateCpfDigits = (cpfNumbers: number[]): CpfDigits => { const factor = 11; let index = 0; let startAuxValue = 10; let totalForDigit = 0; while (index < 9) { totalForDigit = totalForDigit + cpfNumbers[index] * startAuxValue; startAuxValue--; index++; } const calcPDigit = totalForDigit % factor; const resultPDigit = factor - calcPDigit; const zeroIfPGreaterThanNine = resultPDigit > 9 ? 0 : resultPDigit; const penultimateDigit = zeroIfPGreaterThanNine; index = 0; startAuxValue = 11; totalForDigit = 0; cpfNumbers.push(penultimateDigit); while (index < 10) { totalForDigit = totalForDigit + cpfNumbers[index] * startAuxValue; startAuxValue--; index++; } const calcUDigit = totalForDigit % factor; const resultUDigit = factor - calcUDigit; const zeroIfGreaterThanNine = resultUDigit > 9 ? 0 : resultUDigit; const ultimateDigit = zeroIfGreaterThanNine; return { penultimateDigit, ultimateDigit, }; }; export const isValidCpfDigit = (cpf: string): boolean => { const onlyNumbers = removeSpecialCharsFromCpf(cpf); if (onlyNumbers.length !== 11) { return false; } const digits = getCpfDigitsNumbers(onlyNumbers); const arrNumbers = transformCpfInArrNumber(onlyNumbers); const validDigits = calculateCpfDigits(arrNumbers); return ( digits.penultimateDigit === validDigits.penultimateDigit && digits.ultimateDigit === validDigits.ultimateDigit ); }; export default isValidCpfDigit; ================================================ FILE: scripts/login.sh ================================================ #!/usr/bin/expect -f set admin_user "admin" set admin_password "admin" set admin_email "dev@localhost.com" set registry_url "http://localhost:4873/" set specific_config "config=local" spawn npm login --registry=$registry_url expect "Username: " send "$admin_user\r" expect "Password: " send "$admin_password\r" send "$admin_email\r" expect eof ================================================ FILE: scripts/make-user.sh ================================================ #!/bin/bash url='http://localhost:4873/-/user/org.couchdb.user:admin' args=( "//localhost:4873/:_auth=YWRtaW46YWRtaW4=" "strict-ssl=false" "email=dev@localhost.com" "always-auth=true" "init-author-name=dev" "init-author-email=dev@localhost.com" ) response=$(curl -XPUT -H "Content-type: application/json" -d '{"name": "admin", "password": "admin"}' "$url") token=$(echo "$response" | grep -o '"token": ".*"' | awk -F'"' '{print $4}') if [[ $token ]]; then for item in "${args[@]}"; do echo "$item" >>.npmrc done echo "//http://localhost:4873/:_authToken=$token" >>.npmrc echo "" >>.npmrc echo "Configurações salvas em .npmrc." else echo "Erro na requisição. Resposta: $response" fi ================================================ FILE: tsconfig.json ================================================ { "compilerOptions": { "module": "commonjs", "noImplicitAny": false, "noUnusedLocals": false, "removeComments": true, "noLib": false, "emitDecoratorMetadata": true, "experimentalDecorators": true, "useUnknownInCatchVariables": false, "target": "ES2021", "sourceMap": true, "allowJs": false, "outDir": "dist", "baseUrl": ".", "paths": { "@type-ddd/cnpj": ["./packages/cnpj"], "@type-ddd/cpf": ["./packages/cpf"], "@type-ddd/date": ["./packages/date"], "@type-ddd/email": ["./packages/email"], "@type-ddd/logger": ["./packages/logger"], "@type-ddd/money": ["./packages/money"], "@type-ddd/password": ["./packages/password"], "@type-ddd/patterns": ["./packages/patterns"], "@type-ddd/phone": ["./packages/phone"], "@type-ddd/core": ["./packages/type-ddd"], "@type-ddd/username": ["./packages/username"], "@type-ddd/zip-code": ["./packages/zip-code"] } }, "include": ["packages/**/*"], "exclude": ["node_modules", "**/*.spec.ts", "__tests__"] } ================================================ FILE: update-peer-dependency.sh ================================================ #!/bin/bash # Update a specific peer dependency across all packages in the monorepo # Usage example: # in root folder execute: # ./update-peer-dependency.sh # ./update-peer-dependency.sh rich-domain 1.25.0 # Check if jq is installed if ! command -v jq &> /dev/null; then echo "Error: jq is not installed. Please install jq to proceed." exit 1 fi # Check if the correct number of arguments is provided # The script expects two arguments: the name of the dependency and the desired version. if [ "$#" -ne 2 ]; then echo "Usage: $0 " echo "Example: $0 rich-domain 1.25.0-beta" exit 1 fi # Assign the arguments to variables DEPENDENCY=$1 # The name of the dependency to be updated VERSION=$2 # The version to be set for the dependency # Directory where the packages are located. The script will look inside this directory. PACKAGES_DIR="./packages" # Check if the /packages directory exists if [ ! -d "$PACKAGES_DIR" ]; then echo "The /packages directory does not exist! Please ensure the path is correct." exit 1 fi # Start the loop to iterate over all folders inside the /packages directory for PACKAGE in $PACKAGES_DIR/*/; do # Check if the current path is a directory if [ -d "$PACKAGE" ]; then # Build the path for the package.json file inside the package PACKAGE_JSON="${PACKAGE}package.json" # Check if the package.json file exists in the current directory if [ -f "$PACKAGE_JSON" ]; then # Check if the package.json contains the specific dependency under peerDependencies if jq -e ".peerDependencies | has(\"$DEPENDENCY\")" "$PACKAGE_JSON" > /dev/null; then # Update the version of the dependency in the package.json file echo "Updating '$DEPENDENCY' to version ^$VERSION in $PACKAGE_JSON" # Use jq to modify the version of the dependency # The result is written to a temporary file and then replaces the original file jq ".peerDependencies[\"$DEPENDENCY\"] = \"^$VERSION\"" "$PACKAGE_JSON" > "$PACKAGE_JSON.tmp" && mv "$PACKAGE_JSON.tmp" "$PACKAGE_JSON" fi fi fi done # Message indicating the script has finished successfully echo "Update completed."