├── .github └── workflows │ ├── pages.yml │ ├── publish-debug.yml │ └── publish.yml ├── .gitignore ├── .prettierignore ├── .vscode └── launch.json ├── README.md ├── docs ├── _config.yml └── index.md ├── effect-libs.code-workspace ├── eslint.config.js ├── package.json ├── packages ├── ansi-styles │ ├── .gitignore │ ├── .madgerc │ ├── .prettierignore │ ├── README.md │ ├── docgen.json │ ├── eslint.config.js │ ├── esm │ │ ├── AnsiString.ts │ │ ├── Color.ts │ │ ├── ContextStyler.ts │ │ ├── Palette.ts │ │ ├── Style.ts │ │ ├── StyleCharacteristics.ts │ │ ├── Styles.ts │ │ ├── Text.ts │ │ └── index.ts │ ├── examples │ │ ├── all-colors.ts │ │ ├── basic-usage.ts │ │ ├── cancelling-a-style.ts │ │ ├── context-styler.ts │ │ └── simple-colors.ts │ ├── package.json │ ├── prettier.config.js │ ├── project.config.js │ ├── readme-assets │ │ ├── basic-example.png │ │ ├── cancelling-a-style.png │ │ └── simple-colors.png │ ├── tests │ │ ├── AnsiString.test.ts │ │ ├── Color.test.ts │ │ ├── ContextStyler.test.ts │ │ ├── Palette.test.ts │ │ ├── Style.test.ts │ │ ├── StyleCharacteristics.test.ts │ │ └── Text.test.ts │ ├── tsconfig.base.json │ ├── tsconfig.check.json │ ├── tsconfig.docgen.json │ ├── tsconfig.esm.json │ ├── tsconfig.json │ ├── tsconfig.others.json │ └── vite.config.ts ├── conversions │ ├── .gitignore │ ├── .madgerc │ ├── .prettierignore │ ├── docgen.json │ ├── eslint.config.js │ ├── esm │ │ ├── DateTime.ts │ │ ├── Email.ts │ │ ├── Int.ts │ │ ├── NumberBase10Format.ts │ │ ├── PlaceHolder.ts │ │ ├── Positive.ts │ │ ├── PositiveReal.ts │ │ ├── PositiveRealInt.ts │ │ ├── Real.ts │ │ ├── RealInt.ts │ │ ├── RoundingMode.ts │ │ ├── RoundingOption.ts │ │ ├── Schema.ts │ │ ├── SemVer.ts │ │ ├── Template.ts │ │ └── index.ts │ ├── examples │ │ └── stupid.ts │ ├── package.json │ ├── prettier.config.js │ ├── project.config.js │ ├── tests │ │ ├── DateTime.test.ts │ │ ├── Email.test.ts │ │ ├── NumberBase10Format.test.ts │ │ ├── PlaceHolder.test.ts │ │ ├── PositiveReal.test.ts │ │ ├── PositiveRealInt.test.ts │ │ ├── Real.test.ts │ │ ├── RealInt.test.ts │ │ ├── RoundingMode.test.ts │ │ ├── RoundingOption.test.ts │ │ ├── Schema.test.ts │ │ ├── SemVer.test.ts │ │ └── Template.test.ts │ ├── tsconfig.base.json │ ├── tsconfig.check.json │ ├── tsconfig.docgen.json │ ├── tsconfig.esm.json │ ├── tsconfig.json │ ├── tsconfig.others.json │ └── vite.config.ts ├── date │ ├── .gitignore │ ├── .madgerc │ ├── .prettierignore │ ├── eslint.config.js │ ├── esm │ │ ├── Basis.ts │ │ ├── DateFormatter.ts │ │ ├── Errors.ts │ │ ├── MergedBasis.ts │ │ ├── MergedToken.ts │ │ ├── Token.ts │ │ ├── formatDate.ts │ │ ├── index.ts │ │ └── parseDateString.ts │ ├── package.json │ ├── prettier.config.js │ ├── project.config.js │ ├── tests │ │ └── dummy.test.ts │ ├── tsconfig.base.json │ ├── tsconfig.check.json │ ├── tsconfig.esm.json │ ├── tsconfig.json │ ├── tsconfig.others.json │ └── vite.config.ts ├── effect-lib │ ├── .gitignore │ ├── .madgerc │ ├── .prettierignore │ ├── .vscode │ │ └── settings.json │ ├── README.md │ ├── docgen.json │ ├── eslint.config.js │ ├── esm │ │ ├── Array.ts │ │ ├── BigDecimal.ts │ │ ├── BigInt.ts │ │ ├── Cache.ts │ │ ├── Chunk.ts │ │ ├── Either.ts │ │ ├── Fs.ts │ │ ├── Function.ts │ │ ├── InputError.ts │ │ ├── Inspectable.ts │ │ ├── Json.ts │ │ ├── Match.ts │ │ ├── Number.ts │ │ ├── Option.ts │ │ ├── Pipeable.ts │ │ ├── PortError.ts │ │ ├── Predicate.ts │ │ ├── Record.ts │ │ ├── RegExp.ts │ │ ├── RegExpString.ts │ │ ├── String.ts │ │ ├── Struct.ts │ │ ├── Tree.ts │ │ ├── Tuple.ts │ │ ├── index.ts │ │ └── types.ts │ ├── package.json │ ├── prettier.config.js │ ├── project.config.js │ ├── tests │ │ ├── Array.test.ts │ │ ├── BigDecimal.test.ts │ │ ├── BigInt.test.ts │ │ ├── Cache.test.ts │ │ ├── Chunk.test.ts │ │ ├── Fs.test.ts │ │ ├── Function.test.ts │ │ ├── InputError.test.ts │ │ ├── Inspectable.test.ts │ │ ├── Match.test.ts │ │ ├── Number.test.ts │ │ ├── PortError.test.ts │ │ ├── Predicate.test.ts │ │ ├── Record.test.ts │ │ ├── RegExp.test.ts │ │ ├── RegExpString.test.ts │ │ ├── String.test.ts │ │ ├── Struct.test.ts │ │ ├── Tree.test.ts │ │ ├── Tuple.test.ts │ │ └── Types.test.ts │ ├── tsconfig.base.json │ ├── tsconfig.check.json │ ├── tsconfig.docgen.json │ ├── tsconfig.esm.json │ ├── tsconfig.json │ ├── tsconfig.others.json │ └── vite.config.ts ├── effect-report │ ├── .gitignore │ ├── .madgerc │ ├── .prettierignore │ ├── eslint.config.js │ ├── esm │ │ ├── Cause.ts │ │ ├── Effect.ts │ │ ├── Errors.ts │ │ ├── Logger.ts │ │ ├── Schema.ts │ │ ├── index.ts │ │ └── utils.ts │ ├── package.json │ ├── prettier.config.js │ ├── project.config.js │ ├── tests │ │ └── dummy.test.ts │ ├── tsconfig.base.json │ ├── tsconfig.check.json │ ├── tsconfig.esm.json │ ├── tsconfig.json │ ├── tsconfig.others.json │ └── vite.config.ts ├── node-effect-lib │ ├── .gitignore │ ├── .madgerc │ ├── .prettierignore │ ├── .vscode │ │ └── settings.json │ ├── eslint.config.js │ ├── esm │ │ ├── Fs.ts │ │ ├── Path.ts │ │ ├── Stream.ts1 │ │ └── index.ts │ ├── package.json │ ├── prettier.config.js │ ├── project.config.js │ ├── tests │ │ └── dummy.test.ts │ ├── tsconfig.base.json │ ├── tsconfig.check.json │ ├── tsconfig.esm.json │ ├── tsconfig.json │ ├── tsconfig.others.json │ └── vite.config.ts └── pretty-print │ ├── .gitignore │ ├── .madgerc │ ├── .prettierignore │ ├── README.md │ ├── docgen.json │ ├── eslint.config.js │ ├── esm │ ├── ByPasser.ts │ ├── ByPassers.ts │ ├── MarkMap.ts │ ├── MarkShower.ts │ ├── MarkShowerConstructor.ts │ ├── NonPrimitiveFormatter.ts │ ├── Option.ts │ ├── PrimitiveFormatter.ts │ ├── PropertyFilter.ts │ ├── PropertyFilters.ts │ ├── PropertyFormatter.ts │ ├── StringifiedProperties.ts │ ├── StringifiedValue.ts │ ├── StyleMap.ts │ ├── Value.ts │ ├── ValueBasedStyler.ts │ ├── ValueBasedStylerConstructor.ts │ ├── ValueOrder.ts │ ├── Values.ts │ └── index.ts │ ├── examples │ ├── circularity-handling.ts │ ├── treeify-with-leaves.ts │ ├── treeify.ts │ └── util-inspect-like.ts │ ├── package.json │ ├── prettier.config.js │ ├── project.config.js │ ├── readme-assets │ ├── circularity-handling.png │ ├── treeify-with-leaves.png │ ├── treeify.png │ └── util-inspect-like.png │ ├── tests │ ├── ByPasser.test.ts │ ├── ByPassers.test.ts │ ├── MarkMap.test.ts │ ├── NonPrimitiveFormatter.test.ts │ ├── Option.test.ts │ ├── PrimitiveFormatter.test.ts │ ├── PropertyFilter.test.ts │ ├── PropertyFilters.test.ts │ ├── PropertyFormatter.test.ts │ ├── StringifiedValue.test.ts │ ├── StyleMap.test.ts │ ├── Value.test.ts │ ├── ValueBasedStyler.test.ts │ └── Values.test.ts │ ├── tsconfig.base.json │ ├── tsconfig.check.json │ ├── tsconfig.docgen.json │ ├── tsconfig.esm.json │ ├── tsconfig.json │ ├── tsconfig.others.json │ └── vite.config.ts ├── pnpm-lock.yaml ├── pnpm-workspace.yaml ├── prettier.config.js ├── project.config.js ├── tests └── dummy.test.ts ├── tsconfig.base.json ├── tsconfig.check.json ├── tsconfig.esm.json ├── tsconfig.json ├── tsconfig.others.json └── vite.config.ts /.github/workflows/pages.yml: -------------------------------------------------------------------------------- 1 | # This workflow is triggered manually. It will create the documentation and push it to github pages 2 | 3 | name: Pages 4 | on: 5 | workflow_dispatch: 6 | 7 | concurrency: 8 | group: ${{ github.workflow }}-${{ github.ref }} 9 | cancel-in-progress: true 10 | 11 | permissions: {} 12 | 13 | jobs: 14 | build: 15 | name: Build 16 | runs-on: ubuntu-latest 17 | timeout-minutes: 10 18 | steps: 19 | - uses: actions/checkout@v4 20 | - name: Setup pnpm 21 | uses: pnpm/action-setup@v4.0.0 22 | with: 23 | run_install: true 24 | - name: Install Node.js 25 | uses: actions/setup-node@v4 26 | with: 27 | node-version: "latest" 28 | cache: pnpm 29 | registry-url: https://registry.npmjs.org/ 30 | - run: pnpm prepare-docs 31 | - name: Build pages Jekyll 32 | uses: actions/jekyll-build-pages@v1 33 | with: 34 | source: ./docs 35 | destination: ./_site 36 | - name: Upload pages artifact 37 | uses: actions/upload-pages-artifact@v3 38 | 39 | deploy: 40 | name: Deploy 41 | runs-on: ubuntu-latest 42 | timeout-minutes: 10 43 | needs: build 44 | permissions: 45 | pages: write # To deploy to GitHub Pages 46 | id-token: write # To verify the deployment originates from an appropriate source 47 | environment: 48 | name: github-pages 49 | url: ${{ steps.deployment.outputs.page_url }} 50 | steps: 51 | - name: Deploy to GitHub Pages 52 | id: deployment 53 | uses: actions/deploy-pages@v4 54 | -------------------------------------------------------------------------------- /.github/workflows/publish-debug.yml: -------------------------------------------------------------------------------- 1 | # This workflow is used to debug npm publish 2 | 3 | name: publish-debug 4 | 5 | on: 6 | workflow_dispatch: 7 | 8 | jobs: 9 | publish-npm: 10 | runs-on: ubuntu-latest 11 | timeout-minutes: 10 12 | steps: 13 | - name: checkout files 14 | uses: actions/checkout@v4 15 | - name: Install Node.js 16 | uses: actions/setup-node@v4 17 | with: 18 | node-version: "latest" 19 | registry-url: https://registry.npmjs.org/ 20 | - run: mkdir dist 21 | - run: echo '{"name":"@parischap/configs","version":"0.0.19"}' > dist/package.json 22 | - run: npm publish ./dist 23 | env: 24 | NODE_AUTH_TOKEN: ${{secrets.NPM_PUBLISH_TOKEN}} 25 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | # This workflow is triggered by a new release. Based on the release tag that must be in the form version or repo@version, it will create build the target repo, update the version number of the package.json in this repo and publish the repo to npm. 2 | 3 | name: publish 4 | 5 | on: 6 | workflow_dispatch: 7 | release: 8 | types: [created] 9 | 10 | concurrency: 11 | group: ${{ github.workflow }}-${{ github.ref }} 12 | cancel-in-progress: true 13 | 14 | permissions: {} 15 | 16 | jobs: 17 | publish: 18 | runs-on: ubuntu-latest 19 | timeout-minutes: 10 20 | steps: 21 | - name: checkout files 22 | uses: actions/checkout@v4 23 | - name: Setup pnpm 24 | uses: pnpm/action-setup@v4.0.0 25 | with: 26 | run_install: true 27 | - name: Install Node.js 28 | uses: actions/setup-node@v4 29 | with: 30 | node-version: "latest" 31 | cache: pnpm 32 | registry-url: https://registry.npmjs.org/ 33 | - name: Get latest release when entering by workflow_dispatch 34 | run: | 35 | git fetch --prune --unshallow 36 | echo "previous_tag=$(git describe --tags --abbrev=0 2>/dev/null || echo '')" >> $GITHUB_ENV 37 | if: ${{!github.event.release.tag_name}} 38 | - name: Get the target repository and version number 39 | id: determine-repo_and_version 40 | uses: actions/github-script@v7 41 | env: 42 | previous_tag: ${{env.previous_tag}} 43 | with: 44 | script: | 45 | const {previous_tag} = process.env 46 | const tag = context.payload.release?.tag_name??previous_tag; 47 | const pos = tag.lastIndexOf('@'); 48 | const version = tag.substring(pos + 1); 49 | const repo = (pos === -1) ? '.' : 'packages/' + tag.substring(0, pos); 50 | return { 51 | repo, 52 | version, 53 | versionValidity: /^(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)$/.test(version) 54 | }; 55 | - id: get-version-validity 56 | run: echo "versionValidity=${{fromJson(steps.determine-repo_and_version.outputs.result).versionValidity}}" >> $GITHUB_OUTPUT 57 | - name: Fail if version number is ill-formed 58 | if: ${{steps.get-version-validity.outputs.versionValidity == 'false'}} 59 | run: exit 1 60 | - id: get-version 61 | run: echo "version=${{fromJson(steps.determine-repo_and_version.outputs.result).version}}" >> $GITHUB_OUTPUT 62 | - id: get-repo 63 | run: echo "repo=${{fromJson(steps.determine-repo_and_version.outputs.result).repo}}" >> $GITHUB_OUTPUT 64 | - name: Update version number in package.json 65 | id: update-package-json 66 | uses: jaywcjlove/github-action-package@main 67 | with: 68 | path: "${{steps.get-repo.outputs.repo}}/package.json" 69 | version: ${{steps.get-version.outputs.version}} 70 | - name: Build and publish 71 | working-directory: ${{steps.get-repo.outputs.repo}} 72 | env: 73 | NODE_AUTH_TOKEN: ${{ secrets.NPM_PUBLISH_TOKEN }} 74 | run: rm -f tsconfig.json && pnpm build-and-publish 75 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | node_modules/ 3 | vite.config.ts.timestamp-*.mjs 4 | /.tsbuildinfo/ 5 | /*.old -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # Same rules as .git 2 | node_modules/ -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "Launch package-manager", 9 | "program": "${workspaceFolder}/packages/package-manager/dist/main.js", 10 | "request": "launch", 11 | "skipFiles": ["/**"], 12 | "type": "node" 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## EFFECT-LIBS 2 | 3 | effect-libs is a set of open-source libraries to be used in complement to the [Effect](https://effect.website/docs/introduction) library. 4 | 5 | The [pretty-print](docs/pretty-print) module however may be used by non-Effect users. 6 | 7 | Each package contains its own documentation. You may also take a look at our [API](https://parischap.github.io/effect-libs/). 8 | 9 | ## Donate 10 | 11 | [Any donations would be much appreciated](https://ko-fi.com/parischap). 😄 12 | 13 | ## Documentation 14 | 15 | If you need to, take a look at the [documentation](https://parischap.github.io/effect-libs/docs/effect-lib). 16 | -------------------------------------------------------------------------------- /docs/_config.yml: -------------------------------------------------------------------------------- 1 | remote_theme: mikearnaldi/just-the-docs 2 | search_enabled: true 3 | aux_links: 4 | "GitHub": 5 | - "//github.com/parischap/effect-libs" 6 | -------------------------------------------------------------------------------- /docs/index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Introduction 3 | permalink: / 4 | nav_order: 1 5 | has_children: false 6 | has_toc: false 7 | --- 8 | 9 | effect-libs is a set of open-source libraries to be used in complement to the [Effect](https://effect.website/docs/introduction) library. 10 | 11 | The [pretty-print](docs/pretty-print) module however may be used by non-Effect users. 12 | 13 | Each package contains its own documentation. 14 | -------------------------------------------------------------------------------- /effect-libs.code-workspace: -------------------------------------------------------------------------------- 1 | { 2 | "folders": [ 3 | { 4 | "name": "effect-lib", 5 | "path": "packages/effect-lib", 6 | }, 7 | { 8 | "name": "node-effect-lib", 9 | "path": "packages/node-effect-lib", 10 | }, 11 | { 12 | "path": ".", 13 | }, 14 | { 15 | "path": "packages/pretty-print", 16 | }, 17 | { 18 | "path": "packages/date", 19 | }, 20 | { 21 | "path": "packages/ansi-styles", 22 | }, 23 | { 24 | "path": "packages/effect-report", 25 | }, 26 | { 27 | "path": "packages/conversions", 28 | }, 29 | ], 30 | "settings": { 31 | "typescript.tsdk": "effect-libs\\node_modules\\typescript\\lib", 32 | }, 33 | } 34 | -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | import { eslintConfigNode } from '@parischap/configs'; 2 | 3 | export default eslintConfigNode; 4 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@parischap-dev/effect-libs", 3 | "type": "module", 4 | "author": "Jérôme MARTIN", 5 | "license": "MIT", 6 | "scripts": { 7 | "tscheck": "tsc -b tsconfig.check.json --force", 8 | "lint": "eslint .", 9 | "lint-fix": "eslint . --fix", 10 | "lint-rules": "pnpx @eslint/config-inspector", 11 | "update-config-files": "update-config-files", 12 | "clean-config-files": "shx rm -rf package.json && shx rm -rf tsconfig.json", 13 | "update-all-config-files": "pnpm -r -include-workspace-root=true --workspace-concurrency=1 update-config-files", 14 | "clean-all-prod-files": "pnpm -r clean-prod", 15 | "clean-all-config-files": "pnpm -r -include-workspace-root=true clean-config-files", 16 | "build-all": "pnpm -r build", 17 | "prepare-docs": "pnpm --recursive --parallel docgen && compile-docs" 18 | }, 19 | "packageManager": "pnpm@10.11.0", 20 | "devDependencies": { 21 | "globals": "latest", 22 | "ts-deepmerge": "latest", 23 | "@tsconfig/node20": "latest", 24 | "@tsconfig/strictest": "latest", 25 | "shx": "latest", 26 | "@eslint/eslintrc": "latest", 27 | "@eslint/js": "latest", 28 | "@parischap/configs": "latest", 29 | "@parischap/test-utils": "latest", 30 | "@types/eslint": "latest", 31 | "@types/eslint-config-prettier": "latest", 32 | "@types/node": "latest", 33 | "eslint": "latest", 34 | "eslint-config-prettier": "latest", 35 | "eslint-plugin-functional": "latest", 36 | "eslint-plugin-yml": "latest", 37 | "prettier": "latest", 38 | "typescript": "latest", 39 | "typescript-eslint": "latest", 40 | "vite": "latest", 41 | "vite-node": "latest", 42 | "vitest": "latest", 43 | "madge": "latest", 44 | "@babel/core": "latest", 45 | "@babel/plugin-transform-export-namespace-from": "latest", 46 | "@babel/plugin-transform-modules-commonjs": "latest", 47 | "babel-plugin-annotate-pure-calls": "latest", 48 | "@babel/cli": "latest", 49 | "@effect/docgen": "latest", 50 | "prettier-plugin-jsdoc": "latest" 51 | }, 52 | "pnpm": { 53 | "patchedDependencies": {}, 54 | "overrides": {} 55 | }, 56 | "description": "Top repository of monorepo", 57 | "workspaces": [ 58 | "packages/*" 59 | ] 60 | } 61 | -------------------------------------------------------------------------------- /packages/ansi-styles/.gitignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | node_modules/ 3 | vite.config.ts.timestamp-*.mjs 4 | /.tsbuildinfo/ 5 | /*.old -------------------------------------------------------------------------------- /packages/ansi-styles/.madgerc: -------------------------------------------------------------------------------- 1 | { 2 | "detectiveOptions": { 3 | "ts": { 4 | "skipTypeImports": true 5 | } 6 | } 7 | } -------------------------------------------------------------------------------- /packages/ansi-styles/.prettierignore: -------------------------------------------------------------------------------- 1 | # Same rules as .git 2 | node_modules/ -------------------------------------------------------------------------------- /packages/ansi-styles/docgen.json: -------------------------------------------------------------------------------- 1 | { 2 | "parseCompilerOptions": "./tsconfig.docgen.json", 3 | "examplesCompilerOptions": "./tsconfig.docgen.json", 4 | "srcDir": "./esm", 5 | "outDir": "docs", 6 | "exclude": ["esm/index.ts"], 7 | "enforceDescriptions": true, 8 | "enforceVersion": false 9 | } 10 | -------------------------------------------------------------------------------- /packages/ansi-styles/eslint.config.js: -------------------------------------------------------------------------------- 1 | import { eslintConfigLibrary } from '@parischap/configs'; 2 | 3 | export default eslintConfigLibrary; 4 | -------------------------------------------------------------------------------- /packages/ansi-styles/esm/AnsiString.ts: -------------------------------------------------------------------------------- 1 | /** This modules implements a command string that produces an ANSI style, e.g. `\x1b[1m` for bold */ 2 | 3 | import { MFunction, MString, MTypes } from '@parischap/effect-lib'; 4 | import { Array, flow, Option } from 'effect'; 5 | 6 | /** 7 | * Type of an ANSI string 8 | * 9 | * @category Models 10 | */ 11 | export type Type = string; 12 | 13 | /** 14 | * Type that represents the sequence of a command string (the numbers separated by a semicolon 15 | * between `\x1b[` and `m`, e.g. [1,31] for bold red) 16 | * 17 | * @category Models 18 | */ 19 | export interface Sequence extends ReadonlyArray {} 20 | 21 | /** 22 | * Same as Sequence but must constain at least one number 23 | * 24 | * @category Models 25 | */ 26 | export interface NonEmptySequence extends MTypes.ReadonlyOverOne {} 27 | 28 | /** 29 | * Builds an AnsiString from a NonEmptySequence 30 | * 31 | * @category Constructors 32 | */ 33 | export const fromNonEmptySequence: MTypes.OneArgFunction = flow( 34 | Array.map(MString.fromNumber(10)), 35 | Array.join(';'), 36 | MString.prepend('\x1b['), 37 | MString.append('m') 38 | ); 39 | 40 | /** 41 | * Builds an AnsiString from a Sequence 42 | * 43 | * @category Constructors 44 | */ 45 | export const fromSequence: MTypes.OneArgFunction = flow( 46 | Option.liftPredicate(MTypes.isReadonlyOverOne), 47 | Option.map(fromNonEmptySequence), 48 | Option.getOrElse(MFunction.constEmptyString) 49 | ); 50 | 51 | /** 52 | * Empty AnsiString instance 53 | * 54 | * @category Instances 55 | */ 56 | export const empty: Type = ''; 57 | 58 | /** 59 | * Reset AnsiString instance 60 | * 61 | * @category Instances 62 | */ 63 | export const reset: Type = fromNonEmptySequence(Array.of(0)); 64 | -------------------------------------------------------------------------------- /packages/ansi-styles/esm/Styles.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This module implements a Type that represents an array of Style's (see Style.ts). It is used by 3 | * the Palette module (see Palette.ts) 4 | */ 5 | 6 | import { MTypes } from '@parischap/effect-lib'; 7 | import { Array, flow } from 'effect'; 8 | import * as ASStyle from './Style.js'; 9 | 10 | /** 11 | * Type that represents an array of Style's. 12 | * 13 | * @category Models 14 | */ 15 | export type Type = MTypes.OverTwo; 16 | 17 | /** 18 | * Gets the id of `self` 19 | * 20 | * @category Destructors 21 | */ 22 | export const toId: MTypes.OneArgFunction = flow( 23 | Array.map(ASStyle.toId), 24 | Array.join('/') 25 | ); 26 | 27 | /** 28 | * Appends `that` to `self` 29 | * 30 | * @category Utils 31 | */ 32 | export const append = 33 | (that: Type) => 34 | (self: Type): Type => [...self, ...that]; 35 | -------------------------------------------------------------------------------- /packages/ansi-styles/esm/index.ts: -------------------------------------------------------------------------------- 1 | export * as ASAnsiString from './AnsiString.js'; 2 | export * as ASColor from './Color.js'; 3 | export * as ASContextStyler from './ContextStyler.js'; 4 | export * as ASPalette from './Palette.js'; 5 | export * as ASStyle from './Style.js'; 6 | export * as ASStyleCharacteristics from './StyleCharacteristics.js'; 7 | export * as ASStyles from './Styles.js'; 8 | export * as ASText from './Text.js'; 9 | -------------------------------------------------------------------------------- /packages/ansi-styles/examples/all-colors.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable functional/no-expression-statements */ 2 | import { ASColor, ASStyle } from '@parischap/ansi-styles'; 3 | 4 | console.log(ASStyle.color(ASColor.rgbCoral)('I am a coral string')); 5 | console.log( 6 | ASStyle.color(ASColor.Rgb.make({ red: 176, green: 17, blue: 243 }))( 7 | 'I am a string colored with an RGB-user-defined color' 8 | ) 9 | ); 10 | -------------------------------------------------------------------------------- /packages/ansi-styles/examples/basic-usage.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable functional/no-expression-statements */ 2 | import { ASStyle } from '@parischap/ansi-styles'; 3 | 4 | console.log( 5 | ASStyle.red( 6 | 'ansi-styles is an ', 7 | ASStyle.bold( 8 | 'Effect library ', 9 | ASStyle.magenta( 10 | ASStyle.dim('for terminal output styling with '), 11 | ASStyle.yellow('ANSI '), 12 | 'colors ' 13 | ) 14 | ), 15 | 'and formats.' 16 | ) 17 | ); 18 | -------------------------------------------------------------------------------- /packages/ansi-styles/examples/cancelling-a-style.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable functional/no-expression-statements */ 2 | import { ASStyle } from '@parischap/ansi-styles'; 3 | 4 | console.log(ASStyle.bold('I am ', ASStyle.notBold('not bold'))); 5 | -------------------------------------------------------------------------------- /packages/ansi-styles/examples/context-styler.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable functional/no-expression-statements */ 2 | import { ASContextStyler, ASPalette } from '@parischap/ansi-styles'; 3 | 4 | interface Value { 5 | readonly pos1: number; 6 | readonly otherStuff: string; 7 | } 8 | 9 | const red: ASContextStyler.Type = ASContextStyler.red(); 10 | 11 | const pos1 = (value: Value): number => value.pos1; 12 | 13 | const pos1BasedAllColorsFormatter = ASContextStyler.fromPalette({ 14 | indexFromContext: pos1, 15 | palette: ASPalette.allStandardOriginalColors 16 | }); 17 | 18 | const value1: Value = { 19 | pos1: 2, 20 | otherStuff: 'dummy' 21 | }; 22 | const pos1BasedAllColorsFormatterInValue1Context = pos1BasedAllColorsFormatter(value1); 23 | const redInValue1Context = red(value1); 24 | 25 | /* Prints `foo` in red */ 26 | console.log(redInValue1Context('foo')); 27 | 28 | /* Prints `foo` in green */ 29 | console.log(pos1BasedAllColorsFormatterInValue1Context('foo')); 30 | 31 | /* Prints `foo` in green */ 32 | console.log(pos1BasedAllColorsFormatter.withContextLast('foo')(value1)); 33 | -------------------------------------------------------------------------------- /packages/ansi-styles/examples/simple-colors.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable functional/no-expression-statements */ 2 | import { ASStyle } from '@parischap/ansi-styles'; 3 | 4 | console.log( 5 | ASStyle.none( 6 | ASStyle.green('I am '), 7 | ASStyle.Bright.green('in different shades '), 8 | ASStyle.Bg.Bright.green('of green', ASStyle.Bg.defaultColor('.')) 9 | ) 10 | ); 11 | -------------------------------------------------------------------------------- /packages/ansi-styles/prettier.config.js: -------------------------------------------------------------------------------- 1 | import * as Configs from '@parischap/configs'; 2 | 3 | export default Configs.prettierconfigBase; 4 | -------------------------------------------------------------------------------- /packages/ansi-styles/project.config.js: -------------------------------------------------------------------------------- 1 | import * as Configs from '@parischap/configs'; 2 | 3 | export default Configs.configSubRepo({ 4 | description: 5 | 'A functional library to read from (PHP sscanf equivalent) and write to (PHP sprintf equivalent) a string with type checking', 6 | dependencies: {}, 7 | devDependencies: {}, 8 | internalPeerDependencies: { 'effect-lib': Configs.constants.effectLibVersion }, 9 | externalPeerDependencies: { 10 | effect: Configs.constants.effectVersion 11 | }, 12 | examples: ['basic-usage.ts', 'cancelling-a-style.ts', 'simple-colors.ts', 'all-colors.ts'], 13 | scripts: {}, 14 | environment: Configs.Environment.Type.Library, 15 | bundled: false, 16 | visibility: Configs.Visibility.Type.Public, 17 | hasStaticFolder: false, 18 | hasDocGen: true, 19 | keywords: [ 20 | 'terminal', 21 | 'color', 22 | 'colour', 23 | 'colors', 24 | 'formatting', 25 | 'cli', 26 | 'console', 27 | 'string', 28 | 'ansi', 29 | 'style', 30 | 'styles', 31 | 'tty', 32 | 'formatting', 33 | 'rgb256', 34 | 'shell', 35 | 'xterm', 36 | 'log', 37 | 'logging', 38 | 'command-line', 39 | 'text' 40 | ] 41 | }); 42 | -------------------------------------------------------------------------------- /packages/ansi-styles/readme-assets/basic-example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/parischap/effect-libs/2f5a1c71c0c4824de2fb8452b2ece6a5d6c54022/packages/ansi-styles/readme-assets/basic-example.png -------------------------------------------------------------------------------- /packages/ansi-styles/readme-assets/cancelling-a-style.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/parischap/effect-libs/2f5a1c71c0c4824de2fb8452b2ece6a5d6c54022/packages/ansi-styles/readme-assets/cancelling-a-style.png -------------------------------------------------------------------------------- /packages/ansi-styles/readme-assets/simple-colors.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/parischap/effect-libs/2f5a1c71c0c4824de2fb8452b2ece6a5d6c54022/packages/ansi-styles/readme-assets/simple-colors.png -------------------------------------------------------------------------------- /packages/ansi-styles/tests/AnsiString.test.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable functional/no-expression-statements */ 2 | import { ASAnsiString } from '@parischap/ansi-styles'; 3 | import { TEUtils } from '@parischap/test-utils'; 4 | import { Array } from 'effect'; 5 | import { describe, it } from 'vitest'; 6 | 7 | describe('AnsiString', () => { 8 | describe('fromSequence', () => { 9 | it('From empty sequence', () => { 10 | TEUtils.strictEqual(ASAnsiString.fromSequence(Array.empty()), ''); 11 | }); 12 | 13 | it('From non-empty sequence', () => { 14 | TEUtils.strictEqual(ASAnsiString.fromSequence(Array.make(0, 1)), '\x1b[0;1m'); 15 | }); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /packages/ansi-styles/tests/ContextStyler.test.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable functional/no-expression-statements */ 2 | import { ASContextStyler, ASPalette, ASStyle } from '@parischap/ansi-styles'; 3 | import { TEUtils } from '@parischap/test-utils'; 4 | import { describe, it } from 'vitest'; 5 | 6 | describe('ContextStyler', () => { 7 | interface Value { 8 | readonly pos1: number; 9 | readonly otherStuff: string; 10 | } 11 | 12 | const red: ASContextStyler.Type = ASContextStyler.red(); 13 | 14 | const pos1 = (value: Value): number => value.pos1; 15 | 16 | const pos1BasedAllColorsFormatter = ASContextStyler.fromPalette({ 17 | indexFromContext: pos1, 18 | palette: ASPalette.allStandardOriginalColors 19 | }); 20 | 21 | const value1: Value = { 22 | pos1: 2, 23 | otherStuff: 'dummy' 24 | }; 25 | 26 | const value2: Value = { 27 | pos1: 9, 28 | otherStuff: 'dummy' 29 | }; 30 | 31 | const redInValue1Context = red(value1); 32 | const pos1BasedAllColorsFormatterInValue1Context = pos1BasedAllColorsFormatter(value1); 33 | const pos1BasedAllColorsFormatterInValue2Context = pos1BasedAllColorsFormatter(value2); 34 | 35 | describe('Tag, prototype and guards', () => { 36 | it('moduleTag', () => { 37 | TEUtils.assertSome(TEUtils.moduleTagFromTestFilePath(__filename), ASContextStyler.moduleTag); 38 | }); 39 | 40 | describe('Equal.equals', () => { 41 | it('Matching', () => { 42 | TEUtils.assertEquals(red, ASContextStyler.fromSingleStyle(ASStyle.red)); 43 | }); 44 | 45 | it('Non-matching', () => { 46 | TEUtils.assertNotEquals(red, ASContextStyler.black()); 47 | }); 48 | }); 49 | 50 | it('.toString()', () => { 51 | TEUtils.strictEqual(red.toString(), 'RedFormatter'); 52 | TEUtils.strictEqual( 53 | pos1BasedAllColorsFormatter.toString(), 54 | 'Pos1BasedBlack/Red/Green/Yellow/Blue/Magenta/Cyan/WhitePaletteFormatter' 55 | ); 56 | }); 57 | 58 | it('.pipe()', () => { 59 | TEUtils.assertTrue(red.pipe(ASContextStyler.has)); 60 | }); 61 | 62 | describe('has', () => { 63 | it('Matching', () => { 64 | TEUtils.assertTrue(ASContextStyler.has(red)); 65 | TEUtils.assertTrue(ASContextStyler.has(pos1BasedAllColorsFormatter)); 66 | }); 67 | it('Non matching', () => { 68 | TEUtils.assertFalse(ASContextStyler.has(new Date())); 69 | }); 70 | }); 71 | }); 72 | 73 | describe('Action', () => { 74 | it('FromSingleStyle', () => { 75 | TEUtils.assertEquals(redInValue1Context('foo'), ASStyle.red('foo')); 76 | }); 77 | 78 | it('From Palette context first within bounds', () => { 79 | TEUtils.assertEquals(pos1BasedAllColorsFormatterInValue1Context('foo'), ASStyle.green('foo')); 80 | }); 81 | 82 | it('From Palette context first out of bounds', () => { 83 | TEUtils.assertEquals(pos1BasedAllColorsFormatterInValue2Context('foo'), ASStyle.red('foo')); 84 | }); 85 | 86 | it('From Palette context last', () => { 87 | TEUtils.assertEquals( 88 | pos1BasedAllColorsFormatter.withContextLast('foo')(value1), 89 | ASStyle.green('foo') 90 | ); 91 | }); 92 | }); 93 | }); 94 | -------------------------------------------------------------------------------- /packages/ansi-styles/tests/Palette.test.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable functional/no-expression-statements */ 2 | import { ASPalette, ASStyle } from '@parischap/ansi-styles'; 3 | import { TEUtils } from '@parischap/test-utils'; 4 | import { pipe } from 'effect'; 5 | import { describe, it } from 'vitest'; 6 | 7 | describe('ASPalette', () => { 8 | const blackRed = ASPalette.make(ASStyle.black, ASStyle.red); 9 | 10 | describe('Tag, prototype and guards', () => { 11 | it('moduleTag', () => { 12 | TEUtils.assertSome(TEUtils.moduleTagFromTestFilePath(__filename), ASPalette.moduleTag); 13 | }); 14 | 15 | describe('Equal.equals', () => { 16 | it('Matching', () => { 17 | TEUtils.assertEquals(blackRed, ASPalette.make(ASStyle.black, ASStyle.red)); 18 | }); 19 | 20 | it('Non-matching', () => { 21 | TEUtils.assertNotEquals(ASPalette.allOriginalColors, blackRed); 22 | }); 23 | }); 24 | 25 | describe('.toString()', () => { 26 | it('Black and red', () => { 27 | TEUtils.strictEqual(blackRed.toString(), 'Black/RedPalette'); 28 | }); 29 | }); 30 | 31 | it('.pipe()', () => { 32 | TEUtils.assertTrue(blackRed.pipe(ASPalette.has)); 33 | }); 34 | 35 | describe('has', () => { 36 | it('Matching', () => { 37 | TEUtils.assertTrue(ASPalette.has(blackRed)); 38 | }); 39 | it('Non matching', () => { 40 | TEUtils.assertFalse(ASPalette.has(new Date())); 41 | }); 42 | }); 43 | }); 44 | 45 | it('append', () => { 46 | TEUtils.assertEquals( 47 | pipe( 48 | ASPalette.make(ASStyle.black, ASStyle.red, ASStyle.green, ASStyle.yellow), 49 | ASPalette.append(ASPalette.make(ASStyle.blue, ASStyle.magenta, ASStyle.cyan, ASStyle.white)) 50 | ), 51 | ASPalette.allStandardOriginalColors 52 | ); 53 | }); 54 | }); 55 | -------------------------------------------------------------------------------- /packages/ansi-styles/tests/Style.test.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable functional/no-expression-statements */ 2 | import { ASStyle, ASStyleCharacteristics, ASText } from '@parischap/ansi-styles'; 3 | import { TEUtils } from '@parischap/test-utils'; 4 | import { pipe } from 'effect'; 5 | import { describe, it } from 'vitest'; 6 | 7 | describe('ASStyle', () => { 8 | const red = ASStyle.red; 9 | const bold = ASStyle.bold; 10 | const boldRed1 = pipe(red, ASStyle.mergeOver(bold)); 11 | const boldRed2 = pipe(bold, ASStyle.mergeOver(red)); 12 | 13 | describe('Tag, prototype and guards', () => { 14 | it('moduleTag', () => { 15 | TEUtils.assertSome(TEUtils.moduleTagFromTestFilePath(__filename), ASStyle.moduleTag); 16 | }); 17 | 18 | describe('Equal.equals', () => { 19 | it('Matching', () => { 20 | TEUtils.assertEquals(ASStyle.none, ASStyle.none); 21 | TEUtils.assertEquals(boldRed1, boldRed2); 22 | }); 23 | 24 | it('Non-matching', () => { 25 | TEUtils.assertNotEquals(boldRed2, bold); 26 | }); 27 | }); 28 | 29 | describe('.toString()', () => { 30 | it('red before bold', () => { 31 | TEUtils.strictEqual(boldRed1.toString(), 'BoldRed'); 32 | }); 33 | it('bold before red', () => { 34 | TEUtils.strictEqual(boldRed2.toString(), 'BoldRed'); 35 | }); 36 | it('Other than color', () => { 37 | TEUtils.strictEqual(ASStyle.struckThrough.toString(), 'StruckThrough'); 38 | }); 39 | it('Default foreground color', () => { 40 | TEUtils.strictEqual(ASStyle.defaultColor.toString(), 'DefaultColor'); 41 | }); 42 | it('Default background color', () => { 43 | TEUtils.strictEqual(ASStyle.Bg.defaultColor.toString(), 'InDefaultColor'); 44 | }); 45 | }); 46 | 47 | it('.pipe()', () => { 48 | TEUtils.strictEqual(boldRed1.pipe(ASStyle.toId), 'BoldRed'); 49 | }); 50 | 51 | describe('has', () => { 52 | it('Matching', () => { 53 | TEUtils.assertTrue(ASStyle.has(boldRed2)); 54 | }); 55 | it('Non matching', () => { 56 | TEUtils.assertFalse(ASStyle.has(new Date())); 57 | }); 58 | }); 59 | }); 60 | 61 | it('mergeOver', () => { 62 | TEUtils.strictEqual( 63 | pipe( 64 | ASStyle.green, 65 | ASStyle.mergeOver(ASStyle.blinking), 66 | ASStyle.mergeOver(ASStyle.Bright.black) 67 | ).toString(), 68 | 'BlinkingBrightBlack' 69 | ); 70 | }); 71 | 72 | it('mergeUnder', () => { 73 | TEUtils.strictEqual( 74 | pipe( 75 | ASStyle.green, 76 | ASStyle.mergeUnder(ASStyle.blinking), 77 | ASStyle.mergeUnder(ASStyle.Bright.black) 78 | ).toString(), 79 | 'BlinkingGreen' 80 | ); 81 | }); 82 | 83 | it('Action', () => { 84 | TEUtils.assertEquals(bold('foo'), ASText.fromStyleAndElems(ASStyleCharacteristics.bold)('foo')); 85 | }); 86 | }); 87 | -------------------------------------------------------------------------------- /packages/ansi-styles/tsconfig.base.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "_version": "20.1.0", 4 | "extends": [ 5 | "@tsconfig/strictest/tsconfig.json", 6 | "@tsconfig/node20/tsconfig.json" 7 | ], 8 | "compilerOptions": { 9 | "allowJs": false, 10 | "checkJs": false, 11 | "moduleDetection": "force", 12 | "composite": true, 13 | "resolveJsonModule": true, 14 | "esModuleInterop": false, 15 | "declaration": true, 16 | "skipLibCheck": true, 17 | "emitDecoratorMetadata": true, 18 | "experimentalDecorators": true, 19 | "moduleResolution": "NodeNext", 20 | "isolatedModules": true, 21 | "sourceMap": true, 22 | "declarationMap": true, 23 | "noEmitOnError": false, 24 | "noErrorTruncation": true, 25 | "target": "ES2022", 26 | "module": "NodeNext", 27 | "incremental": true, 28 | "removeComments": false, 29 | "lib": [], 30 | "types": [], 31 | "plugins": [{ "name": "@effect/language-service" }] 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /packages/ansi-styles/tsconfig.check.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.esm.json", 3 | "compilerOptions": { 4 | "noEmit": true, 5 | "tsBuildInfoFile": ".tsbuildinfo/check.tsbuildinfo" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/ansi-styles/tsconfig.docgen.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.base.json", 3 | "include": ["**/*.ts", "**/*.mts", "**/*.cts"], 4 | "compilerOptions": { "noEmit": true, "lib": ["ESNext"], "types": ["node"] } 5 | } 6 | -------------------------------------------------------------------------------- /packages/ansi-styles/tsconfig.esm.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.base.json", 3 | "include": ["esm/**/*.ts", "esm/**/*.mts", "esm/**/*.cts"], 4 | "compilerOptions": { 5 | "rootDir": "esm", 6 | "tsBuildInfoFile": ".tsbuildinfo/esm.tsbuildinfo", 7 | "declarationDir": "dist/dts", 8 | "declarationMap": true, 9 | "outDir": "dist/esm", 10 | "lib": ["ESNext"] 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /packages/ansi-styles/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.base.json", 3 | "include": [], 4 | "references": [ 5 | { "path": "./tsconfig.esm.json" }, 6 | { "path": "./tsconfig.others.json" } 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /packages/ansi-styles/tsconfig.others.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.base.json", 3 | "include": [ 4 | "**/*.ts", 5 | "**/*.mts", 6 | "**/*.cts", 7 | "**/*.js", 8 | "**/*.mjs", 9 | "**/*.cjs" 10 | ], 11 | "exclude": [ 12 | "esm/**/*.ts", 13 | "esm/**/*.mts", 14 | "esm/**/*.cts", 15 | "**/node_modules/**/*.ts", 16 | "**/node_modules/**/*.mts", 17 | "**/node_modules/**/*.cts", 18 | "**/node_modules/**/*.js", 19 | "**/node_modules/**/*.mjs", 20 | "**/node_modules/**/*.cjs", 21 | "dist/**/*.ts", 22 | "dist/**/*.mts", 23 | "dist/**/*.cts", 24 | "dist/**/*.js", 25 | "dist/**/*.mjs", 26 | "dist/**/*.cjs", 27 | "vite.config.ts.timestamp-*.mjs" 28 | ], 29 | "compilerOptions": { 30 | "rootDir": ".", 31 | "tsBuildInfoFile": ".tsbuildinfo/others.tsbuildinfo", 32 | "outDir": "dist/others", 33 | "lib": ["ESNext"], 34 | "types": ["node"], 35 | "allowJs": true, 36 | "checkJs": true 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /packages/ansi-styles/vite.config.ts: -------------------------------------------------------------------------------- 1 | import type { UserConfig } from 'vite'; 2 | 3 | export default { 4 | test: { 5 | include: ['./tests/*.test.ts'], 6 | isolate: false, 7 | fileParallelism: false, 8 | pool: 'threads' 9 | } 10 | } satisfies UserConfig; 11 | -------------------------------------------------------------------------------- /packages/conversions/.gitignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | node_modules/ 3 | vite.config.ts.timestamp-*.mjs 4 | /.tsbuildinfo/ 5 | /*.old -------------------------------------------------------------------------------- /packages/conversions/.madgerc: -------------------------------------------------------------------------------- 1 | { 2 | "detectiveOptions": { 3 | "ts": { 4 | "skipTypeImports": true 5 | } 6 | } 7 | } -------------------------------------------------------------------------------- /packages/conversions/.prettierignore: -------------------------------------------------------------------------------- 1 | # Same rules as .git 2 | node_modules/ -------------------------------------------------------------------------------- /packages/conversions/docgen.json: -------------------------------------------------------------------------------- 1 | { 2 | "parseCompilerOptions": "./tsconfig.docgen.json", 3 | "examplesCompilerOptions": "./tsconfig.docgen.json", 4 | "srcDir": "./esm", 5 | "outDir": "docs", 6 | "exclude": ["esm/index.ts"], 7 | "enforceDescriptions": true, 8 | "enforceVersion": false 9 | } 10 | -------------------------------------------------------------------------------- /packages/conversions/eslint.config.js: -------------------------------------------------------------------------------- 1 | import { eslintConfigLibrary } from '@parischap/configs'; 2 | 3 | export default eslintConfigLibrary; 4 | -------------------------------------------------------------------------------- /packages/conversions/esm/Email.ts: -------------------------------------------------------------------------------- 1 | /** A module that implements an Email brand */ 2 | 3 | import { MString } from '@parischap/effect-lib'; 4 | import { Brand } from 'effect'; 5 | 6 | /** 7 | * Module tag 8 | * 9 | * @category Module tag 10 | */ 11 | export const moduleTag = '@parischap/conversions/Email/'; 12 | 13 | export const TypeId: unique symbol = Symbol.for(moduleTag) as _TypeId; 14 | type _TypeId = typeof TypeId; 15 | 16 | /** 17 | * Email type 18 | * 19 | * @category Models 20 | */ 21 | export type Type = Brand.Branded; 22 | 23 | /** 24 | * Constructs an Email without any verifications 25 | * 26 | * @category Constructors 27 | */ 28 | export const unsafeFromString = Brand.nominal(); 29 | 30 | /** 31 | * Constructs an Email from a string. Throws an error if the provided string does not represent an 32 | * email 33 | * 34 | * @category Constructors 35 | */ 36 | export const constructor = Brand.refined(MString.isEmail, (s) => 37 | Brand.error(`'${s}' does not represent a email`) 38 | ); 39 | 40 | /** 41 | * Constructs an Either of an Email from a string. 42 | * 43 | * @category Constructors 44 | */ 45 | export const fromString = constructor.either.bind(constructor); 46 | 47 | /** 48 | * Checks if a string is an email 49 | * 50 | * @category Refinement 51 | */ 52 | export const has = (input: string): input is Type => MString.isEmail(input); 53 | -------------------------------------------------------------------------------- /packages/conversions/esm/Int.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * A module that implements an integer brand (non-null fractional part disallowed). Not exported. 3 | * Only used internally 4 | */ 5 | 6 | import { MNumber, MTypes } from '@parischap/effect-lib'; 7 | import { Brand, Either } from 'effect'; 8 | 9 | /** 10 | * Module tag 11 | * 12 | * @category Module tag 13 | */ 14 | export const moduleTag = '@parischap/conversions/Int/'; 15 | 16 | export const TypeId: unique symbol = Symbol.for(moduleTag) as _TypeId; 17 | type _TypeId = typeof TypeId; 18 | 19 | /** 20 | * Int type 21 | * 22 | * @category Models 23 | */ 24 | export type Type = Brand.Branded; 25 | 26 | /** 27 | * Constructs an Int from a number. Throws if the number is not an integer 28 | * 29 | * @category Constructors 30 | */ 31 | export const constructor = Brand.refined(MNumber.isInt, (n) => 32 | Brand.error(`'${n}' does not represent an integer`) 33 | ); 34 | 35 | /** 36 | * Constructs an Either of an Int from a number. 37 | * 38 | * @category Constructors 39 | */ 40 | export const fromNumber: MTypes.OneArgFunction< 41 | number, 42 | Either.Either 43 | > = constructor.either.bind(constructor); 44 | -------------------------------------------------------------------------------- /packages/conversions/esm/Positive.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * A module that implements a positive number brand (negative number disallowed). Not exported. Only 3 | * used internally 4 | */ 5 | 6 | import { Brand, Number } from 'effect'; 7 | 8 | /** 9 | * Module tag 10 | * 11 | * @category Module tag 12 | */ 13 | export const moduleTag = '@parischap/conversions/Positive/'; 14 | 15 | export const TypeId: unique symbol = Symbol.for(moduleTag) as _TypeId; 16 | type _TypeId = typeof TypeId; 17 | 18 | /** 19 | * Positive type 20 | * 21 | * @category Models 22 | */ 23 | export type Type = Brand.Branded; 24 | 25 | /** 26 | * Constructs a Positive from a number. Throws if the number is not an integer 27 | * 28 | * @category Constructors 29 | */ 30 | export const constructor = Brand.refined(Number.greaterThanOrEqualTo(0), (n) => 31 | Brand.error(`'${n}' is not positive`) 32 | ); 33 | 34 | /** 35 | * Constructs an Either of a Positive from a number. 36 | * 37 | * @category Constructors 38 | */ 39 | export const fromNumber = constructor.either.bind(constructor); 40 | -------------------------------------------------------------------------------- /packages/conversions/esm/PositiveReal.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This module implements a positive finite number brand (NaN, Infinity and negative numbers 3 | * disallowed). Useful for a price, a physical quantity... 4 | */ 5 | 6 | import { MTypes } from '@parischap/effect-lib'; 7 | import { BigDecimal, Brand, Either, flow, Function } from 'effect'; 8 | import * as CVPositive from './Positive.js'; 9 | import type * as CVPositiveRealInt from './PositiveRealInt.js'; 10 | import * as CVReal from './Real.js'; 11 | 12 | /** 13 | * Module tag 14 | * 15 | * @category Module tag 16 | */ 17 | export const moduleTag = '@parischap/conversions/PositiveReal/'; 18 | 19 | /** 20 | * Constructs a PositiveReal from a number. Throws if the number is not a finite positive integer 21 | * 22 | * @category Constructors 23 | */ 24 | export const constructor = Brand.all(CVReal.constructor, CVPositive.constructor); 25 | 26 | /** 27 | * PositiveReal type 28 | * 29 | * @category Models 30 | */ 31 | export type Type = Brand.Brand.FromConstructor; 32 | 33 | /** 34 | * Constructs an Either of a PositiveReal from a number. Constructs an Either of a PositiveReal from 35 | * a number. 36 | * 37 | * @category Constructors 38 | */ 39 | export const fromNumber = constructor.either.bind(constructor); 40 | 41 | /** 42 | * Constructs a PositiveReal from a number without any verifications 43 | * 44 | * @category Constructors 45 | */ 46 | export const unsafeFromNumber = Brand.nominal(); 47 | 48 | /** 49 | * Constructs an Either of a PositiveReal from a BigDecimal. 50 | * 51 | * @category Constructors 52 | */ 53 | export const fromBigDecimal: MTypes.OneArgFunction< 54 | BigDecimal.BigDecimal, 55 | Either.Either 56 | > = flow(CVReal.fromBigDecimal, Either.flatMap(CVPositive.fromNumber)) as never; 57 | 58 | /** 59 | * Constructs an Either of a PositiveReal from a bigint. 60 | * 61 | * @category Constructors 62 | */ 63 | export const fromBigInt: MTypes.OneArgFunction< 64 | bigint, 65 | Either.Either 66 | > = flow(CVReal.fromBigInt, Either.flatMap(CVPositive.fromNumber)) as never; 67 | 68 | /** 69 | * Constructs an Either of a PositiveReal from a Real 70 | * 71 | * @category Constructors 72 | */ 73 | export const fromReal: MTypes.OneArgFunction< 74 | CVReal.Type, 75 | Either.Either 76 | > = CVPositive.fromNumber as never; 77 | 78 | /** 79 | * Constructs an Either of a PositiveReal from a PositiveRealInt. 80 | * 81 | * @category Constructors 82 | */ 83 | export const fromPositiveRealInt: MTypes.OneArgFunction< 84 | CVPositiveRealInt.Type, 85 | Either.Either 86 | > = Function.identity as never; 87 | -------------------------------------------------------------------------------- /packages/conversions/esm/PositiveRealInt.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This module implements a positive finite integer brand. Useful for an age, a quantity, a street 3 | * number, hours, muinutes... 4 | */ 5 | 6 | import { MTypes } from '@parischap/effect-lib'; 7 | import { BigDecimal, Brand, Either, flow } from 'effect'; 8 | import * as CVInt from './Int.js'; 9 | import * as CVPositive from './Positive.js'; 10 | import * as CVPositiveReal from './PositiveReal.js'; 11 | import type * as CVReal from './Real.js'; 12 | import * as CVRealInt from './RealInt.js'; 13 | 14 | /** 15 | * Module tag 16 | * 17 | * @category Module tag 18 | */ 19 | export const moduleTag = '@parischap/conversions/PositiveRealInt/'; 20 | 21 | /** 22 | * Constructs a PositiveRealInt from a number. Throws if the number is not a finite positive integer 23 | * 24 | * @category Constructors 25 | */ 26 | export const constructor = Brand.all(CVRealInt.constructor, CVPositive.constructor); 27 | 28 | /** 29 | * PositiveRealInt type 30 | * 31 | * @category Models 32 | */ 33 | export type Type = Brand.Brand.FromConstructor; 34 | 35 | /** 36 | * Constructs an Either of a PositiveRealInt from a number. 37 | * 38 | * @category Constructors 39 | */ 40 | export const fromNumber = constructor.either.bind(constructor); 41 | 42 | /** 43 | * Constructs a PositiveRealInt from a number without any verifications 44 | * 45 | * @category Constructors 46 | */ 47 | export const unsafeFromNumber = Brand.nominal(); 48 | 49 | /** 50 | * Constructs an Either of a PositiveRealInt from a BigDecimal. 51 | * 52 | * @category Constructors 53 | */ 54 | export const fromBigDecimal: MTypes.OneArgFunction< 55 | BigDecimal.BigDecimal, 56 | Either.Either 57 | > = flow(CVPositiveReal.fromBigDecimal, Either.flatMap(CVInt.fromNumber)) as never; 58 | 59 | /** 60 | * Constructs an Either of a PositiveRealInt from a bigint. 61 | * 62 | * @category Constructors 63 | */ 64 | export const fromBigInt: MTypes.OneArgFunction< 65 | bigint, 66 | Either.Either 67 | > = CVPositiveReal.fromBigInt as never; 68 | 69 | /** 70 | * Constructs an Either of a PositiveRealInt from a Real 71 | * 72 | * @category Constructors 73 | */ 74 | export const fromReal: MTypes.OneArgFunction< 75 | CVReal.Type, 76 | Either.Either 77 | > = flow(CVPositive.fromNumber, Either.flatMap(CVInt.fromNumber)) as never; 78 | 79 | /** 80 | * Constructs an Either of a PositiveRealInt from a RealInt 81 | * 82 | * @category Constructors 83 | */ 84 | export const fromRealInt: MTypes.OneArgFunction< 85 | CVRealInt.Type, 86 | Either.Either 87 | > = CVPositive.fromNumber as never; 88 | 89 | /** 90 | * Constructs an Either of a PositiveRealInt from a PositiveReal 91 | * 92 | * @category Constructors 93 | */ 94 | export const fromPositiveReal: MTypes.OneArgFunction< 95 | CVPositiveReal.Type, 96 | Either.Either 97 | > = CVInt.fromNumber as never; 98 | -------------------------------------------------------------------------------- /packages/conversions/esm/Real.ts: -------------------------------------------------------------------------------- 1 | /** This module implements a finite number brand (Infinity or Nan disallowed) */ 2 | 3 | import { MBigDecimal, MBigInt, MNumber, MTypes } from '@parischap/effect-lib'; 4 | import { BigDecimal, BigInt, Brand, Either, flow } from 'effect'; 5 | import * as CVInt from './Int.js'; 6 | 7 | /** 8 | * Module tag 9 | * 10 | * @category Module tag 11 | */ 12 | export const moduleTag = '@parischap/conversions/Real/'; 13 | 14 | export const TypeId: unique symbol = Symbol.for(moduleTag) as _TypeId; 15 | type _TypeId = typeof TypeId; 16 | 17 | /** 18 | * Real type 19 | * 20 | * @category Models 21 | */ 22 | export type Type = Brand.Branded; 23 | 24 | /** 25 | * Constructs a Real from a number without any verifications 26 | * 27 | * @category Constructors 28 | */ 29 | export const unsafeFromNumber = Brand.nominal(); 30 | 31 | /** 32 | * Constructs a Real from a number. Throws an error if the provided number does not represent a 33 | * finite number 34 | * 35 | * @category Constructors 36 | */ 37 | export const constructor = Brand.refined(MNumber.isFinite, (n) => 38 | Brand.error(`'${n}' does not represent a finite number`) 39 | ); 40 | 41 | /** 42 | * Constructs an Either of a Real from a number. 43 | * 44 | * @category Constructors 45 | */ 46 | export const fromNumber: MTypes.OneArgFunction< 47 | number, 48 | Either.Either 49 | > = constructor.either.bind(constructor); 50 | 51 | /** 52 | * Constructs an Either of a Real from a BigDecimal. 53 | * 54 | * @category Constructors 55 | */ 56 | export const fromBigDecimal: MTypes.OneArgFunction< 57 | BigDecimal.BigDecimal, 58 | Either.Either 59 | > = flow( 60 | MBigDecimal.toNumber, 61 | Either.fromOption(() => Brand.error('BigDecimal too big to be converted to number')) 62 | ) as never; 63 | 64 | /** 65 | * Constructs an Either of a Real from a bigint. 66 | * 67 | * @category Constructors 68 | */ 69 | export const fromBigInt: MTypes.OneArgFunction< 70 | bigint, 71 | Either.Either 72 | > = flow( 73 | BigInt.toNumber, 74 | Either.fromOption(() => Brand.error('BigInt too big to be converted to number')) 75 | ) as never; 76 | 77 | /** 78 | * Constructs a BigDecimal from a Real. 79 | * 80 | * @category Destructors 81 | */ 82 | export const toBigDecimal: MTypes.OneArgFunction = 83 | BigDecimal.unsafeFromNumber; 84 | 85 | /** 86 | * Constructs an Either of a bigint from a Real. 87 | * 88 | * @category Destructors 89 | */ 90 | export const toBigInt: MTypes.OneArgFunction< 91 | Type, 92 | Either.Either 93 | > = flow(CVInt.fromNumber, Either.map(MBigInt.unsafeFromNumber)); 94 | -------------------------------------------------------------------------------- /packages/conversions/esm/RealInt.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This module implements a finite integer brand (Infinity, NaN or non-null fractional part 3 | * disallowed) 4 | */ 5 | 6 | import { MBigInt, MTypes } from '@parischap/effect-lib'; 7 | import { BigDecimal, Brand, Either, flow } from 'effect'; 8 | import * as CVInt from './Int.js'; 9 | import * as CVReal from './Real.js'; 10 | 11 | /** 12 | * Module tag 13 | * 14 | * @category Module tag 15 | */ 16 | export const moduleTag = '@parischap/conversions/RealInt/'; 17 | 18 | /** 19 | * Constructs a RealInt from a number. Throws if the number is not a finite integer 20 | * 21 | * @category Constructors 22 | */ 23 | export const constructor = Brand.all(CVReal.constructor, CVInt.constructor); 24 | 25 | /** 26 | * RealInt type 27 | * 28 | * @category Models 29 | */ 30 | export type Type = Brand.Brand.FromConstructor; 31 | 32 | /** 33 | * Constructs an Either of a RealInt from a number. 34 | * 35 | * @category Constructors 36 | */ 37 | export const fromNumber = constructor.either.bind(constructor); 38 | 39 | /** 40 | * Constructs a RealInt from a number without any verifications 41 | * 42 | * @category Constructors 43 | */ 44 | export const unsafeFromNumber = Brand.nominal(); 45 | 46 | /** 47 | * Constructs an Either of a RealInt from a BigDecimal. 48 | * 49 | * @category Constructors 50 | */ 51 | export const fromBigDecimal: MTypes.OneArgFunction< 52 | BigDecimal.BigDecimal, 53 | Either.Either 54 | > = flow(CVReal.fromBigDecimal, Either.flatMap(CVInt.fromNumber)) as never; 55 | 56 | /** 57 | * Constructs an Either of a RealInt from a bigint. 58 | * 59 | * @category Constructors 60 | */ 61 | export const fromBigInt: MTypes.OneArgFunction< 62 | bigint, 63 | Either.Either 64 | > = CVReal.fromBigInt as never; 65 | 66 | /** 67 | * Constructs a BigDecimal from a RealInt. 68 | * 69 | * @category Destructors 70 | */ 71 | export const toBigDecimal: MTypes.OneArgFunction = 72 | BigDecimal.unsafeFromNumber; 73 | 74 | /** 75 | * Constructs a bigint from a RealInt. 76 | * 77 | * @category Destructors 78 | */ 79 | export const toBigInt: MTypes.OneArgFunction = MBigInt.unsafeFromNumber; 80 | 81 | /** 82 | * Constructs an Either of a RealInt from a Real. 83 | * 84 | * @category Constructors 85 | */ 86 | export const fromReal: MTypes.OneArgFunction< 87 | CVReal.Type, 88 | Either.Either 89 | > = CVInt.fromNumber as never; 90 | -------------------------------------------------------------------------------- /packages/conversions/esm/SemVer.ts: -------------------------------------------------------------------------------- 1 | /** A module that implements a SemVer brand */ 2 | 3 | import { MString } from '@parischap/effect-lib'; 4 | import { Brand } from 'effect'; 5 | 6 | /** 7 | * Module tag 8 | * 9 | * @category Module tag 10 | */ 11 | export const moduleTag = '@parischap/conversions/SemVer/'; 12 | 13 | export const TypeId: unique symbol = Symbol.for(moduleTag) as _TypeId; 14 | type _TypeId = typeof TypeId; 15 | 16 | /** 17 | * SemVer type 18 | * 19 | * @category Models 20 | */ 21 | export type Type = Brand.Branded; 22 | 23 | /** 24 | * Constructs a SemVer without any verifications 25 | * 26 | * @category Constructors 27 | */ 28 | export const unsafeFromString = Brand.nominal(); 29 | 30 | /** 31 | * Constructs a SemVer from a string. Throws an error if the provided string does not represent a 32 | * semver 33 | * 34 | * @category Constructors 35 | */ 36 | export const constructor = Brand.refined(MString.isSemVer, (s) => 37 | Brand.error(`'${s}' does not represent a semver`) 38 | ); 39 | 40 | /** 41 | * Constructs an Either of a SemVer from a string. 42 | * 43 | * @category Constructors 44 | */ 45 | export const fromString = constructor.either.bind(constructor); 46 | -------------------------------------------------------------------------------- /packages/conversions/esm/index.ts: -------------------------------------------------------------------------------- 1 | export * as CVDateTime from './DateTime.js'; 2 | export * as CVEmail from './Email.js'; 3 | export * as CVNumberBase10Format from './NumberBase10Format.js'; 4 | export * as CVPlaceHolder from './PlaceHolder.js'; 5 | export * as CVPositiveReal from './PositiveReal.js'; 6 | export * as CVPositiveRealInt from './PositiveRealInt.js'; 7 | export * as CVReal from './Real.js'; 8 | export * as CVRealInt from './RealInt.js'; 9 | export * as CVRoundingMode from './RoundingMode.js'; 10 | export * as CVRoundingOption from './RoundingOption.js'; 11 | export * as CVSchema from './Schema.js'; 12 | export * as CVSemVer from './SemVer.js'; 13 | export * as CVTemplate from './Template.js'; 14 | -------------------------------------------------------------------------------- /packages/conversions/examples/stupid.ts: -------------------------------------------------------------------------------- 1 | import { Transformer } from '@parischap/templater'; 2 | import { pipe } from 'effect'; 3 | 4 | const unsignedInt = Transformer.unsignedInt('.'); 5 | const a = pipe('107.485foo and bar', unsignedInt.read); 6 | console.log(a); 7 | -------------------------------------------------------------------------------- /packages/conversions/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@parischap-dev/conversions", 3 | "type": "module", 4 | "author": "Jérôme MARTIN", 5 | "license": "MIT", 6 | "scripts": { 7 | "tscheck": "tsc -b tsconfig.check.json --force", 8 | "lint": "eslint .", 9 | "lint-fix": "eslint . --fix", 10 | "lint-rules": "pnpx @eslint/config-inspector", 11 | "update-config-files": "update-config-files", 12 | "clean-config-files": "shx rm -rf package.json && shx rm -rf tsconfig.json", 13 | "transpile-esm": "tsc -b tsconfig.esm.json", 14 | "transpile-cjs": "babel dist/esm --plugins @babel/transform-export-namespace-from --plugins @babel/transform-modules-commonjs --out-dir dist/cjs --source-maps", 15 | "transpile-annotate": "babel dist --plugins annotate-pure-calls --out-dir dist --source-maps", 16 | "compile": "pnpm transpile-esm && pnpm transpile-cjs && pnpm transpile-annotate && pnpm prodify", 17 | "build-and-publish": "pnpm build && pnpm checks && pnpm publish-to-npm", 18 | "docgen": "docgen", 19 | "circular": "madge --extensions ts --circular --no-color --no-spinner esm", 20 | "checks": "pnpm circular && pnpm lint && pnpm tscheck && pnpm test", 21 | "test": "vitest run", 22 | "clean-prod": "shx rm -rf dist && shx rm -rf .tsbuildinfo && shx mkdir -p dist", 23 | "publish-to-npm": "cd dist && npm publish --access=public && cd ..", 24 | "install-prod": "cd dist && pnpm i && cd ..", 25 | "build": "pnpm clean-prod && pnpm --if-present pre-build && pnpm compile && pnpm --if-present post-build && pnpm --if-present generate-types && pnpm install-prod", 26 | "prodify": "prodify", 27 | "examples": "vite-node examples/stupid.ts" 28 | }, 29 | "publishConfig": { 30 | "main": "./cjs/index.js", 31 | "types": "./dts/index.d.ts", 32 | "exports": { 33 | ".": { 34 | "default": "./cjs/index.js", 35 | "import": "./esm/index.js", 36 | "types": "./dts/index.d.ts" 37 | } 38 | }, 39 | "scripts": {}, 40 | "devDependencies": {}, 41 | "peerDependencies": { 42 | "@parischap/effect-lib": "^0.5.0", 43 | "effect": "^3.15.4" 44 | }, 45 | "publishConfig": {}, 46 | "packageManager": "", 47 | "pnpm": {}, 48 | "type": "" 49 | }, 50 | "bugs": { 51 | "url": "https://github.com/parischap/effect-libs/issues" 52 | }, 53 | "funding": [ 54 | { 55 | "type": "ko-fi", 56 | "url": "https://ko-fi.com/parischap" 57 | } 58 | ], 59 | "keywords": [ 60 | "number", 61 | "text", 62 | "conversion", 63 | "n2t", 64 | "num2text", 65 | "convert", 66 | "typescript", 67 | "effect", 68 | "effect", 69 | "typescript", 70 | "functional-programming" 71 | ], 72 | "description": "A functional library to convert number and dates to string and vice-versa", 73 | "module": "./esm/index.js", 74 | "exports": { 75 | ".": { 76 | "import": "./esm/index.ts" 77 | } 78 | }, 79 | "dependencies": {}, 80 | "devDependencies": { 81 | "@parischap/conversions": "link:." 82 | }, 83 | "peerDependencies": { 84 | "@parischap/effect-lib": "workspace:@parischap-dev/effect-lib@*", 85 | "effect": "^3.15.4" 86 | }, 87 | "repository": { 88 | "type": "git", 89 | "url": "git+https://github.com/parischap/effect-libs.git", 90 | "directory": "packages/conversions" 91 | }, 92 | "homepage": "https://github.com/parischap/effect-libs/tree/master/packages/conversions" 93 | } 94 | -------------------------------------------------------------------------------- /packages/conversions/prettier.config.js: -------------------------------------------------------------------------------- 1 | import * as Configs from '@parischap/configs'; 2 | 3 | export default Configs.prettierconfigBase; 4 | -------------------------------------------------------------------------------- /packages/conversions/project.config.js: -------------------------------------------------------------------------------- 1 | import * as Configs from '@parischap/configs'; 2 | 3 | export default Configs.configSubRepo({ 4 | description: 'A functional library to convert number and dates to string and vice-versa', 5 | environment: Configs.Environment.Type.Library, 6 | dependencies: {}, 7 | devDependencies: {}, 8 | internalPeerDependencies: { 'effect-lib': Configs.constants.effectLibVersion }, 9 | externalPeerDependencies: { 10 | effect: Configs.constants.effectVersion 11 | }, 12 | examples: ['stupid.ts'], 13 | scripts: {}, 14 | bundled: false, 15 | visibility: Configs.Visibility.Type.Public, 16 | hasStaticFolder: false, 17 | hasDocGen: true, 18 | keywords: ['number', 'text', 'conversion', 'n2t', 'num2text', 'convert', 'typescript', 'effect'] 19 | }); 20 | -------------------------------------------------------------------------------- /packages/conversions/tests/Email.test.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable functional/no-expression-statements */ 2 | import { CVEmail } from '@parischap/conversions'; 3 | import { TEUtils } from '@parischap/test-utils'; 4 | import { Either, pipe } from 'effect'; 5 | import { describe, it } from 'vitest'; 6 | 7 | describe('CVEmail', () => { 8 | it('moduleTag', () => { 9 | TEUtils.assertSome(TEUtils.moduleTagFromTestFilePath(__filename), CVEmail.moduleTag); 10 | }); 11 | 12 | it('unsafeFromString', () => { 13 | TEUtils.strictEqual(CVEmail.unsafeFromString('foo'), 'foo'); 14 | }); 15 | 16 | describe('fromString', () => { 17 | it('Not passing', () => { 18 | TEUtils.assertTrue(pipe('foo', CVEmail.fromString, Either.isLeft)); 19 | }); 20 | it('Passing', () => { 21 | TEUtils.assertTrue(pipe('foo@bar.baz', CVEmail.fromString, Either.isRight)); 22 | }); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /packages/conversions/tests/PositiveReal.test.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable functional/no-expression-statements */ 2 | import { CVPositiveReal, CVPositiveRealInt, CVReal, CVRealInt } from '@parischap/conversions'; 3 | import { TEUtils } from '@parischap/test-utils'; 4 | import { BigDecimal, Either, pipe } from 'effect'; 5 | import { describe, it } from 'vitest'; 6 | 7 | describe('CVPositiveReal', () => { 8 | it('moduleTag', () => { 9 | TEUtils.assertSome(TEUtils.moduleTagFromTestFilePath(__filename), CVPositiveReal.moduleTag); 10 | }); 11 | 12 | it('unsafeFromNumber', () => { 13 | TEUtils.strictEqual(CVPositiveReal.unsafeFromNumber(NaN), NaN); 14 | TEUtils.strictEqual(CVPositiveReal.unsafeFromNumber(15.4), 15.4); 15 | }); 16 | 17 | describe('fromNumber', () => { 18 | it('Not passing: not finite', () => { 19 | TEUtils.assertTrue(pipe(NaN, CVPositiveReal.fromNumber, Either.isLeft)); 20 | }); 21 | it('Not passing: not positive', () => { 22 | TEUtils.assertTrue(pipe(-18, CVPositiveReal.fromNumber, Either.isLeft)); 23 | }); 24 | it('Passing', () => { 25 | TEUtils.assertTrue(pipe(18, CVPositiveReal.fromNumber, Either.isRight)); 26 | }); 27 | }); 28 | 29 | describe('fromBigDecimal', () => { 30 | it('Not passing: too big', () => { 31 | TEUtils.assertTrue( 32 | pipe(BigDecimal.make(1n, -25), CVPositiveReal.fromBigDecimal, Either.isLeft) 33 | ); 34 | }); 35 | it('Not passing: not positive', () => { 36 | TEUtils.assertTrue( 37 | pipe(BigDecimal.make(-1543367754n, 2), CVPositiveReal.fromBigDecimal, Either.isLeft) 38 | ); 39 | }); 40 | it('Passing', () => { 41 | TEUtils.assertTrue( 42 | pipe(BigDecimal.make(1543367754n, 2), CVPositiveReal.fromBigDecimal, Either.isRight) 43 | ); 44 | }); 45 | }); 46 | 47 | describe('fromBigInt', () => { 48 | it('Not passing: too big', () => { 49 | TEUtils.assertTrue( 50 | pipe(BigInt(1e15) * BigInt(1e15), CVPositiveReal.fromBigInt, Either.isLeft) 51 | ); 52 | }); 53 | it('Not passing: not positive', () => { 54 | TEUtils.assertTrue(pipe(BigInt(-1e15), CVPositiveReal.fromBigInt, Either.isLeft)); 55 | }); 56 | it('Passing', () => { 57 | TEUtils.assertTrue(pipe(BigInt(1e15), CVPositiveReal.fromBigInt, Either.isRight)); 58 | }); 59 | }); 60 | 61 | describe('fromReal', () => { 62 | it('Not passing: not positive', () => { 63 | TEUtils.assertTrue( 64 | pipe(-15.4, CVReal.unsafeFromNumber, CVPositiveReal.fromReal, Either.isLeft) 65 | ); 66 | }); 67 | it('Passing', () => { 68 | pipe(15.4, CVReal.unsafeFromNumber, CVPositiveReal.fromReal, Either.isRight); 69 | pipe(-15, CVRealInt.unsafeFromNumber, CVPositiveReal.fromReal, Either.isRight); 70 | pipe(15, CVPositiveRealInt.unsafeFromNumber, CVPositiveReal.fromReal, Either.isRight); 71 | }); 72 | }); 73 | 74 | describe('fromPositiveRealInt', () => { 75 | it('Passing', () => { 76 | pipe( 77 | 15, 78 | CVPositiveRealInt.unsafeFromNumber, 79 | CVPositiveReal.fromPositiveRealInt, 80 | Either.isRight 81 | ); 82 | }); 83 | }); 84 | }); 85 | -------------------------------------------------------------------------------- /packages/conversions/tests/Real.test.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable functional/no-expression-statements */ 2 | import { CVPositiveReal, CVReal } from '@parischap/conversions'; 3 | import { TEUtils } from '@parischap/test-utils'; 4 | import { BigDecimal, Either, pipe } from 'effect'; 5 | import { describe, it } from 'vitest'; 6 | 7 | describe('CVReal', () => { 8 | it('moduleTag', () => { 9 | TEUtils.assertSome(TEUtils.moduleTagFromTestFilePath(__filename), CVReal.moduleTag); 10 | }); 11 | 12 | it('unsafeFromNumber', () => { 13 | TEUtils.strictEqual(CVReal.unsafeFromNumber(NaN), NaN); 14 | TEUtils.strictEqual(CVReal.unsafeFromNumber(15.4), 15.4); 15 | }); 16 | 17 | describe('fromNumber', () => { 18 | it('Not passing: not a finite value', () => { 19 | TEUtils.assertTrue(pipe(NaN, CVReal.fromNumber, Either.isLeft)); 20 | }); 21 | it('Passing', () => { 22 | TEUtils.assertTrue(pipe(18.4, CVReal.fromNumber, Either.isRight)); 23 | }); 24 | }); 25 | 26 | describe('fromBigDecimal', () => { 27 | it('Not passing: too big', () => { 28 | TEUtils.assertTrue(pipe(BigDecimal.make(1n, -25), CVReal.fromBigDecimal, Either.isLeft)); 29 | }); 30 | it('Passing', () => { 31 | TEUtils.assertTrue( 32 | pipe(BigDecimal.make(1543367754n, 2), CVReal.fromBigDecimal, Either.isRight) 33 | ); 34 | }); 35 | }); 36 | 37 | describe('fromBigInt', () => { 38 | it('Not passing', () => { 39 | TEUtils.assertTrue(pipe(BigInt(1e15) * BigInt(1e15), CVReal.fromBigInt, Either.isLeft)); 40 | }); 41 | it('Passing', () => { 42 | TEUtils.assertTrue(pipe(BigInt(1e15), CVReal.fromBigInt, Either.isRight)); 43 | }); 44 | }); 45 | 46 | it('toBigDecimal', () => { 47 | TEUtils.assertEquals( 48 | pipe(-18.4, CVReal.unsafeFromNumber, CVReal.toBigDecimal), 49 | BigDecimal.make(-184n, 1) 50 | ); 51 | TEUtils.assertEquals( 52 | pipe(18.4, CVPositiveReal.unsafeFromNumber, CVReal.toBigDecimal), 53 | BigDecimal.make(184n, 1) 54 | ); 55 | }); 56 | 57 | describe('toBigInt', () => { 58 | it('Not passing: not an integer', () => { 59 | TEUtils.assertTrue(pipe(-18.4, CVReal.unsafeFromNumber, CVReal.toBigInt, Either.isLeft)); 60 | TEUtils.assertTrue( 61 | pipe(18.4, CVPositiveReal.unsafeFromNumber, CVReal.toBigInt, Either.isLeft) 62 | ); 63 | }); 64 | it('Passing', () => { 65 | TEUtils.assertTrue(pipe(-18, CVReal.unsafeFromNumber, CVReal.toBigInt, Either.isRight)); 66 | TEUtils.assertTrue( 67 | pipe(18, CVPositiveReal.unsafeFromNumber, CVReal.toBigInt, Either.isRight) 68 | ); 69 | }); 70 | }); 71 | }); 72 | -------------------------------------------------------------------------------- /packages/conversions/tests/RealInt.test.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable functional/no-expression-statements */ 2 | import { CVPositiveRealInt, CVReal, CVRealInt } from '@parischap/conversions'; 3 | import { TEUtils } from '@parischap/test-utils'; 4 | import { BigDecimal, Either, pipe } from 'effect'; 5 | import { describe, it } from 'vitest'; 6 | 7 | describe('CVRealInt', () => { 8 | it('moduleTag', () => { 9 | TEUtils.assertSome(TEUtils.moduleTagFromTestFilePath(__filename), CVRealInt.moduleTag); 10 | }); 11 | 12 | it('unsafeFromNumber', () => { 13 | TEUtils.strictEqual(CVRealInt.unsafeFromNumber(NaN), NaN); 14 | TEUtils.strictEqual(CVRealInt.unsafeFromNumber(15.4), 15.4); 15 | TEUtils.strictEqual(CVRealInt.unsafeFromNumber(15), 15); 16 | }); 17 | 18 | describe('fromNumber', () => { 19 | it('Not passing: not finite', () => { 20 | TEUtils.assertTrue(pipe(NaN, CVRealInt.fromNumber, Either.isLeft)); 21 | }); 22 | it('Not passing: not integer', () => { 23 | TEUtils.assertTrue(pipe(15.4, CVRealInt.fromNumber, Either.isLeft)); 24 | }); 25 | it('Passing', () => { 26 | TEUtils.assertTrue(pipe(15, CVRealInt.fromNumber, Either.isRight)); 27 | }); 28 | }); 29 | 30 | describe('fromBigDecimal', () => { 31 | it('Not passing: too big', () => { 32 | TEUtils.assertTrue(pipe(BigDecimal.make(1n, -25), CVRealInt.fromBigDecimal, Either.isLeft)); 33 | }); 34 | it('Not passing: not an integer', () => { 35 | TEUtils.assertTrue( 36 | pipe(BigDecimal.make(1543367754n, 2), CVRealInt.fromBigDecimal, Either.isLeft) 37 | ); 38 | }); 39 | it('Passing', () => { 40 | TEUtils.assertTrue( 41 | pipe(BigDecimal.make(1543367754n, 0), CVRealInt.fromBigDecimal, Either.isRight) 42 | ); 43 | }); 44 | }); 45 | 46 | describe('fromBigInt', () => { 47 | it('Not passing: too big', () => { 48 | TEUtils.assertTrue(pipe(BigInt(1e15) * BigInt(1e15), CVRealInt.fromBigInt, Either.isLeft)); 49 | }); 50 | it('Passing', () => { 51 | TEUtils.assertTrue(pipe(BigInt(1e15), CVRealInt.fromBigInt, Either.isRight)); 52 | }); 53 | }); 54 | 55 | it('toBigDecimal', () => { 56 | TEUtils.assertEquals( 57 | pipe(-18, CVRealInt.unsafeFromNumber, CVRealInt.toBigDecimal), 58 | BigDecimal.make(-18n, 0) 59 | ); 60 | TEUtils.assertEquals( 61 | pipe(18, CVPositiveRealInt.unsafeFromNumber, CVRealInt.toBigDecimal), 62 | BigDecimal.make(18n, 0) 63 | ); 64 | }); 65 | 66 | it('toBigInt', () => { 67 | TEUtils.strictEqual(pipe(-18, CVRealInt.unsafeFromNumber, CVRealInt.toBigInt), -18n); 68 | TEUtils.strictEqual(pipe(18, CVPositiveRealInt.unsafeFromNumber, CVRealInt.toBigInt), 18n); 69 | }); 70 | 71 | describe('fromReal', () => { 72 | it('Not passing: not an integer', () => { 73 | TEUtils.assertTrue(pipe(18.4, CVReal.unsafeFromNumber, CVRealInt.fromReal, Either.isLeft)); 74 | }); 75 | it('Passing', () => { 76 | pipe(18, CVReal.unsafeFromNumber, CVRealInt.fromReal, Either.isRight); 77 | pipe(18, CVPositiveRealInt.unsafeFromNumber, CVRealInt.fromReal, Either.isRight); 78 | }); 79 | }); 80 | }); 81 | -------------------------------------------------------------------------------- /packages/conversions/tests/RoundingOption.test.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable functional/no-expression-statements */ 2 | import { CVRoundingMode, CVRoundingOption } from '@parischap/conversions'; 3 | import { MNumber } from '@parischap/effect-lib'; 4 | import { TEUtils } from '@parischap/test-utils'; 5 | import { BigDecimal, Equal, pipe } from 'effect'; 6 | import { describe, it } from 'vitest'; 7 | 8 | describe('CVRoundingOption', () => { 9 | const roundingOption = CVRoundingOption.make({ 10 | precision: 3, 11 | roundingMode: CVRoundingMode.Type.HalfEven 12 | }); 13 | 14 | describe('Tag, prototype and guards', () => { 15 | it('moduleTag', () => { 16 | TEUtils.assertSome(TEUtils.moduleTagFromTestFilePath(__filename), CVRoundingOption.moduleTag); 17 | }); 18 | 19 | describe('Equal.equals', () => { 20 | it('Matching', () => { 21 | TEUtils.assertTrue( 22 | Equal.equals( 23 | roundingOption, 24 | CVRoundingOption.make({ 25 | precision: 3, 26 | roundingMode: CVRoundingMode.Type.HalfEven 27 | }) 28 | ) 29 | ); 30 | }); 31 | 32 | it('Non-matching', () => { 33 | TEUtils.assertNotEquals( 34 | roundingOption, 35 | CVRoundingOption.make({ 36 | precision: 2, 37 | roundingMode: CVRoundingMode.Type.HalfEven 38 | }) 39 | ); 40 | }); 41 | }); 42 | 43 | it('.toString()', () => { 44 | TEUtils.strictEqual(roundingOption.toString(), 'HalfEvenRounderWith3Precision'); 45 | }); 46 | 47 | it('.pipe()', () => { 48 | TEUtils.assertTrue(roundingOption.pipe(CVRoundingOption.has)); 49 | }); 50 | 51 | describe('has', () => { 52 | it('Matching', () => { 53 | TEUtils.assertTrue(CVRoundingOption.has(roundingOption)); 54 | }); 55 | it('Non matching', () => { 56 | TEUtils.assertFalse(CVRoundingOption.has(new Date())); 57 | }); 58 | }); 59 | }); 60 | 61 | describe('toNumberRounder', () => { 62 | const rounder = CVRoundingOption.toNumberRounder(roundingOption); 63 | it('Even number', () => { 64 | TEUtils.assertTrue(pipe(0.4566, rounder, MNumber.equals(0.457))); 65 | }); 66 | it('Odd number', () => { 67 | TEUtils.assertTrue(pipe(-0.4564, rounder, MNumber.equals(-0.456))); 68 | }); 69 | }); 70 | 71 | describe('toBigDecimalRounder', () => { 72 | const rounder = CVRoundingOption.toBigDecimalRounder(roundingOption); 73 | it('Even number', () => { 74 | TEUtils.assertEquals(rounder(BigDecimal.make(4566n, 4)), BigDecimal.make(457n, 3)); 75 | }); 76 | it('Odd number', () => { 77 | TEUtils.assertEquals(rounder(BigDecimal.make(-4564n, 4)), BigDecimal.make(-456n, 3)); 78 | }); 79 | }); 80 | }); 81 | -------------------------------------------------------------------------------- /packages/conversions/tests/SemVer.test.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable functional/no-expression-statements */ 2 | import { CVSemVer } from '@parischap/conversions'; 3 | import { TEUtils } from '@parischap/test-utils'; 4 | import { Either, pipe } from 'effect'; 5 | import { describe, it } from 'vitest'; 6 | 7 | describe('CVSemVer', () => { 8 | it('unsafeFromString', () => { 9 | TEUtils.strictEqual(CVSemVer.unsafeFromString('foo'), 'foo'); 10 | }); 11 | 12 | describe('fromString', () => { 13 | it('Not passing', () => { 14 | TEUtils.assertTrue(pipe('foo', CVSemVer.fromString, Either.isLeft)); 15 | }); 16 | it('Passing', () => { 17 | TEUtils.assertTrue(pipe('1.0.1', CVSemVer.fromString, Either.isRight)); 18 | }); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /packages/conversions/tsconfig.base.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "_version": "20.1.0", 4 | "extends": [ 5 | "@tsconfig/strictest/tsconfig.json", 6 | "@tsconfig/node20/tsconfig.json" 7 | ], 8 | "compilerOptions": { 9 | "allowJs": false, 10 | "checkJs": false, 11 | "moduleDetection": "force", 12 | "composite": true, 13 | "resolveJsonModule": true, 14 | "esModuleInterop": false, 15 | "declaration": true, 16 | "skipLibCheck": true, 17 | "emitDecoratorMetadata": true, 18 | "experimentalDecorators": true, 19 | "moduleResolution": "NodeNext", 20 | "isolatedModules": true, 21 | "sourceMap": true, 22 | "declarationMap": true, 23 | "noEmitOnError": false, 24 | "noErrorTruncation": true, 25 | "target": "ES2022", 26 | "module": "NodeNext", 27 | "incremental": true, 28 | "removeComments": false, 29 | "lib": [], 30 | "types": [], 31 | "plugins": [{ "name": "@effect/language-service" }] 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /packages/conversions/tsconfig.check.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.esm.json", 3 | "compilerOptions": { 4 | "noEmit": true, 5 | "tsBuildInfoFile": ".tsbuildinfo/check.tsbuildinfo" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/conversions/tsconfig.docgen.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.base.json", 3 | "include": ["**/*.ts", "**/*.mts", "**/*.cts"], 4 | "compilerOptions": { "noEmit": true, "lib": ["ESNext"], "types": ["node"] } 5 | } 6 | -------------------------------------------------------------------------------- /packages/conversions/tsconfig.esm.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.base.json", 3 | "include": ["esm/**/*.ts", "esm/**/*.mts", "esm/**/*.cts"], 4 | "compilerOptions": { 5 | "rootDir": "esm", 6 | "tsBuildInfoFile": ".tsbuildinfo/esm.tsbuildinfo", 7 | "declarationDir": "dist/dts", 8 | "declarationMap": true, 9 | "outDir": "dist/esm", 10 | "lib": ["ESNext"] 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /packages/conversions/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.base.json", 3 | "include": [], 4 | "references": [ 5 | { "path": "./tsconfig.esm.json" }, 6 | { "path": "./tsconfig.others.json" } 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /packages/conversions/tsconfig.others.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.base.json", 3 | "include": [ 4 | "**/*.ts", 5 | "**/*.mts", 6 | "**/*.cts", 7 | "**/*.js", 8 | "**/*.mjs", 9 | "**/*.cjs" 10 | ], 11 | "exclude": [ 12 | "esm/**/*.ts", 13 | "esm/**/*.mts", 14 | "esm/**/*.cts", 15 | "**/node_modules/**/*.ts", 16 | "**/node_modules/**/*.mts", 17 | "**/node_modules/**/*.cts", 18 | "**/node_modules/**/*.js", 19 | "**/node_modules/**/*.mjs", 20 | "**/node_modules/**/*.cjs", 21 | "dist/**/*.ts", 22 | "dist/**/*.mts", 23 | "dist/**/*.cts", 24 | "dist/**/*.js", 25 | "dist/**/*.mjs", 26 | "dist/**/*.cjs", 27 | "vite.config.ts.timestamp-*.mjs" 28 | ], 29 | "compilerOptions": { 30 | "rootDir": ".", 31 | "tsBuildInfoFile": ".tsbuildinfo/others.tsbuildinfo", 32 | "outDir": "dist/others", 33 | "lib": ["ESNext"], 34 | "types": ["node"], 35 | "allowJs": true, 36 | "checkJs": true 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /packages/conversions/vite.config.ts: -------------------------------------------------------------------------------- 1 | import type { UserConfig } from 'vite'; 2 | 3 | export default { 4 | test: { 5 | include: ['./tests/*.test.ts'], 6 | isolate: false, 7 | fileParallelism: false, 8 | pool: 'threads' 9 | } 10 | } satisfies UserConfig; 11 | -------------------------------------------------------------------------------- /packages/date/.gitignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | node_modules/ 3 | vite.config.ts.timestamp-*.mjs 4 | /.tsbuildinfo/ 5 | /*.old -------------------------------------------------------------------------------- /packages/date/.madgerc: -------------------------------------------------------------------------------- 1 | { 2 | "detectiveOptions": { 3 | "ts": { 4 | "skipTypeImports": true 5 | } 6 | } 7 | } -------------------------------------------------------------------------------- /packages/date/.prettierignore: -------------------------------------------------------------------------------- 1 | # Same rules as .git 2 | node_modules/ -------------------------------------------------------------------------------- /packages/date/eslint.config.js: -------------------------------------------------------------------------------- 1 | import { eslintConfigLibrary } from '@parischap/configs'; 2 | 3 | export default eslintConfigLibrary; 4 | -------------------------------------------------------------------------------- /packages/date/esm/DateFormatter.ts: -------------------------------------------------------------------------------- 1 | import { MBadArgumentError, MEither, MOption } from '@parischap/effect-lib'; 2 | import * as Templater from '@parischap/templater'; 3 | import { Either, Function, Option, pipe } from 'effect'; 4 | import { apply, compose } from 'effect/Function'; 5 | import * as Date from './Date.js'; 6 | import * as Token from './Token.js'; 7 | 8 | const moduleTag = '@parischap/date/DateFormatter/'; 9 | 10 | export interface Type { 11 | readonly dateTemplater: Templater.Type; 12 | } 13 | 14 | export const make = (format: string): Either.Either => 15 | Either.gen(function* () { 16 | const dateTemplater = Templater.make(format, Token.tokens); 17 | 18 | return { dateTemplater }; 19 | }); 20 | apply; 21 | export const read = ( 22 | self: Type, 23 | locale?: string 24 | ): ((dateToRead: string) => Either.Either) => 25 | Either.gen(function* () { 26 | const tokenToMergedTokensForLocale = yield* pipe( 27 | Token.tokenToMergedTokens, 28 | Array.map(Function.apply(locale)), 29 | Either.all, 30 | Either.mapLeft(MBadArgumentError.setModuleTagAndFunctionName(moduleTag, 'read')), 31 | Either.map( 32 | Array.map( 33 | compose(Either.mapLeft(MBadArgumentError.setModuleTagAndFunctionName(moduleTag, 'read'))) 34 | ) 35 | ) 36 | ); 37 | return (dateToRead: string) => 38 | Either.gen(function* () { 39 | const tokenValues = yield* pipe( 40 | dateToRead, 41 | Templater.read(self.dateTemplater, Token.tokenPatterns), 42 | Either.mapLeft(MBadArgumentError.setModuleTagAndFunctionName(moduleTag, 'read')), 43 | MEither.catchTag( 44 | 'Effect-lib_BadArgument_BadFormat', 45 | MBadArgumentError.mapId(() => 'dateToRead') 46 | ) 47 | ); 48 | const mergedTokenValues = yield* pipe( 49 | tokenValues, 50 | Array.zip(tokenToMergedTokensForLocale), 51 | Array.map(([tokenValue, tokenToMergedTokenForLocale]) => 52 | pipe(tokenValue, Option.map(tokenToMergedTokenForLocale), MOption.traverseEither) 53 | ), 54 | Either.all 55 | ); 56 | }) as never; 57 | }) as never; 58 | -------------------------------------------------------------------------------- /packages/date/esm/Errors.ts: -------------------------------------------------------------------------------- 1 | import { Data } from 'effect'; 2 | 3 | /* eslint-disable-next-line @typescript-eslint/ban-types */ 4 | export class MissingData extends Data.TaggedError('MissingData')<{}> {} 5 | -------------------------------------------------------------------------------- /packages/date/esm/MergedBasis.ts: -------------------------------------------------------------------------------- 1 | import * as utils from '@parischap/date/utils'; 2 | import { MFunction } from '@parischap/effect-lib'; 3 | import { HashMap, Record, pipe } from 'effect'; 4 | 5 | export interface DefaultValue { 6 | (now: Date): number; 7 | } 8 | 9 | export interface Descriptor { 10 | readonly defaultValue: DefaultValue; 11 | } 12 | 13 | const Descriptor = MFunction.make; 14 | 15 | const struct = { 16 | d: Descriptor({ 17 | defaultValue: (now) => { 18 | const cpy = new Date(now.getTime()); 19 | return cpy.setHours(0, 0, 0, 0); 20 | } 21 | }), 22 | H: Descriptor({ 23 | defaultValue: (now) => now.getHours() * utils.HOUR_MS 24 | }), 25 | m: Descriptor({ defaultValue: (now) => now.getMinutes() * utils.MINUTE_MS }), 26 | s: Descriptor({ defaultValue: (now) => now.getSeconds() * utils.SECOND_MS }), 27 | S: Descriptor({ defaultValue: (now) => now.getMilliseconds() }), 28 | Z: Descriptor({ 29 | defaultValue: (now) => -now.getTimezoneOffset() * utils.MINUTE_MS 30 | }) 31 | }; 32 | 33 | export type Type = keyof typeof struct; 34 | 35 | export const map: HashMap.HashMap = pipe( 36 | struct, 37 | Record.toEntries, 38 | HashMap.fromIterable 39 | ); 40 | -------------------------------------------------------------------------------- /packages/date/esm/MergedToken.ts: -------------------------------------------------------------------------------- 1 | import { MFunction } from '@parischap/effect-lib'; 2 | import { HashMap, Record, pipe } from 'effect'; 3 | 4 | /*export interface DateToMergedToken { 5 | (date: Date.Components): number; 6 | }*/ 7 | 8 | export interface Descriptor { 9 | readonly label: string; 10 | //readonly dateToMergedToken: DateToMergedToken; 11 | } 12 | 13 | const Descriptor = MFunction.make; 14 | 15 | // year must be in first position 16 | const struct = { 17 | year: Descriptor({ label: 'year' }), 18 | ordinalDay: Descriptor({ 19 | label: 'year day', 20 | dateToMergedToken: (date) => date.ordinalDay 21 | }), 22 | month: Descriptor({ label: 'month' }), 23 | monthDay: Descriptor({ label: 'day of month' }), 24 | isoWeek: Descriptor({ 25 | label: 'week number' 26 | }), 27 | weekDay: Descriptor({ 28 | label: 'weekday' 29 | }), 30 | hour24: Descriptor({ label: 'hour in 24-hour format' }), 31 | hour12: Descriptor({ 32 | label: 'hour in 12-hour format' 33 | }), 34 | meridiem: Descriptor({ 35 | label: 'meridiem' 36 | }), 37 | minute: Descriptor({ label: 'minute' }), 38 | second: Descriptor({ label: 'second' }), 39 | millisecond: Descriptor({ label: 'millisecond' }), 40 | timeZoneOffset: Descriptor({ label: 'zone offset' }) 41 | }; 42 | 43 | export type Type = keyof typeof struct; 44 | 45 | export const map: HashMap.HashMap = pipe( 46 | struct, 47 | Record.toEntries, 48 | HashMap.fromIterable 49 | ); 50 | 51 | /*export const name = (self: Type): string => 52 | pipe( 53 | map, 54 | HashMap.get(self), 55 | Option.getOrThrowWith(() => new Error(`Abnormal Error. ${self} not found in MergedToken.map`)), 56 | ({ label }) => `${self}(${label})` 57 | );*/ 58 | -------------------------------------------------------------------------------- /packages/date/esm/formatDate.ts: -------------------------------------------------------------------------------- 1 | import * as Errors from '@parischap/date/Errors'; 2 | 3 | /* 4 | * Formats a date string according to the passed format. See Token.ts for the list and description of the tokens that can be used in the format param. 5 | * @param format example: `yyyy-MM-ddTHH:mm:ssXXX` 6 | * @param locale locale to use to parse the date. If omitted, system locale is used. The locale is used for tokens that output a string like `MMM`, `MMMM`, `EEE`, `EEEE`,... 7 | * @returns The function returns an error if the locale does not exist. Otherwise, it returns a function that takes a date to format (`input`) and returns a string. 8 | */ 9 | 10 | export const formatDate = ( 11 | format: string, 12 | locale?: string 13 | ): Either.Either string> => { 14 | const searchPattern = new RegExp( 15 | pipe( 16 | Token, 17 | HashMap.keys, 18 | Array.fromIterable, 19 | // We sort the patterns in reverse order so smaller patterns match after larger ones in which they may be included. 20 | Array.sort(Order.reverse(Order.string)), 21 | (arr) => MRegExpString.either(...arr), 22 | MRegExpString.capture 23 | ), 24 | 'g' 25 | ); 26 | }; 27 | -------------------------------------------------------------------------------- /packages/date/esm/index.ts: -------------------------------------------------------------------------------- 1 | export * from './Date.js'; 2 | //export { parseDateString } from './parseDateString.js'; 3 | -------------------------------------------------------------------------------- /packages/date/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@parischap-dev/date", 3 | "type": "module", 4 | "author": "Jérôme MARTIN", 5 | "license": "MIT", 6 | "scripts": { 7 | "tscheck": "tsc -b tsconfig.check.json --force", 8 | "lint": "eslint .", 9 | "lint-fix": "eslint . --fix", 10 | "lint-rules": "pnpx @eslint/config-inspector", 11 | "update-config-files": "update-config-files", 12 | "clean-config-files": "shx rm -rf package.json && shx rm -rf tsconfig.json", 13 | "transpile-esm": "tsc -b tsconfig.esm.json", 14 | "transpile-cjs": "babel dist/esm --plugins @babel/transform-export-namespace-from --plugins @babel/transform-modules-commonjs --out-dir dist/cjs --source-maps", 15 | "transpile-annotate": "babel dist --plugins annotate-pure-calls --out-dir dist --source-maps", 16 | "compile": "pnpm transpile-esm && pnpm transpile-cjs && pnpm transpile-annotate && pnpm prodify", 17 | "build-and-publish": "pnpm build && pnpm checks && pnpm publish-to-npm", 18 | "docgen": "echo \"docgen not activated for this package\"", 19 | "circular": "madge --extensions ts --circular --no-color --no-spinner esm", 20 | "checks": "pnpm circular && pnpm lint && pnpm tscheck && pnpm test", 21 | "test": "vitest run", 22 | "clean-prod": "shx rm -rf dist && shx rm -rf .tsbuildinfo && shx mkdir -p dist", 23 | "publish-to-npm": "cd dist && npm publish --access=public && cd ..", 24 | "install-prod": "cd dist && pnpm i && cd ..", 25 | "build": "pnpm clean-prod && pnpm --if-present pre-build && pnpm compile && pnpm --if-present post-build && pnpm --if-present generate-types && pnpm install-prod", 26 | "prodify": "prodify", 27 | "examples": "" 28 | }, 29 | "publishConfig": { 30 | "main": "./cjs/index.js", 31 | "types": "./dts/index.d.ts", 32 | "exports": { 33 | ".": { 34 | "default": "./cjs/index.js", 35 | "import": "./esm/index.js", 36 | "types": "./dts/index.d.ts" 37 | } 38 | }, 39 | "scripts": {}, 40 | "devDependencies": {}, 41 | "peerDependencies": { 42 | "@parischap/effect-lib": "^0.5.0", 43 | "effect": "^3.15.4" 44 | }, 45 | "publishConfig": {}, 46 | "packageManager": "", 47 | "pnpm": {}, 48 | "type": "" 49 | }, 50 | "bugs": { 51 | "url": "https://github.com/parischap/effect-libs/issues" 52 | }, 53 | "funding": [ 54 | { 55 | "type": "ko-fi", 56 | "url": "https://ko-fi.com/parischap" 57 | } 58 | ], 59 | "keywords": [ 60 | "effect", 61 | "typescript", 62 | "functional-programming" 63 | ], 64 | "description": "A complement to the official effect library dedicated to date and time", 65 | "module": "./esm/index.js", 66 | "exports": { 67 | ".": { 68 | "import": "./esm/index.ts" 69 | } 70 | }, 71 | "dependencies": {}, 72 | "devDependencies": { 73 | "@parischap/date": "link:." 74 | }, 75 | "peerDependencies": { 76 | "@parischap/effect-lib": "workspace:@parischap-dev/effect-lib@*", 77 | "effect": "^3.15.4" 78 | }, 79 | "repository": { 80 | "type": "git", 81 | "url": "git+https://github.com/parischap/effect-libs.git", 82 | "directory": "packages/date" 83 | }, 84 | "homepage": "https://github.com/parischap/effect-libs/tree/master/packages/date" 85 | } 86 | -------------------------------------------------------------------------------- /packages/date/prettier.config.js: -------------------------------------------------------------------------------- 1 | import * as Configs from '@parischap/configs'; 2 | 3 | export default Configs.prettierconfigBase; 4 | -------------------------------------------------------------------------------- /packages/date/project.config.js: -------------------------------------------------------------------------------- 1 | import * as Configs from '@parischap/configs'; 2 | 3 | export default Configs.configSubRepo({ 4 | description: 'A complement to the official effect library dedicated to date and time', 5 | dependencies: {}, 6 | devDependencies: {}, 7 | internalPeerDependencies: { 'effect-lib': Configs.constants.effectLibVersion }, 8 | externalPeerDependencies: { 9 | effect: Configs.constants.effectVersion 10 | }, 11 | examples: [], 12 | scripts: {}, 13 | environment: Configs.Environment.Type.Library, 14 | bundled: false, 15 | visibility: Configs.Visibility.Type.Public, 16 | hasStaticFolder: false, 17 | hasDocGen: false, 18 | keywords: [] 19 | }); 20 | -------------------------------------------------------------------------------- /packages/date/tests/dummy.test.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable functional/no-expression-statements */ 2 | import { describe, it } from 'vitest'; 3 | 4 | describe('Dummy', () => { 5 | it('dummy', () => { 6 | TEUtils.strictEqual(1,1); 7 | }); 8 | }); 9 | -------------------------------------------------------------------------------- /packages/date/tsconfig.base.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "_version": "20.1.0", 4 | "extends": [ 5 | "@tsconfig/strictest/tsconfig.json", 6 | "@tsconfig/node20/tsconfig.json" 7 | ], 8 | "compilerOptions": { 9 | "allowJs": false, 10 | "checkJs": false, 11 | "moduleDetection": "force", 12 | "composite": true, 13 | "resolveJsonModule": true, 14 | "esModuleInterop": false, 15 | "declaration": true, 16 | "skipLibCheck": true, 17 | "emitDecoratorMetadata": true, 18 | "experimentalDecorators": true, 19 | "moduleResolution": "NodeNext", 20 | "isolatedModules": true, 21 | "sourceMap": true, 22 | "declarationMap": true, 23 | "noEmitOnError": false, 24 | "noErrorTruncation": true, 25 | "target": "ES2022", 26 | "module": "NodeNext", 27 | "incremental": true, 28 | "removeComments": false, 29 | "lib": [], 30 | "types": [], 31 | "plugins": [{ "name": "@effect/language-service" }] 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /packages/date/tsconfig.check.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.esm.json", 3 | "compilerOptions": { 4 | "noEmit": true, 5 | "tsBuildInfoFile": ".tsbuildinfo/check.tsbuildinfo" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/date/tsconfig.esm.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.base.json", 3 | "include": ["esm/**/*.ts", "esm/**/*.mts", "esm/**/*.cts"], 4 | "compilerOptions": { 5 | "rootDir": "esm", 6 | "tsBuildInfoFile": ".tsbuildinfo/esm.tsbuildinfo", 7 | "declarationDir": "dist/dts", 8 | "declarationMap": true, 9 | "outDir": "dist/esm", 10 | "lib": ["ESNext"] 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /packages/date/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.base.json", 3 | "include": [], 4 | "references": [ 5 | { "path": "./tsconfig.esm.json" }, 6 | { "path": "./tsconfig.others.json" } 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /packages/date/tsconfig.others.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.base.json", 3 | "include": [ 4 | "**/*.ts", 5 | "**/*.mts", 6 | "**/*.cts", 7 | "**/*.js", 8 | "**/*.mjs", 9 | "**/*.cjs" 10 | ], 11 | "exclude": [ 12 | "esm/**/*.ts", 13 | "esm/**/*.mts", 14 | "esm/**/*.cts", 15 | "**/node_modules/**/*.ts", 16 | "**/node_modules/**/*.mts", 17 | "**/node_modules/**/*.cts", 18 | "**/node_modules/**/*.js", 19 | "**/node_modules/**/*.mjs", 20 | "**/node_modules/**/*.cjs", 21 | "dist/**/*.ts", 22 | "dist/**/*.mts", 23 | "dist/**/*.cts", 24 | "dist/**/*.js", 25 | "dist/**/*.mjs", 26 | "dist/**/*.cjs", 27 | "vite.config.ts.timestamp-*.mjs" 28 | ], 29 | "compilerOptions": { 30 | "rootDir": ".", 31 | "tsBuildInfoFile": ".tsbuildinfo/others.tsbuildinfo", 32 | "outDir": "dist/others", 33 | "lib": ["ESNext"], 34 | "types": ["node"], 35 | "allowJs": true, 36 | "checkJs": true 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /packages/date/vite.config.ts: -------------------------------------------------------------------------------- 1 | import type { UserConfig } from 'vite'; 2 | 3 | export default { 4 | test: { 5 | include: ['./tests/*.test.ts'], 6 | isolate: false, 7 | fileParallelism: false, 8 | pool: 'threads' 9 | } 10 | } satisfies UserConfig; 11 | -------------------------------------------------------------------------------- /packages/effect-lib/.gitignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | node_modules/ 3 | vite.config.ts.timestamp-*.mjs 4 | /.tsbuildinfo/ 5 | /*.old -------------------------------------------------------------------------------- /packages/effect-lib/.madgerc: -------------------------------------------------------------------------------- 1 | { 2 | "detectiveOptions": { 3 | "ts": { 4 | "skipTypeImports": true 5 | } 6 | } 7 | } -------------------------------------------------------------------------------- /packages/effect-lib/.prettierignore: -------------------------------------------------------------------------------- 1 | # Same rules as .git 2 | node_modules/ -------------------------------------------------------------------------------- /packages/effect-lib/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "vitest.disableWorkspaceWarning": true 3 | } -------------------------------------------------------------------------------- /packages/effect-lib/README.md: -------------------------------------------------------------------------------- 1 | ## EFFECT-LIB 2 | 3 | This library extends the official [Effect](https://effect.website/) library. It is not meant to be used on its own. Used as peerDependency by other NPM packages. 4 | 5 | ## Donate 6 | 7 | [Any donations would be much appreciated](https://ko-fi.com/parischap). 😄 8 | 9 | ## Documentation 10 | 11 | If you need to, take a look at the [API](https://parischap.github.io/effect-libs/docs/effect-lib). 12 | -------------------------------------------------------------------------------- /packages/effect-lib/docgen.json: -------------------------------------------------------------------------------- 1 | { 2 | "parseCompilerOptions": "./tsconfig.docgen.json", 3 | "examplesCompilerOptions": "./tsconfig.docgen.json", 4 | "srcDir": "./esm", 5 | "outDir": "docs", 6 | "exclude": ["esm/index.ts"], 7 | "enforceDescriptions": true, 8 | "enforceVersion": false 9 | } 10 | -------------------------------------------------------------------------------- /packages/effect-lib/eslint.config.js: -------------------------------------------------------------------------------- 1 | import { eslintConfigLibrary } from '@parischap/configs'; 2 | 3 | export default eslintConfigLibrary; 4 | -------------------------------------------------------------------------------- /packages/effect-lib/esm/BigDecimal.ts: -------------------------------------------------------------------------------- 1 | /** A simple extension to the Effect BigDecimal module */ 2 | 3 | import { BigDecimal, flow, Function, Option, pipe, Tuple } from 'effect'; 4 | import * as MBigInt from './BigInt.js'; 5 | import * as MTypes from './types.js'; 6 | 7 | const _bigDecimalMinSafeInteger = BigDecimal.make(BigInt(Number.MIN_SAFE_INTEGER), 0); 8 | const _bigDecimalMaxSafeInteger = BigDecimal.make(BigInt(Number.MAX_SAFE_INTEGER), 0); 9 | const _tupledMake = Function.tupled(BigDecimal.make); 10 | 11 | /** 12 | * Function that creates a Bigdecimal from a scale and a string representing a bigint 13 | * 14 | * @category Constructors 15 | */ 16 | export const unsafeFromIntString = ( 17 | scale: number 18 | ): MTypes.OneArgFunction => 19 | flow(MBigInt.unsafeFromString, Tuple.make, Tuple.appendElement(scale), _tupledMake); 20 | 21 | /** 22 | * Function that converts a BigDecimal to a number. Returns a `some` if the BigDecimal is in the 23 | * 64-bit range of a number. Returns a `none` otherwise 24 | * 25 | * @category Destructors 26 | */ 27 | export const toNumber: MTypes.OneArgFunction> = flow( 28 | Option.liftPredicate( 29 | BigDecimal.between({ minimum: _bigDecimalMinSafeInteger, maximum: _bigDecimalMaxSafeInteger }) 30 | ), 31 | Option.map(BigDecimal.unsafeToNumber) 32 | ); 33 | 34 | /** 35 | * BigDecimal instance representing the 0 value 36 | * 37 | * @category Instances 38 | */ 39 | export const zero: BigDecimal.BigDecimal = BigDecimal.make(0n, 0); 40 | 41 | /** 42 | * Truncates a BigDecimal after `precision` decimal digits. `precision` must be a positive finite 43 | * integer. If not provided, `precision` is taken equal to 0. 44 | * 45 | * @category Utils 46 | */ 47 | export const trunc = (precision = 0): MTypes.OneArgFunction => 48 | BigDecimal.scale(precision); 49 | 50 | /** 51 | * Returns `truncatedPart`, `self` truncated after `precision` decimal digits, and `followingpart`, 52 | * the difference between `self` and `truncatedPart`. `precision` must be a positive finite integer. 53 | * If not provided, `precision` is taken equal to 0. 54 | * 55 | * @category Destructors 56 | */ 57 | 58 | export const truncatedAndFollowingParts = 59 | (precision = 0) => 60 | ( 61 | self: BigDecimal.BigDecimal 62 | ): [truncatedPart: BigDecimal.BigDecimal, followingpart: BigDecimal.BigDecimal] => { 63 | const truncatedPart = pipe(self, trunc(precision)); 64 | return Tuple.make(truncatedPart, BigDecimal.subtract(self, truncatedPart)); 65 | }; 66 | -------------------------------------------------------------------------------- /packages/effect-lib/esm/BigInt.ts: -------------------------------------------------------------------------------- 1 | /** A simple extension to the Effect BigInt module */ 2 | 3 | import { flow, Number, Option, pipe, Predicate, String } from 'effect'; 4 | import * as MTypes from './types.js'; 5 | 6 | /** 7 | * Constructs a bigint from a number. Will only fail if the number is NaN, Infinity or not an 8 | * integer. 9 | * 10 | * @category Constructors 11 | */ 12 | export const unsafeFromNumber: MTypes.OneArgFunction = BigInt; 13 | 14 | /** 15 | * Constructs a bigint from a string. Will only fail if the string does not represent an integer. 16 | * 17 | * @category Constructors 18 | */ 19 | export const unsafeFromString: MTypes.OneArgFunction = BigInt; 20 | 21 | /** 22 | * Returns `true` if `self` is positive 23 | * 24 | * @category Predicates 25 | */ 26 | export const isPositive: Predicate.Predicate = (self) => self > 0n; 27 | 28 | /** 29 | * Returns `true` if `self` is negative 30 | * 31 | * @category Predicates 32 | */ 33 | export const isNegative: Predicate.Predicate = (self) => self < 0n; 34 | 35 | /** 36 | * Returns `true` if `self` is zero 37 | * 38 | * @category Predicates 39 | */ 40 | export const isZero: Predicate.Predicate = (self) => self === 0n; 41 | 42 | /** 43 | * Returns `true` if `self` is even 44 | * 45 | * @category Predicates 46 | */ 47 | export const isEven: Predicate.Predicate = (self) => self % 2n === 0n; 48 | 49 | /** 50 | * Returns `true` if `self` is even 51 | * 52 | * @category Predicates 53 | */ 54 | export const isOdd: Predicate.Predicate = Predicate.not(isEven); 55 | 56 | /** 57 | * Calculates the base-10 log of a bigint 58 | * 59 | * @category Destructors 60 | */ 61 | export const unsafeLog10 = (self: bigint): number => 62 | pipe(self.toString(), String.length, Number.decrement); 63 | 64 | /** 65 | * Calculates the base-10 log of a bigint 66 | * 67 | * @category Destructors 68 | */ 69 | export const log10: MTypes.OneArgFunction> = flow( 70 | Option.liftPredicate(isPositive), 71 | Option.map(unsafeLog10) 72 | ); 73 | -------------------------------------------------------------------------------- /packages/effect-lib/esm/Chunk.ts: -------------------------------------------------------------------------------- 1 | /** A simple extension to the Effect Chunk module */ 2 | 3 | import { Boolean, Chunk, Option, Predicate, pipe } from 'effect'; 4 | 5 | /** 6 | * Returns true if the length of `self` is `l` 7 | * 8 | * @category Predicates 9 | */ 10 | export const hasLength = 11 | (l: number) => 12 | (self: Chunk.Chunk): boolean => 13 | self.length === l; 14 | 15 | /** 16 | * Returns true if the provided Chunk contains duplicates 17 | * 18 | * @category Utils 19 | */ 20 | export const hasDuplicates = (self: Chunk.Chunk): boolean => 21 | pipe(self, Chunk.dedupe, hasLength(self.length), Boolean.not); 22 | 23 | /** 24 | * Returns a Chunk of the indexes of all elements of self matching the predicate 25 | * 26 | * @category Utils 27 | */ 28 | export const findAll = 29 | (predicate: Predicate.Predicate) => 30 | (self: Chunk.Chunk): Chunk.Chunk => 31 | Chunk.filterMap(self, (b, i) => 32 | pipe( 33 | i, 34 | Option.liftPredicate(() => predicate(b)) 35 | ) 36 | ); 37 | 38 | /** 39 | * Returns a chunk containing all elements of self except the n last elements 40 | * 41 | * @category Utils 42 | */ 43 | export const takeBut = 44 | (n: number) => 45 | (self: Chunk.Chunk): Chunk.Chunk => 46 | Chunk.take(self, Chunk.size(self) - n); 47 | 48 | /** 49 | * Returns a chunk containing all elements of self except the n first elements 50 | * 51 | * @category Utils 52 | */ 53 | export const takeRightBut = 54 | (n: number) => 55 | (self: Chunk.Chunk): Chunk.Chunk => 56 | Chunk.takeRight(self, Chunk.size(self) - n); 57 | -------------------------------------------------------------------------------- /packages/effect-lib/esm/Either.ts: -------------------------------------------------------------------------------- 1 | /** A simple extension to the Effect Either module */ 2 | 3 | import { Cause, Either, Function, Option, Predicate, Tuple, pipe } from 'effect'; 4 | import * as MTuple from './Tuple.js'; 5 | import * as MTypes from './types.js'; 6 | 7 | /** 8 | * Same as Effect.optionFromOptional but for Either's 9 | * 10 | * @category Utils 11 | */ 12 | export const optionFromOptional = ( 13 | self: Either.Either 14 | ): Either.Either, Exclude> => 15 | pipe( 16 | self, 17 | Either.map(Option.some), 18 | Either.orElse((e) => 19 | e instanceof Cause.NoSuchElementException ? 20 | Either.right(Option.none()) 21 | : Either.left(e as Exclude) 22 | ) 23 | ); 24 | 25 | /** 26 | * Flattens two eithers into a single one 27 | * 28 | * @category Utils 29 | */ 30 | export const flatten: ( 31 | self: Either.Either, L2> 32 | ) => Either.Either = Either.flatMap(Function.identity); 33 | 34 | /** 35 | * Gets the value of an Either that can never be a left 36 | * 37 | * @category Utils 38 | */ 39 | export const getRightWhenNoLeft = (self: Either.Either): A => 40 | (self as Either.Right).right; 41 | 42 | /** 43 | * Transforms an either of a tuple into a tuple of either's. Useful for instance for error 44 | * management in reduce or mapAccum 45 | * 46 | * @category Utils 47 | */ 48 | export const traversePair = ( 49 | self: Either.Either, L> 50 | ): MTypes.Pair, Either.Either> => 51 | pipe( 52 | self, 53 | Either.map(Tuple.mapBoth({ onFirst: Either.right, onSecond: Either.right })), 54 | Either.getOrElse(MTuple.makeBothBy({ toFirst: Either.left, toSecond: Either.left })) 55 | ); 56 | 57 | /** 58 | * Recovers from the specified tagged error. 59 | * 60 | * @category Utils 61 | */ 62 | export const catchTag = 63 | ( 64 | k: K, 65 | f: (e: Extract) => E1 66 | ) => 67 | (self: Either.Either): Either.Either> => 68 | Either.mapLeft(self, (e) => 69 | Predicate.isTagged(e, k) ? 70 | f(e as Extract) 71 | : (e as Exclude) 72 | ); 73 | -------------------------------------------------------------------------------- /packages/effect-lib/esm/Fs.ts: -------------------------------------------------------------------------------- 1 | /** A module that applies the concept of branding to filepaths */ 2 | 3 | import { Brand } from 'effect'; 4 | 5 | /** 6 | * Module tag 7 | * 8 | * @category Module tag 9 | */ 10 | export const moduleTag = '@parischap/effect-lib/Fs/'; 11 | type moduleTag = typeof moduleTag; 12 | 13 | /** 14 | * Brand that represents a file 15 | * 16 | * @category Branding 17 | */ 18 | export type FileBrand = Brand.Branded; 19 | 20 | /** 21 | * Brand that represents a folder 22 | * 23 | * @category Branding 24 | */ 25 | export type FolderBrand = Brand.Branded; 26 | 27 | /** 28 | * Brand that represents a name 29 | * 30 | * @category Branding 31 | */ 32 | export type NameBrand = Brand.Branded; 33 | 34 | /** 35 | * Brand that represents a path 36 | * 37 | * @category Branding 38 | */ 39 | export type PathBrand = Brand.Branded; 40 | 41 | /** 42 | * Brand that represents a filename 43 | * 44 | * @category Branding 45 | */ 46 | export type Filename = FileBrand & NameBrand; 47 | 48 | /** 49 | * Filename constructor 50 | * 51 | * @category Constructors 52 | */ 53 | export const Filename = Brand.nominal(); 54 | 55 | /** 56 | * Brand that represents a foldername 57 | * 58 | * @category Branding 59 | */ 60 | export type Foldername = FolderBrand & NameBrand; 61 | 62 | /** 63 | * Foldername constructor 64 | * 65 | * @category Constructors 66 | */ 67 | export const Foldername = Brand.nominal(); 68 | 69 | /** 70 | * Brand that represents a file path 71 | * 72 | * @category Branding 73 | */ 74 | export type Filepath = FileBrand & PathBrand; 75 | 76 | /** 77 | * Filename constructor 78 | * 79 | * @category Constructors 80 | */ 81 | export const Filepath = Brand.nominal(); 82 | 83 | /** 84 | * Brand that represents a folder path 85 | * 86 | * @category Branding 87 | */ 88 | export type Folderpath = FolderBrand & PathBrand; 89 | 90 | /** 91 | * Folder path constructor 92 | * 93 | * @category Constructors 94 | */ 95 | export const Folderpath = Brand.nominal(); 96 | 97 | /** 98 | * Brand that represents a filename or a folder name 99 | * 100 | * @category Branding 101 | */ 102 | export type Name = Filename | Foldername; 103 | 104 | /** 105 | * Name constructor 106 | * 107 | * @category Constructors 108 | */ 109 | export const Name = Brand.nominal(); 110 | 111 | /** 112 | * Brand that represents a file path or a folder path 113 | * 114 | * @category Branding 115 | */ 116 | export type Path = Filepath | Folderpath; 117 | 118 | /** 119 | * Path constructor 120 | * 121 | * @category Constructors 122 | */ 123 | export const Path = Brand.nominal(); 124 | 125 | /** 126 | * Type utility that turns a path into a name 127 | * 128 | * @category Utility types 129 | */ 130 | export type ToName

= 131 | readonly [P] extends readonly [Filepath] ? Filename 132 | : readonly [P] extends readonly [Folderpath] ? Foldername 133 | : Name; 134 | 135 | /** 136 | * Type utility that turns a file system name into a path 137 | * 138 | * @category Utility types 139 | */ 140 | export type ToPath = 141 | readonly [N] extends readonly [Filename] ? Filepath 142 | : readonly [N] extends readonly [Foldername] ? Folderpath 143 | : Path; 144 | -------------------------------------------------------------------------------- /packages/effect-lib/esm/Inspectable.ts: -------------------------------------------------------------------------------- 1 | /** A simple extension to the Effect Inspectable module */ 2 | 3 | import { Inspectable, Option, pipe } from 'effect'; 4 | import * as MRecord from './Record.js'; 5 | 6 | /** 7 | * Module tag 8 | * 9 | * @category Module tag 10 | */ 11 | export const moduleTag = '@parischap/effect-lib/Inspectable/'; 12 | 13 | /** 14 | * Symbol used to define a special prototype function that must return an id for the object 15 | * 16 | * @category Models 17 | */ 18 | export const IdSymbol: unique symbol = Symbol.for(moduleTag + 'IdSymbol/') as IdSymbol; 19 | 20 | /** 21 | * Type used to define a special prototype function that must return an id for the object 22 | * 23 | * @category Models 24 | */ 25 | export type IdSymbol = typeof IdSymbol; 26 | 27 | /** 28 | * Interface that an object should implement when an id can be used to represent it 29 | * 30 | * @category Models 31 | */ 32 | export interface Type extends Inspectable.Inspectable { 33 | readonly [IdSymbol]: () => string; 34 | } 35 | /** 36 | * Prototype of an `Inspectable` that overloads the `toJSON` method. If the object (usually its 37 | * prototype) has a `[IdSymbol]` function, returns the result of this function. Otherwise, return 38 | * this with an extra '_id' field containing the moduleTag. 39 | * 40 | * @category Constants 41 | */ 42 | export const BaseProto = (moduleTag: string): Inspectable.Inspectable => ({ 43 | ...Inspectable.BaseProto, 44 | toJSON(this: {}): unknown { 45 | return pipe( 46 | this, 47 | MRecord.tryZeroParamStringFunction({ 48 | functionName: IdSymbol 49 | }), 50 | Option.getOrElse(() => ({ _id: moduleTag, ...this })) 51 | ); 52 | }, 53 | toString(this: {}): string { 54 | return pipe( 55 | this, 56 | MRecord.tryZeroParamStringFunction({ 57 | functionName: IdSymbol 58 | }), 59 | Option.getOrElse(() => Inspectable.BaseProto.toString.call(this)) 60 | ); 61 | } 62 | }); 63 | -------------------------------------------------------------------------------- /packages/effect-lib/esm/Json.ts: -------------------------------------------------------------------------------- 1 | /** A port of JSON.stringify and JSON.parse in the effect world */ 2 | 3 | import { Effect } from 'effect'; 4 | import * as MPortError from './PortError.js'; 5 | 6 | /** 7 | * Port of JSON.stringify 8 | * 9 | * @category Utils 10 | */ 11 | export const stringify = (value: unknown, replacer?: Parameters[1]) => 12 | Effect.try({ 13 | try: () => JSON.stringify(value, replacer), 14 | catch: (e) => 15 | new MPortError.Type({ 16 | originalError: e, 17 | originalFunctionName: 'JSON.stringify', 18 | moduleName: 'json.ts', 19 | libraryName: 'effect-lib' 20 | }) 21 | }); 22 | 23 | /** 24 | * Port of JSON.parse 25 | * 26 | * @category Utils 27 | */ 28 | export const parse = (text: string, reviver?: Parameters[1]) => 29 | Effect.try({ 30 | try: () => JSON.parse(text, reviver) as unknown, 31 | catch: (e) => 32 | new MPortError.Type({ 33 | originalError: e, 34 | originalFunctionName: 'JSON.parse', 35 | moduleName: 'json.ts', 36 | libraryName: 'effect-lib' 37 | }) 38 | }); 39 | -------------------------------------------------------------------------------- /packages/effect-lib/esm/Option.ts: -------------------------------------------------------------------------------- 1 | /** A simple extension to the Effect Option module */ 2 | 3 | import { Either, Option, flow } from 'effect'; 4 | 5 | /** 6 | * Type that synthesizes two different ways to represent an optional value. Useful to open a dev to 7 | * non effect users 8 | * 9 | * @category Models 10 | */ 11 | export type OptionOrNullable = Option.Option | null | undefined | A; 12 | 13 | /** 14 | * Converts an `OptionOrNullable` into an `Option`. 15 | * 16 | * @category Utils 17 | */ 18 | export const fromOptionOrNullable = (a: OptionOrNullable): Option.Option => 19 | Option.isOption(a) ? a : Option.fromNullable(a); 20 | 21 | /** 22 | * Transforms an `Option` of an `Either` in an `Either` of an `Option` 23 | * 24 | * @category Utils 25 | */ 26 | export const traverseEither = ( 27 | o: Option.Option> 28 | ): Either.Either, L> => 29 | Option.match(o, { 30 | onNone: () => Either.right(Option.none()), 31 | onSome: Either.match({ onLeft: Either.left, onRight: flow(Option.some, Either.right) }) 32 | }); 33 | 34 | /** 35 | * Reads the next value of an Iterator into an Option 36 | * 37 | * @category Utils 38 | */ 39 | export const fromNextIteratorValue = (iterator: Iterator): Option.Option => { 40 | const next = iterator.next(); 41 | return next.done === false ? Option.some(next.value) : Option.none(); 42 | }; 43 | -------------------------------------------------------------------------------- /packages/effect-lib/esm/Pipeable.ts: -------------------------------------------------------------------------------- 1 | /** A simple extension to the Effect Pipeable module */ 2 | 3 | import { Pipeable } from 'effect'; 4 | 5 | /** 6 | * Default prototype of a Pipeable 7 | * 8 | * @category Constants 9 | */ 10 | export const BaseProto: Pipeable.Pipeable = { 11 | pipe(this: {}) { 12 | /* eslint-disable-next-line prefer-rest-params */ 13 | return Pipeable.pipeArguments(this, arguments); 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /packages/effect-lib/esm/PortError.ts: -------------------------------------------------------------------------------- 1 | /** A module that implements an error that occurs when porting a function to Effect */ 2 | 3 | import { Data } from 'effect'; 4 | 5 | /** 6 | * Module tag 7 | * 8 | * @category Module tag 9 | */ 10 | export const moduleTag = '@parischap/effect-lib/PortError/'; 11 | 12 | /** 13 | * Type of a Port Error 14 | * 15 | * @category Models 16 | */ 17 | export class Type extends Data.TaggedError(moduleTag)<{ 18 | readonly originalError: unknown; 19 | readonly originalFunctionName: string; 20 | readonly moduleName: string; 21 | readonly libraryName: string; 22 | }> {} 23 | -------------------------------------------------------------------------------- /packages/effect-lib/esm/Predicate.ts: -------------------------------------------------------------------------------- 1 | /** A simple extension to the Effect Predicate module */ 2 | 3 | import { Effect, Predicate } from 'effect'; 4 | import * as MTypes from './types.js'; 5 | 6 | /** 7 | * Effectful predicate that returns an effectful boolean 8 | * 9 | * @category Models 10 | */ 11 | export interface EffectPredicate { 12 | (x: Z): Effect.Effect; 13 | } 14 | 15 | /** 16 | * Type utiliy that extracts the source of a predicate or refinement. 17 | * 18 | * @category Utility types 19 | */ 20 | export type Source = 21 | readonly [R] extends readonly [Predicate.Predicate] ? A : never; 22 | 23 | /** 24 | * Type utiliy that extracts the target of a predicate or refinement. 25 | * 26 | * @category Utility types 27 | */ 28 | export type Target = 29 | readonly [R] extends readonly [Predicate.Refinement] ? A : Source; 30 | 31 | /** 32 | * Type utiliy that extracts the type covered by a refinement or predicate. Returns never when 33 | * applied to a predicate and its target when applied to a refinement. 34 | * 35 | * @category Utility types 36 | */ 37 | export type Coverage = 38 | readonly [R] extends readonly [Predicate.Refinement] ? A : never; 39 | 40 | // Do not use an interface here. 41 | type PredicateArray = ReadonlyArray; 42 | 43 | /** 44 | * Type utiliy that takes an array of predicates or refinements and returns an array/record of their 45 | * sources 46 | * 47 | * @category Utility types 48 | */ 49 | export type PredicatesToSources = { 50 | readonly [key in keyof T]: Source; 51 | }; 52 | 53 | /** 54 | * Type utiliy that takes an array/record of predicates or refinements and returns an array/record 55 | * of their targets 56 | * 57 | * @category Utility types 58 | */ 59 | export type PredicatesToTargets = { 60 | readonly [key in keyof T]: Target; 61 | }; 62 | /** 63 | * Type utiliy that takes an array/record of predicates or refinements and returns an array/record 64 | * of their coverages 65 | * 66 | * @category Utility types 67 | */ 68 | export type PredicatesToCoverages = { 69 | readonly [key in keyof T]: Coverage; 70 | }; 71 | 72 | /** 73 | * Type utiliy that takes an array/record and returns an array/record of predicates 74 | * 75 | * @category Utility types 76 | */ 77 | export type SourcesToPredicates = { 78 | readonly [key in keyof T]: Predicate.Predicate; 79 | }; 80 | 81 | /** 82 | * Same as Predicate.struct but allows field completion and makes it possible to only pass a subset 83 | * of the object fields even when there are some refinements. 84 | * 85 | * @category Utils 86 | */ 87 | export const struct = 88 | >>>( 89 | fields: F 90 | ) => 91 | ( 92 | o: O 93 | ): o is { 94 | readonly [key in keyof O]: key extends keyof F ? 95 | F[key] extends MTypes.AnyPredicate ? 96 | Target & O[key] 97 | : never 98 | : O[key]; 99 | } => 100 | Predicate.struct(fields as never)(o as never) as never; 101 | -------------------------------------------------------------------------------- /packages/effect-lib/esm/Record.ts: -------------------------------------------------------------------------------- 1 | /** A simple extension to the Effect Record module */ 2 | 3 | import { flow, Function, Option, pipe, Predicate, Record } from 'effect'; 4 | import * as MFunction from './Function.js'; 5 | import * as MTypes from './types.js'; 6 | 7 | /** 8 | * Unsafe get an element from a record. No checks, faster than the Effect version 9 | * 10 | * @category Utils 11 | */ 12 | export const unsafeGet = 13 | (key: string | symbol) => 14 | (self: Record.ReadonlyRecord): A => 15 | // @ts-expect-error getting record content unsafely 16 | self[key]; 17 | 18 | /** 19 | * Tries to call method `functionName` on `self` with no parameters. Returns a `some` of the result 20 | * if such a function exists, is different from exception (if defined), and takes no parameter. 21 | * Returns a `none` otherwise 22 | * 23 | * @category Utils 24 | */ 25 | export const tryZeroParamFunction = 26 | ({ 27 | functionName, 28 | exception 29 | }: { 30 | readonly functionName: string | symbol; 31 | readonly exception?: MTypes.AnyFunction; 32 | }) => 33 | (self: MTypes.NonPrimitive): Option.Option => 34 | pipe( 35 | self[functionName], 36 | Option.liftPredicate(MTypes.isFunction), 37 | Option.filter( 38 | Predicate.and( 39 | pipe( 40 | exception, 41 | Option.liftPredicate(MTypes.isNotUndefined), 42 | Option.map(flow(MFunction.strictEquals, Predicate.not)), 43 | Option.getOrElse(() => Function.constTrue) 44 | ), 45 | flow(MFunction.parameterNumber, MFunction.strictEquals(0)) 46 | ) 47 | ), 48 | Option.map(MFunction.applyAsThis(self)) 49 | ); 50 | 51 | /** 52 | * Same as `tryZeroParamStringFunction` but returns a `none` if the result of the function is not a 53 | * string 54 | * 55 | * @category Utils 56 | */ 57 | export const tryZeroParamStringFunction = (params: { 58 | readonly functionName: string | symbol; 59 | readonly exception?: MTypes.AnyFunction; 60 | }): MTypes.OneArgFunction> => 61 | flow(tryZeroParamFunction(params), Option.filter(MTypes.isString)); 62 | -------------------------------------------------------------------------------- /packages/effect-lib/esm/Tuple.ts: -------------------------------------------------------------------------------- 1 | /** A simple extension to the Effect Tuple module */ 2 | 3 | import { Tuple, pipe } from 'effect'; 4 | 5 | /** 6 | * Creates a one element tuple. `MTuple.fromSingleValue` must be used preferably to `Tuple.make` in 7 | * functions that send a variable number of arguments like Array.map or Array.filter because a tuple 8 | * with several arguments may accidentally be built. For instance, `Array.map([1, 2, 3], 9 | * Tuple.make)` will return `[[1,0], [2,1], [3,2]]` whereas you might expect `[[1], [2], [3]]`. 10 | * That's because `Array.map` and `Array.filter` send a second argument with the position of the 11 | * value. 12 | * 13 | * @category Constructors 14 | */ 15 | export const fromSingleValue = (a: A): [A] => Tuple.make(a); 16 | 17 | /** 18 | * Creates a two element tuple with the same value 19 | * 20 | * @category Constructors 21 | */ 22 | export const makeBoth = (a: A): [A, A] => Tuple.make(a, a); 23 | 24 | /** 25 | * Creates a two element tuple applying two different functions to the same value 26 | * 27 | * @category Constructors 28 | */ 29 | export const makeBothBy = 30 | ({ 31 | toFirst, 32 | toSecond 33 | }: { 34 | readonly toFirst: (a: NoInfer) => B; 35 | readonly toSecond: (a: NoInfer) => C; 36 | }) => 37 | (a: A): [B, C] => 38 | pipe(a, makeBoth, Tuple.mapBoth({ onFirst: toFirst, onSecond: toSecond })); 39 | 40 | /** 41 | * Prepends an element at the start of a tuple. 42 | * 43 | * @category Utils 44 | */ 45 | export const prependElement = 46 | (that: B) => 47 | >(self: A): [B, ...A] => 48 | [that, ...self] as const; 49 | 50 | /** 51 | * Returns the first two elements of a tuple 52 | * 53 | * @category Utils 54 | */ 55 | export const firstTwo = >( 56 | a: readonly [A, B, ...C] 57 | ): [A, B] => Tuple.make(a[0], a[1]); 58 | -------------------------------------------------------------------------------- /packages/effect-lib/esm/index.ts: -------------------------------------------------------------------------------- 1 | export * as MArray from './Array.js'; 2 | export * as MBigDecimal from './BigDecimal.js'; 3 | export * as MBigInt from './BigInt.js'; 4 | export * as MCache from './Cache.js'; 5 | export * as MChunk from './Chunk.js'; 6 | export * as MEither from './Either.js'; 7 | export * as MFs from './Fs.js'; 8 | export * as MFunction from './Function.js'; 9 | export * as MInputError from './InputError.js'; 10 | export * as MInspectable from './Inspectable.js'; 11 | export * as MJson from './Json.js'; 12 | export * as MMatch from './Match.js'; 13 | export * as MNumber from './Number.js'; 14 | export * as MOption from './Option.js'; 15 | export * as MPipeable from './Pipeable.js'; 16 | export * as MPortError from './PortError.js'; 17 | export * as MPredicate from './Predicate.js'; 18 | export * as MRecord from './Record.js'; 19 | export * as MRegExp from './RegExp.js'; 20 | export * as MRegExpString from './RegExpString.js'; 21 | export * as MString from './String.js'; 22 | export * as MStruct from './Struct.js'; 23 | export * as MTree from './Tree.js'; 24 | export * as MTuple from './Tuple.js'; 25 | export * as MTypes from './types.js'; 26 | -------------------------------------------------------------------------------- /packages/effect-lib/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@parischap-dev/effect-lib", 3 | "type": "module", 4 | "author": "Jérôme MARTIN", 5 | "license": "MIT", 6 | "scripts": { 7 | "tscheck": "tsc -b tsconfig.check.json --force", 8 | "lint": "eslint .", 9 | "lint-fix": "eslint . --fix", 10 | "lint-rules": "pnpx @eslint/config-inspector", 11 | "update-config-files": "update-config-files", 12 | "clean-config-files": "shx rm -rf package.json && shx rm -rf tsconfig.json", 13 | "transpile-esm": "tsc -b tsconfig.esm.json", 14 | "transpile-cjs": "babel dist/esm --plugins @babel/transform-export-namespace-from --plugins @babel/transform-modules-commonjs --out-dir dist/cjs --source-maps", 15 | "transpile-annotate": "babel dist --plugins annotate-pure-calls --out-dir dist --source-maps", 16 | "compile": "pnpm transpile-esm && pnpm transpile-cjs && pnpm transpile-annotate && pnpm prodify", 17 | "build-and-publish": "pnpm build && pnpm checks && pnpm publish-to-npm", 18 | "docgen": "docgen", 19 | "circular": "madge --extensions ts --circular --no-color --no-spinner esm", 20 | "checks": "pnpm circular && pnpm lint && pnpm tscheck && pnpm test", 21 | "test": "vitest run", 22 | "clean-prod": "shx rm -rf dist && shx rm -rf .tsbuildinfo && shx mkdir -p dist", 23 | "publish-to-npm": "cd dist && npm publish --access=public && cd ..", 24 | "install-prod": "cd dist && pnpm i && cd ..", 25 | "build": "pnpm clean-prod && pnpm --if-present pre-build && pnpm compile && pnpm --if-present post-build && pnpm --if-present generate-types && pnpm install-prod", 26 | "prodify": "prodify", 27 | "examples": "" 28 | }, 29 | "publishConfig": { 30 | "main": "./cjs/index.js", 31 | "types": "./dts/index.d.ts", 32 | "exports": { 33 | ".": { 34 | "default": "./cjs/index.js", 35 | "import": "./esm/index.js", 36 | "types": "./dts/index.d.ts" 37 | } 38 | }, 39 | "scripts": {}, 40 | "devDependencies": {}, 41 | "peerDependencies": { 42 | "effect": "^3.15.4" 43 | }, 44 | "publishConfig": {}, 45 | "packageManager": "", 46 | "pnpm": {}, 47 | "type": "" 48 | }, 49 | "bugs": { 50 | "url": "https://github.com/parischap/effect-libs/issues" 51 | }, 52 | "funding": [ 53 | { 54 | "type": "ko-fi", 55 | "url": "https://ko-fi.com/parischap" 56 | } 57 | ], 58 | "keywords": [ 59 | "effect", 60 | "typescript", 61 | "functional-programming" 62 | ], 63 | "description": "An extension to the official Effect library", 64 | "module": "./esm/index.js", 65 | "exports": { 66 | ".": { 67 | "import": "./esm/index.ts" 68 | } 69 | }, 70 | "dependencies": {}, 71 | "devDependencies": { 72 | "@parischap/effect-lib": "link:." 73 | }, 74 | "peerDependencies": { 75 | "effect": "^3.15.4" 76 | }, 77 | "repository": { 78 | "type": "git", 79 | "url": "git+https://github.com/parischap/effect-libs.git", 80 | "directory": "packages/effect-lib" 81 | }, 82 | "homepage": "https://github.com/parischap/effect-libs/tree/master/packages/effect-lib" 83 | } 84 | -------------------------------------------------------------------------------- /packages/effect-lib/prettier.config.js: -------------------------------------------------------------------------------- 1 | import * as Configs from '@parischap/configs'; 2 | 3 | export default Configs.prettierconfigBase; 4 | -------------------------------------------------------------------------------- /packages/effect-lib/project.config.js: -------------------------------------------------------------------------------- 1 | import * as Configs from '@parischap/configs'; 2 | 3 | export default Configs.configSubRepo({ 4 | description: 'An extension to the official Effect library', 5 | dependencies: {}, 6 | devDependencies: { 7 | }, 8 | internalPeerDependencies: {}, 9 | externalPeerDependencies: { 10 | effect: Configs.constants.effectVersion 11 | }, 12 | examples: [], 13 | scripts: { }, 14 | environment: Configs.Environment.Type.Library, 15 | bundled: false, 16 | visibility: Configs.Visibility.Type.Public, 17 | hasStaticFolder: false, 18 | hasDocGen: true, 19 | keywords: [] 20 | }); 21 | -------------------------------------------------------------------------------- /packages/effect-lib/tests/BigDecimal.test.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable functional/no-expression-statements */ 2 | import { MBigDecimal } from '@parischap/effect-lib'; 3 | import { TEUtils } from '@parischap/test-utils'; 4 | import { BigDecimal, pipe } from 'effect'; 5 | import { describe, it } from 'vitest'; 6 | 7 | describe('MBigDecimal', () => { 8 | describe('trunc', () => { 9 | it('Number that does not need to be truncated', () => { 10 | TEUtils.assertEquals( 11 | pipe(BigDecimal.make(545n, 1), MBigDecimal.trunc(2)), 12 | BigDecimal.make(545n, 1) 13 | ); 14 | }); 15 | 16 | it('Positive number, first fractional digit < 5', () => { 17 | TEUtils.assertEquals( 18 | pipe(BigDecimal.make(544n, 3), MBigDecimal.trunc(2)), 19 | BigDecimal.make(54n, 2) 20 | ); 21 | }); 22 | 23 | it('Positive number, first fractional digit >= 5', () => { 24 | TEUtils.assertEquals( 25 | pipe(BigDecimal.make(545n, 3), MBigDecimal.trunc(2)), 26 | BigDecimal.make(54n, 2) 27 | ); 28 | }); 29 | 30 | it('Negative number, first fractional digit < 5', () => { 31 | TEUtils.assertEquals( 32 | pipe(BigDecimal.make(-544n, 3), MBigDecimal.trunc(2)), 33 | BigDecimal.make(-54n, 2) 34 | ); 35 | }); 36 | 37 | it('Negative number, first fractional digit >= 5', () => { 38 | TEUtils.assertEquals( 39 | pipe(BigDecimal.make(-545n, 3), MBigDecimal.trunc(2)), 40 | BigDecimal.make(-54n, 2) 41 | ); 42 | }); 43 | }); 44 | 45 | describe('truncatedAndFollowingParts', () => { 46 | const truncatedAndFollowingParts = MBigDecimal.truncatedAndFollowingParts(1); 47 | it('Positive number, first fractional digit < 5', () => { 48 | TEUtils.deepStrictEqual(truncatedAndFollowingParts(BigDecimal.make(544n, 2)), [ 49 | BigDecimal.make(54n, 1), 50 | BigDecimal.make(4n, 2) 51 | ]); 52 | }); 53 | 54 | it('Positive number, first fractional digit >= 5', () => { 55 | TEUtils.deepStrictEqual(truncatedAndFollowingParts(BigDecimal.make(545n, 2)), [ 56 | BigDecimal.make(54n, 1), 57 | BigDecimal.make(5n, 2) 58 | ]); 59 | }); 60 | 61 | it('Negative number, first fractional digit < 5', () => { 62 | TEUtils.deepStrictEqual(truncatedAndFollowingParts(BigDecimal.make(-544n, 2)), [ 63 | BigDecimal.make(-54n, 1), 64 | BigDecimal.make(-4n, 2) 65 | ]); 66 | }); 67 | 68 | it('Negative number, first fractional digit >= 5', () => { 69 | TEUtils.deepStrictEqual(truncatedAndFollowingParts(BigDecimal.make(-545n, 2)), [ 70 | BigDecimal.make(-54n, 1), 71 | BigDecimal.make(-5n, 2) 72 | ]); 73 | }); 74 | }); 75 | }); 76 | -------------------------------------------------------------------------------- /packages/effect-lib/tests/BigInt.test.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable functional/no-expression-statements */ 2 | import { MBigInt } from '@parischap/effect-lib'; 3 | import { TEUtils } from '@parischap/test-utils'; 4 | import { describe, it } from 'vitest'; 5 | 6 | describe('MBigInt', () => { 7 | describe('isEven', () => { 8 | it('Passing', () => { 9 | TEUtils.assertTrue(MBigInt.isEven(10n)); 10 | }); 11 | it('Not passing', () => { 12 | TEUtils.assertFalse(MBigInt.isEven(11n)); 13 | }); 14 | }); 15 | 16 | describe('log10', () => { 17 | it('Negative value', () => { 18 | TEUtils.assertNone(MBigInt.log10(-3n)); 19 | }); 20 | 21 | it('Positive value', () => { 22 | TEUtils.assertSome(MBigInt.log10(1248n), 3); 23 | }); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /packages/effect-lib/tests/Chunk.test.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable functional/no-expression-statements */ 2 | import { MChunk, MFunction } from '@parischap/effect-lib'; 3 | import { TEUtils } from '@parischap/test-utils'; 4 | import { Chunk, pipe } from 'effect'; 5 | import { describe, it } from 'vitest'; 6 | 7 | describe('MChunk', () => { 8 | describe('hasLength', () => { 9 | it('Simple chunk', () => { 10 | TEUtils.assertTrue(pipe(Chunk.make(1, 2, 3), MChunk.hasLength(3))); 11 | }); 12 | }); 13 | 14 | describe('hasDuplicates', () => { 15 | it('With no duplicates', () => { 16 | TEUtils.assertFalse(pipe(Chunk.make(1, 2, 3), MChunk.hasDuplicates)); 17 | }); 18 | 19 | it('With duplicates', () => { 20 | TEUtils.assertTrue(pipe(Chunk.make(1, 2, 3, 2), MChunk.hasDuplicates)); 21 | }); 22 | }); 23 | 24 | describe('findAll', () => { 25 | it('Empty chunk', () => { 26 | TEUtils.assertTrue( 27 | pipe(Chunk.empty(), MChunk.findAll(MFunction.strictEquals(3)), Chunk.isEmpty) 28 | ); 29 | }); 30 | it('Non empty chunk', () => { 31 | TEUtils.assertEquals( 32 | pipe(Chunk.make(3, 2, 5, 3, 8, 3), MChunk.findAll(MFunction.strictEquals(3))), 33 | Chunk.make(0, 3, 5) 34 | ); 35 | }); 36 | }); 37 | 38 | describe('takeBut', () => { 39 | it('Empty chunk', () => { 40 | TEUtils.assertTrue(pipe(Chunk.empty(), MChunk.takeBut(2), Chunk.isEmpty)); 41 | }); 42 | it('Non empty chunk', () => { 43 | TEUtils.assertEquals( 44 | pipe(Chunk.make(3, 2, 5, 3, 8, 3), MChunk.takeBut(2)), 45 | Chunk.make(3, 2, 5, 3) 46 | ); 47 | }); 48 | }); 49 | 50 | describe('takeRightBut', () => { 51 | it('Empty chunk', () => { 52 | TEUtils.assertTrue(pipe(Chunk.empty(), MChunk.takeRightBut(2), Chunk.isEmpty)); 53 | }); 54 | it('Non empty chunk', () => { 55 | TEUtils.assertEquals( 56 | pipe(Chunk.make(3, 2, 5, 3, 8, 3), MChunk.takeRightBut(2)), 57 | Chunk.make(5, 3, 8, 3) 58 | ); 59 | }); 60 | }); 61 | }); 62 | -------------------------------------------------------------------------------- /packages/effect-lib/tests/Fs.test.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable functional/no-expression-statements */ 2 | import { MFs } from '@parischap/effect-lib'; 3 | import { TEUtils } from '@parischap/test-utils'; 4 | import { describe, it } from 'vitest'; 5 | 6 | describe('MFs', () => { 7 | it('moduleTag', () => { 8 | TEUtils.assertSome(TEUtils.moduleTagFromTestFilePath(__filename), MFs.moduleTag); 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /packages/effect-lib/tests/Function.test.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable functional/no-expression-statements */ 2 | import { MFunction } from '@parischap/effect-lib'; 3 | import { TEUtils } from '@parischap/test-utils'; 4 | import { Number, pipe, String } from 'effect'; 5 | import { describe, it } from 'vitest'; 6 | 7 | describe('MFunction', () => { 8 | describe('fIfTrue', () => { 9 | it('Matching', () => { 10 | TEUtils.strictEqual( 11 | pipe(1 as number, MFunction.fIfTrue({ condition: true, f: Number.increment })), 12 | 2 13 | ); 14 | }); 15 | 16 | it('Non-matching', () => { 17 | TEUtils.strictEqual( 18 | pipe(1 as number, MFunction.fIfTrue({ condition: false, f: Number.increment })), 19 | 1 20 | ); 21 | }); 22 | }); 23 | 24 | it('flipDual', () => { 25 | TEUtils.strictEqual(pipe(2, MFunction.flipDual(String.takeLeft)('foo')), 'fo'); 26 | }); 27 | 28 | describe('strictEquals', () => { 29 | it('Matching', () => { 30 | TEUtils.assertTrue(pipe(5, MFunction.strictEquals(5))); 31 | }); 32 | 33 | it('Non matching', () => { 34 | TEUtils.assertFalse(pipe(5, MFunction.strictEquals(2))); 35 | }); 36 | }); 37 | 38 | it('parameterNumber', () => { 39 | TEUtils.strictEqual( 40 | pipe((m: number, n: number) => m + n, MFunction.parameterNumber), 41 | 2 42 | ); 43 | }); 44 | 45 | it('name', () => { 46 | TEUtils.strictEqual(pipe(Math.max, MFunction.name), 'max'); 47 | }); 48 | 49 | it('once', () => { 50 | let a = 0; 51 | const complexFoo = () => a++; 52 | const memoized = MFunction.once(complexFoo); 53 | TEUtils.strictEqual(memoized(), 0); 54 | TEUtils.strictEqual(memoized(), 0); 55 | }); 56 | 57 | it('applyAsMethod', () => { 58 | TEUtils.strictEqual(pipe(Array.prototype.pop, MFunction.applyAsThis([1, 2])), 2); 59 | }); 60 | 61 | it('execute', () => { 62 | TEUtils.strictEqual( 63 | pipe(() => 1, MFunction.execute), 64 | 1 65 | ); 66 | }); 67 | 68 | it('clone', () => { 69 | const incCopy = MFunction.clone(Number.increment); 70 | TEUtils.assertFalse(incCopy === Number.increment); 71 | TEUtils.strictEqual(incCopy(1), 2); 72 | }); 73 | }); 74 | -------------------------------------------------------------------------------- /packages/effect-lib/tests/Inspectable.test.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable functional/no-expression-statements */ 2 | import { MInspectable } from '@parischap/effect-lib'; 3 | import { TEUtils } from '@parischap/test-utils'; 4 | import { describe, it } from 'vitest'; 5 | 6 | describe('MInspectable', () => { 7 | it('moduleTag', () => { 8 | TEUtils.assertSome(TEUtils.moduleTagFromTestFilePath(__filename), MInspectable.moduleTag); 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /packages/effect-lib/tests/Number.test.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable functional/no-expression-statements */ 2 | import { MNumber } from '@parischap/effect-lib'; 3 | import { TEUtils } from '@parischap/test-utils'; 4 | import { pipe } from 'effect'; 5 | import { describe, it } from 'vitest'; 6 | 7 | describe('MNumber', () => { 8 | it('intModulo', () => { 9 | TEUtils.strictEqual(MNumber.intModulo(3)(5), 2); 10 | TEUtils.strictEqual(MNumber.intModulo(5)(3), 3); 11 | TEUtils.strictEqual(MNumber.intModulo(3)(-5), 1); 12 | TEUtils.strictEqual(MNumber.intModulo(5)(-3), 2); 13 | TEUtils.strictEqual(pipe(-3, MNumber.intModulo(3), MNumber.abs), 0); 14 | TEUtils.strictEqual(MNumber.intModulo(-3)(5), 2); 15 | TEUtils.strictEqual(MNumber.intModulo(-5)(3), 3); 16 | TEUtils.strictEqual(MNumber.intModulo(-3)(-5), 1); 17 | TEUtils.strictEqual(MNumber.intModulo(-5)(-3), 2); 18 | }); 19 | 20 | describe('quotientAndRemainder', () => { 21 | it('Positive dividend, positive divisor', () => { 22 | TEUtils.deepStrictEqual(pipe(27, MNumber.quotientAndRemainder(5)), [5, 2]); 23 | }); 24 | 25 | it('Negative dividend, positive divisor', () => { 26 | TEUtils.deepStrictEqual(pipe(-27, MNumber.quotientAndRemainder(5)), [-6, 3]); 27 | }); 28 | 29 | it('Positive dividend, negative divisor', () => { 30 | TEUtils.deepStrictEqual(pipe(27, MNumber.quotientAndRemainder(-5)), [-6, -3]); 31 | }); 32 | 33 | it('Negative dividend, negative divisor', () => { 34 | TEUtils.deepStrictEqual(pipe(-27, MNumber.quotientAndRemainder(-5)), [5, -2]); 35 | }); 36 | }); 37 | 38 | describe('equals', () => { 39 | it('Passing', () => { 40 | TEUtils.assertTrue(pipe(0.3, MNumber.equals(0.1 + 0.2))); 41 | }); 42 | 43 | it('Not passing', () => { 44 | TEUtils.assertFalse(pipe(0.4, MNumber.equals(0.1 + 0.2))); 45 | }); 46 | }); 47 | 48 | describe('trunc', () => { 49 | it('Number that does not need to be truncated', () => { 50 | TEUtils.assertTrue(pipe(54.5, MNumber.trunc(2), MNumber.equals(54.5))); 51 | }); 52 | 53 | it('Positive number, first following digit < 5', () => { 54 | TEUtils.assertTrue(pipe(0.544, MNumber.trunc(2), MNumber.equals(0.54))); 55 | }); 56 | 57 | it('Positive number, first following digit >= 5', () => { 58 | TEUtils.assertTrue(pipe(0.545, MNumber.trunc(2), MNumber.equals(0.54))); 59 | }); 60 | 61 | it('Negative number, first following digit < 5', () => { 62 | TEUtils.assertTrue(pipe(-0.544, MNumber.trunc(2), MNumber.equals(-0.54))); 63 | }); 64 | 65 | it('Negative number, first following digit >= 5', () => { 66 | TEUtils.assertTrue(pipe(-0.545, MNumber.trunc(2), MNumber.equals(-0.54))); 67 | }); 68 | }); 69 | 70 | describe('isMultipleOf', () => { 71 | it('Passing', () => { 72 | TEUtils.assertTrue(pipe(27, MNumber.isMultipleOf(3))); 73 | }); 74 | 75 | it('Not passing', () => { 76 | TEUtils.assertFalse(pipe(26, MNumber.isMultipleOf(3))); 77 | }); 78 | }); 79 | 80 | describe('shift', () => { 81 | it('Positive shift', () => { 82 | TEUtils.strictEqual(pipe(5.04, MNumber.shift(2)), 504); 83 | }); 84 | 85 | it('Negative shift', () => { 86 | TEUtils.strictEqual(pipe(504, MNumber.shift(-2)), 5.04); 87 | }); 88 | }); 89 | }); 90 | -------------------------------------------------------------------------------- /packages/effect-lib/tests/PortError.test.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable functional/no-expression-statements */ 2 | import { MPortError } from '@parischap/effect-lib'; 3 | import { TEUtils } from '@parischap/test-utils'; 4 | import { describe, it } from 'vitest'; 5 | 6 | describe('MPortError', () => { 7 | it('moduleTag', () => { 8 | TEUtils.assertSome(TEUtils.moduleTagFromTestFilePath(__filename), MPortError.moduleTag); 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /packages/effect-lib/tests/Predicate.test.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable functional/no-expression-statements */ 2 | import { MPredicate, MTypes } from '@parischap/effect-lib'; 3 | import { TEUtils } from '@parischap/test-utils'; 4 | import { pipe, Predicate } from 'effect'; 5 | import { describe, it } from 'vitest'; 6 | 7 | /** Source */ 8 | MTypes.areEqualTypes>, number>() satisfies true; 9 | MTypes.areEqualTypes>, number>() satisfies true; 10 | 11 | /** Target */ 12 | MTypes.areEqualTypes>, number>() satisfies true; 13 | MTypes.areEqualTypes>, 5>() satisfies true; 14 | 15 | /** Coverage */ 16 | MTypes.areEqualTypes>, never>() satisfies true; 17 | MTypes.areEqualTypes>, 5>() satisfies true; 18 | 19 | /** PredicatesToSources */ 20 | MTypes.areEqualTypes< 21 | MPredicate.PredicatesToSources< 22 | readonly [Predicate.Predicate, Predicate.Refinement] 23 | >, 24 | readonly [number, boolean] 25 | >() satisfies true; 26 | 27 | /** PredicatesToTargets */ 28 | MTypes.areEqualTypes< 29 | MPredicate.PredicatesToTargets< 30 | readonly [Predicate.Predicate, Predicate.Refinement] 31 | >, 32 | readonly [number, true] 33 | >() satisfies true; 34 | 35 | /** PredicatesToCoverages */ 36 | MTypes.areEqualTypes< 37 | MPredicate.PredicatesToCoverages< 38 | readonly [Predicate.Predicate, Predicate.Refinement] 39 | >, 40 | readonly [never, true] 41 | >() satisfies true; 42 | 43 | /** SourcesToPredicates */ 44 | MTypes.areEqualTypes< 45 | MPredicate.SourcesToPredicates, 46 | readonly [Predicate.Predicate, Predicate.Predicate] 47 | >() satisfies true; 48 | MTypes.areEqualTypes< 49 | MPredicate.SourcesToPredicates<{ readonly a: number; readonly b: boolean }>, 50 | { readonly a: Predicate.Predicate; readonly b: Predicate.Predicate } 51 | >() satisfies true; 52 | 53 | describe('MPredicate', () => { 54 | describe('struct', () => { 55 | it('Type error expected', () => { 56 | /* @ts-expect-error c not present in object */ 57 | TEUtils.assertFalse(pipe({ a: 0, b: 1 }, MPredicate.struct({ c: Predicate.isNumber }))); 58 | }); 59 | 60 | it('Passing', () => { 61 | TEUtils.assertTrue( 62 | pipe( 63 | { a: 0, b: 1, c: 2 }, 64 | MPredicate.struct({ b: Predicate.isNumber, c: Predicate.isNumber }) 65 | ) 66 | ); 67 | }); 68 | 69 | it('Failing', () => { 70 | TEUtils.assertFalse( 71 | pipe( 72 | { a: 0, b: 1, c: 2 }, 73 | MPredicate.struct({ b: Predicate.isNumber, c: Predicate.isString }) 74 | ) 75 | ); 76 | }); 77 | }); 78 | }); 79 | -------------------------------------------------------------------------------- /packages/effect-lib/tests/Record.test.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable functional/no-expression-statements */ 2 | import { MRecord, MTypes } from '@parischap/effect-lib'; 3 | import { TEUtils } from '@parischap/test-utils'; 4 | import { Option, pipe } from 'effect'; 5 | import { describe, it } from 'vitest'; 6 | 7 | describe('MRecord', () => { 8 | it('unsafeGet', () => { 9 | TEUtils.strictEqual(pipe({ a: 1, b: true }, MRecord.unsafeGet('a')), 1); 10 | }); 11 | 12 | describe('tryZeroParamFunction', () => { 13 | it('Object with default prototype', () => { 14 | TEUtils.assertNone( 15 | pipe( 16 | { a: 5 }, 17 | MRecord.tryZeroParamFunction({ 18 | functionName: 'toString', 19 | /* eslint-disable-next-line @typescript-eslint/unbound-method */ 20 | exception: Object.prototype.toString 21 | }) 22 | ) 23 | ); 24 | }); 25 | 26 | it('getDay on Date object', () => { 27 | TEUtils.assertSome( 28 | pipe( 29 | new Date(0), 30 | MRecord.tryZeroParamFunction({ 31 | functionName: 'getDay' 32 | }), 33 | Option.filter(MTypes.isNumber) 34 | ), 35 | 4 36 | ); 37 | }); 38 | }); 39 | 40 | describe('tryZeroParamStringFunction', () => { 41 | it('getDay on Date object', () => { 42 | TEUtils.assertNone( 43 | pipe( 44 | new Date(0), 45 | MRecord.tryZeroParamStringFunction({ 46 | functionName: 'getDay' 47 | }) 48 | ) 49 | ); 50 | }); 51 | 52 | it('toString on Date object', () => { 53 | TEUtils.assertTrue( 54 | pipe( 55 | new Date(), 56 | MRecord.tryZeroParamStringFunction({ 57 | functionName: 'toString', 58 | /* eslint-disable-next-line @typescript-eslint/unbound-method */ 59 | exception: Object.prototype.toString 60 | }), 61 | Option.isSome 62 | ) 63 | ); 64 | }); 65 | }); 66 | }); 67 | -------------------------------------------------------------------------------- /packages/effect-lib/tests/RegExp.test.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable functional/no-expression-statements */ 2 | import { MRegExp } from '@parischap/effect-lib'; 3 | import { TEUtils } from '@parischap/test-utils'; 4 | import { pipe, Tuple } from 'effect'; 5 | import { describe, it } from 'vitest'; 6 | 7 | describe('MRegExp', () => { 8 | const regExp = /af(o)o(bar)?a/; 9 | 10 | describe('match', () => { 11 | it('Matching', () => { 12 | TEUtils.assertSome(pipe(regExp, MRegExp.match('afooa')), 'afooa'); 13 | TEUtils.assertSome(pipe(regExp, MRegExp.match('afoobara')), 'afoobara'); 14 | }); 15 | 16 | it('Non matching', () => { 17 | TEUtils.assertNone(pipe(regExp, MRegExp.match('afoob'))); 18 | }); 19 | }); 20 | 21 | describe('matchAndGroups', () => { 22 | it('Matching', () => { 23 | TEUtils.assertSome( 24 | pipe(regExp, MRegExp.matchAndGroups('afooa', 2)), 25 | Tuple.make('afooa', Tuple.make('o', '')) 26 | ); 27 | }); 28 | 29 | it('Non matching', () => { 30 | TEUtils.assertNone(MRegExp.match('afoob')(regExp)); 31 | }); 32 | }); 33 | 34 | describe('capturedGroups', () => { 35 | it('Matching', () => { 36 | TEUtils.assertSome(pipe(regExp, MRegExp.capturedGroups('afooa', 2)), Tuple.make('o', '')); 37 | }); 38 | }); 39 | }); 40 | -------------------------------------------------------------------------------- /packages/effect-lib/tests/Tuple.test.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable functional/no-expression-statements */ 2 | import { MTuple } from '@parischap/effect-lib'; 3 | import { TEUtils } from '@parischap/test-utils'; 4 | import { Array, Number, pipe, Tuple } from 'effect'; 5 | import { describe, it } from 'vitest'; 6 | 7 | describe('MTuple', () => { 8 | describe('make', () => { 9 | it('With Array.map', () => { 10 | TEUtils.deepStrictEqual( 11 | pipe(Array.make(1, 2), Array.map(MTuple.fromSingleValue)), 12 | Array.make(Tuple.make(1), Tuple.make(2)) 13 | ); 14 | }); 15 | }); 16 | 17 | describe('makeBoth', () => { 18 | it('From number', () => { 19 | TEUtils.deepStrictEqual(MTuple.makeBoth(1), Tuple.make(1, 1)); 20 | }); 21 | }); 22 | 23 | describe('makeBothBy', () => { 24 | it('From number', () => { 25 | TEUtils.deepStrictEqual( 26 | MTuple.makeBothBy({ toFirst: Number.sum(1), toSecond: Number.multiply(2) })(1), 27 | Tuple.make(2, 2) 28 | ); 29 | }); 30 | }); 31 | 32 | describe('prepend', () => { 33 | it('From number', () => { 34 | TEUtils.deepStrictEqual(pipe(1, Tuple.make, MTuple.prependElement(2)), Tuple.make(2, 1)); 35 | }); 36 | }); 37 | 38 | describe('firstTwo', () => { 39 | it('From number tuple', () => { 40 | TEUtils.deepStrictEqual(MTuple.firstTwo(Tuple.make(1, 2, 3)), Tuple.make(1, 2)); 41 | }); 42 | }); 43 | }); 44 | -------------------------------------------------------------------------------- /packages/effect-lib/tsconfig.base.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "_version": "20.1.0", 4 | "extends": [ 5 | "@tsconfig/strictest/tsconfig.json", 6 | "@tsconfig/node20/tsconfig.json" 7 | ], 8 | "compilerOptions": { 9 | "allowJs": false, 10 | "checkJs": false, 11 | "moduleDetection": "force", 12 | "composite": true, 13 | "resolveJsonModule": true, 14 | "esModuleInterop": false, 15 | "declaration": true, 16 | "skipLibCheck": true, 17 | "emitDecoratorMetadata": true, 18 | "experimentalDecorators": true, 19 | "moduleResolution": "NodeNext", 20 | "isolatedModules": true, 21 | "sourceMap": true, 22 | "declarationMap": true, 23 | "noEmitOnError": false, 24 | "noErrorTruncation": true, 25 | "target": "ES2022", 26 | "module": "NodeNext", 27 | "incremental": true, 28 | "removeComments": false, 29 | "lib": [], 30 | "types": [], 31 | "plugins": [{ "name": "@effect/language-service" }] 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /packages/effect-lib/tsconfig.check.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.esm.json", 3 | "compilerOptions": { 4 | "noEmit": true, 5 | "tsBuildInfoFile": ".tsbuildinfo/check.tsbuildinfo" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/effect-lib/tsconfig.docgen.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.base.json", 3 | "include": ["**/*.ts", "**/*.mts", "**/*.cts"], 4 | "compilerOptions": { "noEmit": true, "lib": ["ESNext"], "types": ["node"] } 5 | } 6 | -------------------------------------------------------------------------------- /packages/effect-lib/tsconfig.esm.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.base.json", 3 | "include": ["esm/**/*.ts", "esm/**/*.mts", "esm/**/*.cts"], 4 | "compilerOptions": { 5 | "rootDir": "esm", 6 | "tsBuildInfoFile": ".tsbuildinfo/esm.tsbuildinfo", 7 | "declarationDir": "dist/dts", 8 | "declarationMap": true, 9 | "outDir": "dist/esm", 10 | "lib": ["ESNext"] 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /packages/effect-lib/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.base.json", 3 | "include": [], 4 | "references": [ 5 | { "path": "./tsconfig.esm.json" }, 6 | { "path": "./tsconfig.others.json" } 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /packages/effect-lib/tsconfig.others.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.base.json", 3 | "include": [ 4 | "**/*.ts", 5 | "**/*.mts", 6 | "**/*.cts", 7 | "**/*.js", 8 | "**/*.mjs", 9 | "**/*.cjs" 10 | ], 11 | "exclude": [ 12 | "esm/**/*.ts", 13 | "esm/**/*.mts", 14 | "esm/**/*.cts", 15 | "**/node_modules/**/*.ts", 16 | "**/node_modules/**/*.mts", 17 | "**/node_modules/**/*.cts", 18 | "**/node_modules/**/*.js", 19 | "**/node_modules/**/*.mjs", 20 | "**/node_modules/**/*.cjs", 21 | "dist/**/*.ts", 22 | "dist/**/*.mts", 23 | "dist/**/*.cts", 24 | "dist/**/*.js", 25 | "dist/**/*.mjs", 26 | "dist/**/*.cjs", 27 | "vite.config.ts.timestamp-*.mjs" 28 | ], 29 | "compilerOptions": { 30 | "rootDir": ".", 31 | "tsBuildInfoFile": ".tsbuildinfo/others.tsbuildinfo", 32 | "outDir": "dist/others", 33 | "lib": ["ESNext"], 34 | "types": ["node"], 35 | "allowJs": true, 36 | "checkJs": true 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /packages/effect-lib/vite.config.ts: -------------------------------------------------------------------------------- 1 | import type { UserConfig } from 'vite'; 2 | 3 | export default { 4 | test: { 5 | include: ['./tests/*.test.ts'], 6 | isolate: false, 7 | fileParallelism: false, 8 | pool: 'threads' 9 | } 10 | } satisfies UserConfig; 11 | -------------------------------------------------------------------------------- /packages/effect-report/.gitignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | node_modules/ 3 | vite.config.ts.timestamp-*.mjs 4 | /.tsbuildinfo/ 5 | /*.old -------------------------------------------------------------------------------- /packages/effect-report/.madgerc: -------------------------------------------------------------------------------- 1 | { 2 | "detectiveOptions": { 3 | "ts": { 4 | "skipTypeImports": true 5 | } 6 | } 7 | } -------------------------------------------------------------------------------- /packages/effect-report/.prettierignore: -------------------------------------------------------------------------------- 1 | # Same rules as .git 2 | node_modules/ -------------------------------------------------------------------------------- /packages/effect-report/eslint.config.js: -------------------------------------------------------------------------------- 1 | import { eslintConfigNode } from '@parischap/configs'; 2 | 3 | export default eslintConfigNode; 4 | -------------------------------------------------------------------------------- /packages/effect-report/esm/Cause.ts: -------------------------------------------------------------------------------- 1 | import { MArray, MFs, MString, MTypes } from '@parischap/effect-lib'; 2 | import { Array, Cause, FiberId, Function, String, flow, pipe } from 'effect'; 3 | import * as RErrors from './Errors.js'; 4 | import { formatError } from './utils.js'; 5 | 6 | export const toString = 7 | ({ 8 | eol, 9 | pathSep, 10 | stringify, 11 | tabChar, 12 | thisProgramPath 13 | }: { 14 | readonly eol: string; 15 | readonly pathSep: string; 16 | readonly stringify: (u: unknown) => string; 17 | readonly tabChar: string; 18 | readonly thisProgramPath: MFs.Folderpath; 19 | }) => 20 | (self: Cause.Cause): string => { 21 | const formatUnknownError = (title: string) => (error: unknown) => 22 | Array.of( 23 | error instanceof RErrors.WithOriginalCause ? 24 | pipe( 25 | error.originalCause, 26 | toString({ eol, pathSep, stringify, tabChar, thisProgramPath }) 27 | ) + 28 | eol + 29 | JsANSI.yellow(`Rethrown in:${error.message}`) 30 | : JsANSI.red(title) + 31 | eol + 32 | JsString.tabify(tabChar)( 33 | MTypes.isErrorish(error) ? 34 | formatError(error, { 35 | eol, 36 | pathSep, 37 | stringify, 38 | tabChar 39 | }) 40 | : stringify(error) 41 | ) 42 | ); 43 | return pipe( 44 | Cause.match(self, { 45 | onEmpty: Array.of(''), 46 | onFail: formatUnknownError('SCRIPT ERRORED WITH:'), 47 | onDie: formatUnknownError('SCRIPT DIED WITH FOLLOWING DEFECT:'), 48 | onInterrupt: (fiberId) => 49 | Array.of(JsANSI.red(`FIBER ${FiberId.threadName(fiberId)} WAS INTERRUPTED`)), 50 | onSequential: (left, right) => Array.appendAll(left, right), 51 | onParallel: (left, right) => Array.appendAll(left, right) 52 | }), 53 | Array.filter(String.isNonEmpty), 54 | MArray.match012({ 55 | onEmpty: () => '', 56 | onSingleton: Function.identity, 57 | onOverTwo: flow( 58 | Array.map(flow(MString.prepend('- '), JsString.tabify(tabChar))), 59 | Array.join(eol) 60 | ) 61 | }) 62 | ); 63 | }; 64 | -------------------------------------------------------------------------------- /packages/effect-report/esm/Effect.ts: -------------------------------------------------------------------------------- 1 | import { ASStyle } from '@parischap/ansi-styles'; 2 | import { MFs } from '@parischap/effect-lib'; 3 | import { Effect, flow, pipe } from 'effect'; 4 | import * as MCause from './Cause.js'; 5 | import * as RErrors from './Errors.js'; 6 | import * as RLogger from './Logger.js'; 7 | 8 | export const presentAndWrapErrors = ( 9 | message: string 10 | ): ((self: Effect.Effect) => Effect.Effect) => 11 | flow( 12 | Effect.tap(() => RLogger.logDebugTitle(message)), 13 | Effect.withLogSpan(message), 14 | Effect.catchAllCause((c) => 15 | Effect.die( 16 | new RErrors.WithOriginalCause({ 17 | message, 18 | originalCause: c 19 | }) 20 | ) 21 | ) 22 | ); 23 | 24 | export const logIntermediateResult = 25 | (message: (s: string) => string) => 26 | (self: Effect.Effect): Effect.Effect => 27 | Effect.andThen(self, (result) => 28 | Effect.gen(function* () { 29 | yield* pipe(Effect.logDebug('')); 30 | yield* pipe(Effect.logDebug(message(result))); 31 | return result; 32 | }) 33 | ); 34 | 35 | export const presentAndShowErrors = 36 | ({ 37 | eol, 38 | message, 39 | pathSep, 40 | stringify, 41 | tabChar, 42 | thisProgramPath 43 | }: { 44 | readonly eol: string; 45 | readonly message: string; 46 | readonly pathSep: string; 47 | readonly stringify: (u: unknown) => string; 48 | readonly tabChar: string; 49 | readonly thisProgramPath: MFs.Folderpath; 50 | }) => 51 | (self: Effect.Effect): Effect.Effect => 52 | pipe( 53 | self, 54 | Effect.withLogSpan(message), 55 | Effect.catchAllCause((c) => 56 | pipe( 57 | c, 58 | MCause.toString({ 59 | eol, 60 | pathSep, 61 | stringify, 62 | tabChar, 63 | thisProgramPath 64 | }), 65 | (errorText) => { 66 | // Do not use Effect.log here because it has a special formatting 67 | if (errorText.trim() === '') { 68 | /* eslint-disable-next-line functional/no-expression-statements */ 69 | console.log(ASStyle.green('SCRIPT EXITED SUCCESSFULLY')); 70 | } else { 71 | /* eslint-disable-next-line functional/no-expression-statements */ 72 | console.error(ASStyle.red('SCRIPT FAILED WITH FOLLOWING ERROR(S)'), eol + errorText); 73 | } 74 | return Effect.void; 75 | } 76 | ) 77 | ) 78 | ); 79 | -------------------------------------------------------------------------------- /packages/effect-report/esm/Errors.ts: -------------------------------------------------------------------------------- 1 | import { Cause, Data } from 'effect'; 2 | 3 | // Data.TaggedError extends Error and may therefore have a stack trace 4 | // Data.TaggedError extends Error and therefore is Errorish 5 | // Data.TaggedError extends YieldableError and may therefore be yielded directly 6 | 7 | /** 8 | * This error is meant to be handled by a human being (no action triggered like a retry on HTTP Error). The message must give sufficient context to help identify the origin the error 9 | */ 10 | export class General extends Data.TaggedError('General')<{ 11 | readonly message: string; 12 | }> {} 13 | 14 | /** 15 | * This error is meant to be rethrown in an Effect.catchAllCause 16 | */ 17 | export class WithOriginalCause extends Data.TaggedError('WithOriginalCause')<{ 18 | readonly message: string; 19 | readonly originalCause: Cause.Cause; 20 | }> {} 21 | -------------------------------------------------------------------------------- /packages/effect-report/esm/Logger.ts: -------------------------------------------------------------------------------- 1 | import { MString, MTypes } from '@parischap/effect-lib'; 2 | import { Array, Effect, FiberId, List, LogLevel, Logger, Option, String, pipe } from 'effect'; 3 | 4 | //const moduleTag = '@parischap/effect-lib/Logger/'; 5 | 6 | class TitleMessage { 7 | readonly title: string; 8 | 9 | constructor({ title }: MTypes.Data) { 10 | this.title = title; 11 | } 12 | } 13 | 14 | export const logDebugTitle = (title: string): Effect.Effect => 15 | Effect.logDebug(new TitleMessage({ title })); 16 | export const logInfoTitle = (title: string): Effect.Effect => 17 | Effect.logInfo(new TitleMessage({ title })); 18 | 19 | const colorizeByLogLevel = (logLevel: LogLevel.LogLevel): ((s: string) => string) => { 20 | switch (logLevel) { 21 | case LogLevel.Fatal: 22 | case LogLevel.Error: 23 | return JsANSI.red; 24 | case LogLevel.Warning: 25 | return JsANSI.yellow; 26 | default: 27 | return JsANSI.green; 28 | } 29 | }; 30 | 31 | const colorizeBySpanLevel = (spanLevel: number): ((s: string) => string) => 32 | pipe( 33 | Array.make(JsANSI.cyan, JsANSI.yellow, JsANSI.magenta), 34 | Array.get(spanLevel), 35 | Option.getOrElse(() => JsANSI.blue) 36 | ); 37 | 38 | export const live = ({ 39 | eol, 40 | startTime, 41 | stringify, 42 | tabChar 43 | }: { 44 | readonly eol: string; 45 | readonly startTime: number; 46 | readonly stringify: (u: unknown) => string; 47 | readonly tabChar: string; 48 | }) => 49 | Logger.replace( 50 | Logger.defaultLogger, 51 | Logger.make(({ date, fiberId, logLevel, message, spans }) => { 52 | try { 53 | const isString = MTypes.isString(message); 54 | // Don't show time and threadName if we print an empty message 55 | if (isString && String.isEmpty(message.trim())) console.log(''); 56 | else { 57 | const isTitle = message instanceof TitleMessage; 58 | 59 | const spanLevel = List.size(spans); 60 | 61 | const latestSpanCreationTime = pipe( 62 | spans, 63 | List.head, 64 | Option.map((span) => span.startTime), 65 | Option.getOrElse(() => startTime) 66 | ); 67 | 68 | const strMessage = 69 | isTitle ? message.title 70 | : isString ? message 71 | : stringify(message); 72 | 73 | const isMultiLine = JsString.isMultiLine(strMessage); 74 | 75 | console.log( 76 | pipe( 77 | (isMultiLine ? 'Multi-line message' : strMessage) + 78 | ` (${FiberId.threadName(fiberId)})`, 79 | isTitle ? colorizeBySpanLevel(spanLevel) : JsANSI.highContrastBlack, 80 | MString.prepend( 81 | colorizeByLogLevel(logLevel)(`${date.getTime() - latestSpanCreationTime}ms `) 82 | ), 83 | String.concat(isMultiLine ? eol + JsString.tabify(tabChar)(strMessage) : ''), 84 | JsString.tabify(tabChar, spanLevel) 85 | ) 86 | ); 87 | } 88 | } catch (e) { 89 | console.log( 90 | JsANSI.red('Logging error' + eol + (MTypes.isErrorish(e) ? e.message : stringify(e))) 91 | ); 92 | } 93 | }) 94 | ); 95 | -------------------------------------------------------------------------------- /packages/effect-report/esm/Schema.ts: -------------------------------------------------------------------------------- 1 | import { AST, Schema } from '@effect/schema'; 2 | import { MSchema } from '@parischap/effect-lib'; 3 | import * as RErrors from './Errors.js'; 4 | 5 | import { Effect } from 'effect'; 6 | 7 | export type CompiledParser = ( 8 | i: unknown, 9 | overrideoptions?: AST.ParseOptions 10 | ) => Effect.Effect; 11 | 12 | // Parsing 13 | export const makeCompiledParser = ({ 14 | eol, 15 | schema, 16 | tabChar 17 | }: { 18 | readonly eol: string; 19 | readonly schema: Schema.Schema; 20 | readonly tabChar: string; 21 | }): CompiledParser => { 22 | const compiledParser = Schema.decodeUnknown(schema, { 23 | errors: 'all', 24 | onExcessProperty: 'error' 25 | }); 26 | return (i, overrideoptions) => 27 | Effect.catchAll( 28 | compiledParser(i, overrideoptions), 29 | (e) => 30 | new RErrors.General({ 31 | message: 'Effect Schema parsing error' + eol + MSchema.prettyPrintError(e, eol, tabChar) 32 | }) 33 | ); 34 | }; 35 | -------------------------------------------------------------------------------- /packages/effect-report/esm/index.ts: -------------------------------------------------------------------------------- 1 | export * as RCause from './Cause.js'; 2 | export * as REffect from './Effect.js'; 3 | export * as RErrors from './Errors.js'; 4 | export * as RLogger from './Logger.js'; 5 | export * as RSchema from './Schema.js'; 6 | export * as RUtils from './utils.js'; 7 | 8 | -------------------------------------------------------------------------------- /packages/effect-report/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@parischap-dev/effect-report", 3 | "type": "module", 4 | "author": "Jérôme MARTIN", 5 | "license": "MIT", 6 | "scripts": { 7 | "tscheck": "tsc -b tsconfig.check.json --force", 8 | "lint": "eslint .", 9 | "lint-fix": "eslint . --fix", 10 | "lint-rules": "pnpx @eslint/config-inspector", 11 | "update-config-files": "update-config-files", 12 | "clean-config-files": "shx rm -rf package.json && shx rm -rf tsconfig.json", 13 | "transpile-esm": "tsc -b tsconfig.esm.json", 14 | "transpile-cjs": "babel dist/esm --plugins @babel/transform-export-namespace-from --plugins @babel/transform-modules-commonjs --out-dir dist/cjs --source-maps", 15 | "transpile-annotate": "babel dist --plugins annotate-pure-calls --out-dir dist --source-maps", 16 | "compile": "pnpm transpile-esm && pnpm transpile-cjs && pnpm transpile-annotate && pnpm prodify", 17 | "build-and-publish": "echo \"private package cannot be published\"", 18 | "docgen": "echo \"docgen not activated for this package\"", 19 | "circular": "madge --extensions ts --circular --no-color --no-spinner esm", 20 | "checks": "pnpm circular && pnpm lint && pnpm tscheck && pnpm test", 21 | "test": "vitest run", 22 | "clean-prod": "shx rm -rf dist && shx rm -rf .tsbuildinfo && shx mkdir -p dist", 23 | "publish-to-npm": "cd dist && npm publish --access=public && cd ..", 24 | "install-prod": "cd dist && pnpm i && cd ..", 25 | "build": "pnpm clean-prod && pnpm --if-present pre-build && pnpm compile && pnpm --if-present post-build && pnpm --if-present generate-types && pnpm install-prod", 26 | "prodify": "prodify", 27 | "examples": "" 28 | }, 29 | "publishConfig": { 30 | "main": "./cjs/index.js", 31 | "types": "./dts/index.d.ts", 32 | "exports": { 33 | ".": { 34 | "default": "./cjs/index.js", 35 | "import": "./esm/index.js", 36 | "types": "./dts/index.d.ts" 37 | } 38 | }, 39 | "scripts": {}, 40 | "devDependencies": {}, 41 | "peerDependencies": { 42 | "@parischap/effect-lib": "^0.5.0", 43 | "@parischap/ansi-styles": "^0.2.1", 44 | "effect": "^3.15.4" 45 | }, 46 | "publishConfig": {}, 47 | "packageManager": "", 48 | "pnpm": {}, 49 | "type": "" 50 | }, 51 | "private": true, 52 | "description": "A functional library to prettify Effect logging", 53 | "module": "./esm/index.js", 54 | "exports": { 55 | ".": { 56 | "import": "./esm/index.ts" 57 | } 58 | }, 59 | "dependencies": {}, 60 | "devDependencies": { 61 | "@parischap/effect-report": "link:." 62 | }, 63 | "peerDependencies": { 64 | "@parischap/effect-lib": "workspace:@parischap-dev/effect-lib@*", 65 | "@parischap/ansi-styles": "workspace:@parischap-dev/ansi-styles@*", 66 | "effect": "^3.15.4" 67 | }, 68 | "repository": { 69 | "type": "git", 70 | "url": "git+https://github.com/parischap/effect-libs.git", 71 | "directory": "packages/effect-report" 72 | }, 73 | "homepage": "https://github.com/parischap/effect-libs/tree/master/packages/effect-report" 74 | } 75 | -------------------------------------------------------------------------------- /packages/effect-report/prettier.config.js: -------------------------------------------------------------------------------- 1 | import * as Configs from '@parischap/configs'; 2 | 3 | export default Configs.prettierconfigBase; 4 | -------------------------------------------------------------------------------- /packages/effect-report/project.config.js: -------------------------------------------------------------------------------- 1 | import * as Configs from '@parischap/configs'; 2 | 3 | export default Configs.configSubRepo({ 4 | description: 'A functional library to prettify Effect logging', 5 | dependencies: {}, 6 | devDependencies: {}, 7 | internalPeerDependencies: { 8 | 'effect-lib': Configs.constants.effectLibVersion, 9 | 'ansi-styles': Configs.constants.ansiStylesVersion 10 | }, 11 | externalPeerDependencies: { 12 | effect: Configs.constants.effectVersion 13 | }, 14 | examples: [], 15 | scripts: {}, 16 | environment: Configs.Environment.Type.Node, 17 | bundled: false, 18 | visibility: Configs.Visibility.Type.Private, 19 | hasStaticFolder: false, 20 | hasDocGen: false, 21 | keywords: [] 22 | }); 23 | -------------------------------------------------------------------------------- /packages/effect-report/tests/dummy.test.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable functional/no-expression-statements */ 2 | import { describe, it } from 'vitest'; 3 | 4 | describe('Dummy', () => { 5 | it('dummy', () => { 6 | TEUtils.strictEqual(1,1); 7 | }); 8 | }); 9 | -------------------------------------------------------------------------------- /packages/effect-report/tsconfig.base.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "_version": "20.1.0", 4 | "extends": [ 5 | "@tsconfig/strictest/tsconfig.json", 6 | "@tsconfig/node20/tsconfig.json" 7 | ], 8 | "compilerOptions": { 9 | "allowJs": false, 10 | "checkJs": false, 11 | "moduleDetection": "force", 12 | "composite": true, 13 | "resolveJsonModule": true, 14 | "esModuleInterop": false, 15 | "declaration": true, 16 | "skipLibCheck": true, 17 | "emitDecoratorMetadata": true, 18 | "experimentalDecorators": true, 19 | "moduleResolution": "NodeNext", 20 | "isolatedModules": true, 21 | "sourceMap": true, 22 | "declarationMap": true, 23 | "noEmitOnError": false, 24 | "noErrorTruncation": true, 25 | "target": "ES2022", 26 | "module": "NodeNext", 27 | "incremental": true, 28 | "removeComments": false, 29 | "lib": [], 30 | "types": [], 31 | "plugins": [{ "name": "@effect/language-service" }] 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /packages/effect-report/tsconfig.check.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.esm.json", 3 | "compilerOptions": { 4 | "noEmit": true, 5 | "tsBuildInfoFile": ".tsbuildinfo/check.tsbuildinfo" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/effect-report/tsconfig.esm.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.base.json", 3 | "include": ["esm/**/*.ts", "esm/**/*.mts", "esm/**/*.cts"], 4 | "compilerOptions": { 5 | "rootDir": "esm", 6 | "tsBuildInfoFile": ".tsbuildinfo/esm.tsbuildinfo", 7 | "declarationDir": "dist/dts", 8 | "declarationMap": true, 9 | "outDir": "dist/esm", 10 | "lib": ["ESNext"], 11 | "types": ["node"] 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /packages/effect-report/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.base.json", 3 | "include": [], 4 | "references": [ 5 | { "path": "./tsconfig.esm.json" }, 6 | { "path": "./tsconfig.others.json" } 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /packages/effect-report/tsconfig.others.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.base.json", 3 | "include": [ 4 | "**/*.ts", 5 | "**/*.mts", 6 | "**/*.cts", 7 | "**/*.js", 8 | "**/*.mjs", 9 | "**/*.cjs" 10 | ], 11 | "exclude": [ 12 | "esm/**/*.ts", 13 | "esm/**/*.mts", 14 | "esm/**/*.cts", 15 | "**/node_modules/**/*.ts", 16 | "**/node_modules/**/*.mts", 17 | "**/node_modules/**/*.cts", 18 | "**/node_modules/**/*.js", 19 | "**/node_modules/**/*.mjs", 20 | "**/node_modules/**/*.cjs", 21 | "dist/**/*.ts", 22 | "dist/**/*.mts", 23 | "dist/**/*.cts", 24 | "dist/**/*.js", 25 | "dist/**/*.mjs", 26 | "dist/**/*.cjs", 27 | "vite.config.ts.timestamp-*.mjs" 28 | ], 29 | "compilerOptions": { 30 | "rootDir": ".", 31 | "tsBuildInfoFile": ".tsbuildinfo/others.tsbuildinfo", 32 | "outDir": "dist/others", 33 | "lib": ["ESNext"], 34 | "types": ["node"], 35 | "allowJs": true, 36 | "checkJs": true 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /packages/effect-report/vite.config.ts: -------------------------------------------------------------------------------- 1 | import type { UserConfig } from 'vite'; 2 | 3 | export default { 4 | test: { 5 | include: ['./tests/*.test.ts'], 6 | isolate: false, 7 | fileParallelism: false, 8 | pool: 'threads' 9 | } 10 | } satisfies UserConfig; 11 | -------------------------------------------------------------------------------- /packages/node-effect-lib/.gitignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | node_modules/ 3 | vite.config.ts.timestamp-*.mjs 4 | /.tsbuildinfo/ 5 | /*.old -------------------------------------------------------------------------------- /packages/node-effect-lib/.madgerc: -------------------------------------------------------------------------------- 1 | { 2 | "detectiveOptions": { 3 | "ts": { 4 | "skipTypeImports": true 5 | } 6 | } 7 | } -------------------------------------------------------------------------------- /packages/node-effect-lib/.prettierignore: -------------------------------------------------------------------------------- 1 | # Same rules as .git 2 | node_modules/ -------------------------------------------------------------------------------- /packages/node-effect-lib/.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "vitest.disableWorkspaceWarning": true 3 | } -------------------------------------------------------------------------------- /packages/node-effect-lib/eslint.config.js: -------------------------------------------------------------------------------- 1 | import { eslintConfigLibrary } from '@parischap/configs'; 2 | 3 | export default eslintConfigLibrary; 4 | -------------------------------------------------------------------------------- /packages/node-effect-lib/esm/Stream.ts1: -------------------------------------------------------------------------------- 1 | import { BinaryLike, CipherKey, createDecipheriv } from 'crypto'; 2 | import { Effect, Stream, pipe } from 'effect'; 3 | 4 | /** 5 | * ATTENTION: ReadableStream as defined in javascript for the browser is not the same as stream.Readable as defined in nodejs. Same for WritableStream. The Effect.fromReadableStream method refers to a web ReadableStream 6 | */ 7 | 8 | const moduleTag = '@mjljm/node-effect-lib/NStream/'; 9 | 10 | export const feedWritableStream = 11 | (s: WritableStream) => 12 | (self: Stream.Stream): Effect.Effect => 13 | pipe( 14 | Effect.acquireRelease( 15 | Effect.sync(() => s.getWriter()), 16 | (writer) => 17 | Effect.andThen( 18 | Effect.promise(() => writer.ready), 19 | Effect.promise(() => writer.close()) 20 | ) 21 | ), 22 | Effect.flatMap((writer) => 23 | pipe( 24 | self, 25 | Stream.runForEach((a) => 26 | Effect.andThen( 27 | Effect.promise(() => writer.ready), 28 | Effect.promise(() => writer.write(a)) 29 | ) 30 | ) 31 | ) 32 | ), 33 | Effect.scoped 34 | ); 35 | 36 | export const decipheriv = 37 | (algorithm: string, key: CipherKey, iv: BinaryLike | null) => 38 | (self: Stream.Stream): Stream.Stream => 39 | Stream.unwrap( 40 | Effect.gen(function* (_) { 41 | const decipher = yield* _( 42 | Effect.try({ 43 | try: () => createDecipheriv(algorithm, key, iv), 44 | catch: (error) => 45 | new MFunctionPortError.Type({ 46 | originalError: error, 47 | originalFunctionName: 'crypto.createDecipheriv', 48 | moduleName: moduleTag, 49 | libraryName: 'node-effect-lib' 50 | }) 51 | }) 52 | ); 53 | const decipheredStream = Stream.fromReadableStream( 54 | () => decipher, 55 | (error) => 56 | new MFunctionPortError.Type({ 57 | originalError: error, 58 | originalFunctionName: 'crypto.createDecipheriv.getReader', 59 | moduleName: moduleTag, 60 | libraryName: 'node-effect-lib' 61 | }) 62 | ); 63 | 64 | const essai = Stream.asyncInterrupt((emit) => { 65 | while () 66 | 67 | watcher.on('change', (eventType, filename) => void emit.single([eventType, filename as string])); 68 | watcher.on( 69 | 'error', 70 | (error) => 71 | void emit.die( 72 | new MFunctionPortError.Type({ 73 | originalError: error, 74 | originalFunctionName: 'fs.watch', 75 | moduleName: moduleTag, 76 | libraryName: 'node-effect-lib' 77 | }) 78 | ) 79 | ); 80 | watcher.on('close', () => void emit.end()); 81 | 82 | return Either.left(Effect.succeed(watcher.close())); 83 | }); 84 | return decipheredStream; 85 | }) 86 | ) as never; -------------------------------------------------------------------------------- /packages/node-effect-lib/esm/index.ts: -------------------------------------------------------------------------------- 1 | export * as NFs from './Fs.js'; 2 | export * as NPath from './Path.js'; 3 | -------------------------------------------------------------------------------- /packages/node-effect-lib/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@parischap-dev/node-effect-lib", 3 | "type": "module", 4 | "author": "Jérôme MARTIN", 5 | "license": "MIT", 6 | "scripts": { 7 | "tscheck": "tsc -b tsconfig.check.json --force", 8 | "lint": "eslint .", 9 | "lint-fix": "eslint . --fix", 10 | "lint-rules": "pnpx @eslint/config-inspector", 11 | "update-config-files": "update-config-files", 12 | "clean-config-files": "shx rm -rf package.json && shx rm -rf tsconfig.json", 13 | "transpile-esm": "tsc -b tsconfig.esm.json", 14 | "transpile-cjs": "babel dist/esm --plugins @babel/transform-export-namespace-from --plugins @babel/transform-modules-commonjs --out-dir dist/cjs --source-maps", 15 | "transpile-annotate": "babel dist --plugins annotate-pure-calls --out-dir dist --source-maps", 16 | "compile": "pnpm transpile-esm && pnpm transpile-cjs && pnpm transpile-annotate && pnpm prodify", 17 | "build-and-publish": "pnpm build && pnpm checks && pnpm publish-to-npm", 18 | "docgen": "echo \"docgen not activated for this package\"", 19 | "circular": "madge --extensions ts --circular --no-color --no-spinner esm", 20 | "checks": "pnpm circular && pnpm lint && pnpm tscheck && pnpm test", 21 | "test": "vitest run", 22 | "clean-prod": "shx rm -rf dist && shx rm -rf .tsbuildinfo && shx mkdir -p dist", 23 | "publish-to-npm": "cd dist && npm publish --access=public && cd ..", 24 | "install-prod": "cd dist && pnpm i && cd ..", 25 | "build": "pnpm clean-prod && pnpm --if-present pre-build && pnpm compile && pnpm --if-present post-build && pnpm --if-present generate-types && pnpm install-prod", 26 | "prodify": "prodify", 27 | "examples": "" 28 | }, 29 | "publishConfig": { 30 | "main": "./cjs/index.js", 31 | "types": "./dts/index.d.ts", 32 | "exports": { 33 | ".": { 34 | "default": "./cjs/index.js", 35 | "import": "./esm/index.js", 36 | "types": "./dts/index.d.ts" 37 | } 38 | }, 39 | "scripts": {}, 40 | "devDependencies": {}, 41 | "peerDependencies": { 42 | "@parischap/effect-lib": "^0.5.0", 43 | "effect": "^3.15.4", 44 | "@effect/platform": "^0.82.7", 45 | "@effect/platform-node": "^0.81.4" 46 | }, 47 | "publishConfig": {}, 48 | "packageManager": "", 49 | "pnpm": {}, 50 | "type": "" 51 | }, 52 | "bugs": { 53 | "url": "https://github.com/parischap/effect-libs/issues" 54 | }, 55 | "funding": [ 56 | { 57 | "type": "ko-fi", 58 | "url": "https://ko-fi.com/parischap" 59 | } 60 | ], 61 | "keywords": [ 62 | "effect", 63 | "typescript", 64 | "functional-programming" 65 | ], 66 | "description": "A complement to the official @effect/platform library with add-ons for Node.js", 67 | "module": "./esm/index.js", 68 | "exports": { 69 | ".": { 70 | "import": "./esm/index.ts" 71 | } 72 | }, 73 | "dependencies": {}, 74 | "devDependencies": { 75 | "@parischap/node-effect-lib": "link:." 76 | }, 77 | "peerDependencies": { 78 | "@parischap/effect-lib": "workspace:@parischap-dev/effect-lib@*", 79 | "effect": "^3.15.4", 80 | "@effect/platform": "^0.82.7", 81 | "@effect/platform-node": "^0.81.4" 82 | }, 83 | "repository": { 84 | "type": "git", 85 | "url": "git+https://github.com/parischap/effect-libs.git", 86 | "directory": "packages/node-effect-lib" 87 | }, 88 | "homepage": "https://github.com/parischap/effect-libs/tree/master/packages/node-effect-lib" 89 | } 90 | -------------------------------------------------------------------------------- /packages/node-effect-lib/prettier.config.js: -------------------------------------------------------------------------------- 1 | import * as Configs from '@parischap/configs'; 2 | 3 | export default Configs.prettierconfigBase; 4 | -------------------------------------------------------------------------------- /packages/node-effect-lib/project.config.js: -------------------------------------------------------------------------------- 1 | import * as Configs from '@parischap/configs'; 2 | 3 | export default Configs.configSubRepo({ 4 | description: 'A complement to the official @effect/platform library with add-ons for Node.js', 5 | dependencies: {}, 6 | devDependencies: {}, 7 | internalPeerDependencies: { 'effect-lib': Configs.constants.effectLibVersion }, 8 | externalPeerDependencies: { 9 | effect: Configs.constants.effectVersion, 10 | '@effect/platform': Configs.constants.effectPlatformVersion, 11 | '@effect/platform-node': Configs.constants.effectPlatformNodeVersion 12 | }, 13 | examples: [], 14 | scripts: {}, 15 | environment: Configs.Environment.Type.Library, 16 | bundled: false, 17 | visibility: Configs.Visibility.Type.Public, 18 | hasStaticFolder: false, 19 | hasDocGen: false, 20 | keywords: [] 21 | }); 22 | -------------------------------------------------------------------------------- /packages/node-effect-lib/tests/dummy.test.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable functional/no-expression-statements */ 2 | import { TEUtils } from '@parischap/test-utils'; 3 | import { describe, it } from 'vitest'; 4 | 5 | describe('Dummy', () => { 6 | it('dummy', () => { 7 | TEUtils.assertTrue(true); 8 | }); 9 | }); 10 | -------------------------------------------------------------------------------- /packages/node-effect-lib/tsconfig.base.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "_version": "20.1.0", 4 | "extends": [ 5 | "@tsconfig/strictest/tsconfig.json", 6 | "@tsconfig/node20/tsconfig.json" 7 | ], 8 | "compilerOptions": { 9 | "allowJs": false, 10 | "checkJs": false, 11 | "moduleDetection": "force", 12 | "composite": true, 13 | "resolveJsonModule": true, 14 | "esModuleInterop": false, 15 | "declaration": true, 16 | "skipLibCheck": true, 17 | "emitDecoratorMetadata": true, 18 | "experimentalDecorators": true, 19 | "moduleResolution": "NodeNext", 20 | "isolatedModules": true, 21 | "sourceMap": true, 22 | "declarationMap": true, 23 | "noEmitOnError": false, 24 | "noErrorTruncation": true, 25 | "target": "ES2022", 26 | "module": "NodeNext", 27 | "incremental": true, 28 | "removeComments": false, 29 | "lib": [], 30 | "types": [], 31 | "plugins": [{ "name": "@effect/language-service" }] 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /packages/node-effect-lib/tsconfig.check.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.esm.json", 3 | "compilerOptions": { 4 | "noEmit": true, 5 | "tsBuildInfoFile": ".tsbuildinfo/check.tsbuildinfo" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/node-effect-lib/tsconfig.esm.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.base.json", 3 | "include": ["esm/**/*.ts", "esm/**/*.mts", "esm/**/*.cts"], 4 | "compilerOptions": { 5 | "rootDir": "esm", 6 | "tsBuildInfoFile": ".tsbuildinfo/esm.tsbuildinfo", 7 | "declarationDir": "dist/dts", 8 | "declarationMap": true, 9 | "outDir": "dist/esm", 10 | "lib": ["ESNext"] 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /packages/node-effect-lib/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.base.json", 3 | "include": [], 4 | "references": [ 5 | { "path": "./tsconfig.esm.json" }, 6 | { "path": "./tsconfig.others.json" } 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /packages/node-effect-lib/tsconfig.others.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.base.json", 3 | "include": [ 4 | "**/*.ts", 5 | "**/*.mts", 6 | "**/*.cts", 7 | "**/*.js", 8 | "**/*.mjs", 9 | "**/*.cjs" 10 | ], 11 | "exclude": [ 12 | "esm/**/*.ts", 13 | "esm/**/*.mts", 14 | "esm/**/*.cts", 15 | "**/node_modules/**/*.ts", 16 | "**/node_modules/**/*.mts", 17 | "**/node_modules/**/*.cts", 18 | "**/node_modules/**/*.js", 19 | "**/node_modules/**/*.mjs", 20 | "**/node_modules/**/*.cjs", 21 | "dist/**/*.ts", 22 | "dist/**/*.mts", 23 | "dist/**/*.cts", 24 | "dist/**/*.js", 25 | "dist/**/*.mjs", 26 | "dist/**/*.cjs", 27 | "vite.config.ts.timestamp-*.mjs" 28 | ], 29 | "compilerOptions": { 30 | "rootDir": ".", 31 | "tsBuildInfoFile": ".tsbuildinfo/others.tsbuildinfo", 32 | "outDir": "dist/others", 33 | "lib": ["ESNext"], 34 | "types": ["node"], 35 | "allowJs": true, 36 | "checkJs": true 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /packages/node-effect-lib/vite.config.ts: -------------------------------------------------------------------------------- 1 | import type { UserConfig } from 'vite'; 2 | 3 | export default { 4 | test: { 5 | include: ['./tests/*.test.ts'], 6 | isolate: false, 7 | fileParallelism: false, 8 | pool: 'threads' 9 | } 10 | } satisfies UserConfig; 11 | -------------------------------------------------------------------------------- /packages/pretty-print/.gitignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | node_modules/ 3 | vite.config.ts.timestamp-*.mjs 4 | /.tsbuildinfo/ 5 | /*.old -------------------------------------------------------------------------------- /packages/pretty-print/.madgerc: -------------------------------------------------------------------------------- 1 | { 2 | "detectiveOptions": { 3 | "ts": { 4 | "skipTypeImports": true 5 | } 6 | } 7 | } -------------------------------------------------------------------------------- /packages/pretty-print/.prettierignore: -------------------------------------------------------------------------------- 1 | # Same rules as .git 2 | node_modules/ -------------------------------------------------------------------------------- /packages/pretty-print/docgen.json: -------------------------------------------------------------------------------- 1 | { 2 | "parseCompilerOptions": "./tsconfig.docgen.json", 3 | "examplesCompilerOptions": "./tsconfig.docgen.json", 4 | "srcDir": "./esm", 5 | "outDir": "docs", 6 | "exclude": ["esm/index.ts"], 7 | "enforceDescriptions": true, 8 | "enforceVersion": false 9 | } 10 | -------------------------------------------------------------------------------- /packages/pretty-print/eslint.config.js: -------------------------------------------------------------------------------- 1 | import { eslintConfigLibrary } from '@parischap/configs'; 2 | 3 | export default eslintConfigLibrary; 4 | -------------------------------------------------------------------------------- /packages/pretty-print/esm/ByPassers.ts: -------------------------------------------------------------------------------- 1 | /** This module implements a Type that represents an array of ByPasser's (see ByPasser.ts) */ 2 | 3 | import { MArray } from '@parischap/effect-lib'; 4 | import { Array, Function, pipe } from 'effect'; 5 | import * as PPByPasser from './ByPasser.js'; 6 | 7 | /** 8 | * Type of a ByPassers 9 | * 10 | * @category Models 11 | */ 12 | export interface Type extends ReadonlyArray {} 13 | 14 | /** 15 | * Returns a ByPasser that is equivalent to `self`. The returned ByPasser executes successively each 16 | * ByPasser of `self` until it meets one that returns a `some`. If such a ByPasser exists, the 17 | * corresponding `some` is returned. Otherwise, it returns a `none`. 18 | * 19 | * @category Destructors 20 | */ 21 | export const toSyntheticByPasser = (self: Type): PPByPasser.Action.Type => 22 | function (this, constructors) { 23 | const initializedByPassers = Array.map(self, (byPasser) => byPasser.call(this, constructors)); 24 | 25 | return (value) => pipe(initializedByPassers, MArray.firstSomeResult(Function.apply(value))); 26 | }; 27 | -------------------------------------------------------------------------------- /packages/pretty-print/esm/MarkShower.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Namespace of a MarkShower. A MarkShower is a ContextStyler (see @parischap/ContextStyler.ts> used 3 | * as a reversed action. It always displays the same text but in a style that depends on the Value 4 | * context object. 5 | */ 6 | import { ASContextStyler, ASText } from '@parischap/ansi-styles'; 7 | import * as PPValue from './Value.js'; 8 | /** 9 | * Type of a MarkShower 10 | * 11 | * @category Models 12 | */ 13 | export interface Type extends ASContextStyler.ReversedAction.Initialized.Type {} 14 | 15 | /** 16 | * MarkShower instance that always prints an empty Text 17 | * 18 | * @category Instances 19 | */ 20 | export const empty: Type = (_context) => ASText.empty; 21 | -------------------------------------------------------------------------------- /packages/pretty-print/esm/MarkShowerConstructor.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This module implements a Type that builds a MarkShower (see MarkShower.ts) from an Option (see 3 | * Option.ts) and a markName 4 | */ 5 | 6 | import { MTypes } from '@parischap/effect-lib'; 7 | import { HashMap, Option, pipe } from 'effect'; 8 | import * as PPMarkShower from './MarkShower.js'; 9 | import type * as PPOption from './Option.js'; 10 | import * as PPStyleMap from './StyleMap.js'; 11 | /** 12 | * Type of a MarkShowerConstructor 13 | * 14 | * @category Models 15 | */ 16 | export interface Type extends MTypes.OneArgFunction {} 17 | 18 | /** 19 | * Creates a MarkShowerConstructor that will return a MarkShower from `markName` and `option`. 20 | * Concretely, this markShower will display the text attached to markName in option.markMap using 21 | * the reversed action of the ValueBasedContextStyler attached to markName in option.markMap 22 | * 23 | * @category Constructors 24 | */ 25 | export const fromOption = (option: PPOption.Type): Type => { 26 | const markShowerMap = HashMap.map(option.markMap.marks, ({ text, partName }) => 27 | pipe(option.styleMap, PPStyleMap.get(partName), (contextStyler) => 28 | contextStyler.withContextLast(text) 29 | ) 30 | ); 31 | 32 | return (markName) => 33 | pipe( 34 | markShowerMap, 35 | HashMap.get(markName), 36 | Option.getOrElse(() => PPMarkShower.empty) 37 | ); 38 | }; 39 | -------------------------------------------------------------------------------- /packages/pretty-print/esm/PropertyFilters.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This module implements a Type that represents an array of PropertyFilter's (see 3 | * PropertyFilter.ts) 4 | */ 5 | import { Array } from 'effect'; 6 | import * as PPPropertyFilter from './PropertyFilter.js'; 7 | 8 | /** 9 | * Type of a PropertyFilters 10 | * 11 | * @category Models 12 | */ 13 | export interface Type extends ReadonlyArray {} 14 | 15 | /** 16 | * Empty PropertyFilters instance 17 | * 18 | * @category Instances 19 | */ 20 | export const empty: Type = Array.empty(); 21 | 22 | /** 23 | * Default PropertyFilters instance 24 | * 25 | * @category Instances 26 | */ 27 | export const utilInspectLike: Type = Array.of(PPPropertyFilter.removeNonEnumerables); 28 | 29 | /** 30 | * Returns a PropertyFilter that is equivalent to `self`. The returned PropertyFilter executes 31 | * successively each PropertyFilter of `self`. 32 | * 33 | * @category Destructors 34 | */ 35 | export const toSyntheticPropertyFilter = 36 | (self: Type): PPPropertyFilter.Action.Type => 37 | (properties) => 38 | Array.reduce(self, properties, (remainingProperties, propertyFilter) => 39 | propertyFilter(remainingProperties) 40 | ); 41 | -------------------------------------------------------------------------------- /packages/pretty-print/esm/ValueBasedStyler.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Alias for ASContextStyler.Type (see @parischap/ansi-style/ContextStyler.ts and 3 | * Value.ts) 4 | */ 5 | 6 | import { ASContextStyler, ASPalette } from '@parischap/ansi-styles'; 7 | import * as PPValue from './Value.js'; 8 | 9 | /** 10 | * Type of a ValueBasedStyler 11 | * 12 | * @category Models 13 | */ 14 | export type Type = ASContextStyler.Type; 15 | 16 | /** 17 | * Constructor of a depth-indexed ValueBasedStyler 18 | * 19 | * @category Constructors 20 | */ 21 | export const makeDepthIndexed = (palette: ASPalette.Type): Type => 22 | ASContextStyler.fromPalette({ 23 | // Use named function so the name gets printed by the toString function 24 | indexFromContext: function valueDepth(value: PPValue.All) { 25 | return PPValue.depth(value); 26 | }, 27 | palette 28 | }); 29 | 30 | /** 31 | * Constructor of a type-indexed ValueBasedStyler 32 | * 33 | * @category Constructors 34 | */ 35 | export const makeTypeIndexed = (palette: ASPalette.Type): Type => 36 | ASContextStyler.fromPalette({ 37 | // Use named function so the name gets printed by the toString function 38 | indexFromContext: function valueType(value: PPValue.All) { 39 | return value.contentType; 40 | }, 41 | palette 42 | }); 43 | 44 | /** 45 | * Constructor of a key-type-indexed ValueBasedStyler 46 | * 47 | * @category Constructors 48 | */ 49 | export const makeKeyTypeIndexed = (palette: ASPalette.Type): Type => 50 | ASContextStyler.fromPalette({ 51 | // Use named function so the name gets printed by the toString function 52 | indexFromContext: function keyType(value: PPValue.All) { 53 | // `1` for symbolic key, `0` for string key 54 | return +value.hasSymbolicKey; 55 | }, 56 | palette 57 | }); 58 | -------------------------------------------------------------------------------- /packages/pretty-print/esm/ValueBasedStylerConstructor.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * This module implements a Type that builds a ValueBasedStyler (see ValueBasedStyler.ts) from an 3 | * Option (see Option.ts) and a partName. 4 | */ 5 | 6 | import { MTypes } from '@parischap/effect-lib'; 7 | import { pipe } from 'effect'; 8 | import type * as PPOption from './Option.js'; 9 | import * as PPStyleMap from './StyleMap.js'; 10 | import * as PPValueBasedStyler from './ValueBasedStyler.js'; 11 | /** 12 | * Type of a ValueBasedStylerConstructor 13 | * 14 | * @category Models 15 | */ 16 | export interface Type extends MTypes.OneArgFunction {} 17 | 18 | /** 19 | * Builds a ValueBasedStyler (see ValueBasedStyler.ts) from the `partName` style of the provided 20 | * `option.styleMap` (see StyleMap.ts) 21 | * 22 | * @category Constructors 23 | */ 24 | export const fromOption = 25 | (option: PPOption.Type): Type => 26 | (partName) => 27 | pipe(option.styleMap, PPStyleMap.get(partName)); 28 | -------------------------------------------------------------------------------- /packages/pretty-print/esm/ValueOrder.ts: -------------------------------------------------------------------------------- 1 | /** Module that implements an Order on Value.All */ 2 | 3 | import { MTypes } from '@parischap/effect-lib'; 4 | import { flow, Order } from 'effect'; 5 | import * as PPValue from './Value.js'; 6 | 7 | /** 8 | * Type of an Order on a Value.All 9 | * 10 | * @category Models 11 | */ 12 | export interface Type extends Order.Order {} 13 | /** 14 | * Order instance based on the `protoDepth` property, lowest depth first 15 | * 16 | * @category Ordering 17 | */ 18 | export const byProtoDepth: Type = Order.mapInput(Order.number, PPValue.protoDepth); 19 | 20 | /** 21 | * Order instance based on the `oneLineStringKey` property 22 | * 23 | * @category Ordering 24 | */ 25 | export const byOneLineStringKey: Type = Order.mapInput(Order.string, PPValue.oneLineStringKey); 26 | 27 | /** 28 | * Order instance based on the callability of the `content` property (non functions first, then 29 | * functions) 30 | * 31 | * @category Ordering 32 | */ 33 | export const byCallability: Type = Order.mapInput( 34 | Order.boolean, 35 | flow(PPValue.contentType, MTypes.Category.isFunction) 36 | ); 37 | 38 | /** 39 | * Order instance based on the type of the key associated to the value (symbolic keys first, then 40 | * string keys) 41 | * 42 | * @category Ordering 43 | */ 44 | export const byKeyType: Type = Order.mapInput(Order.reverse(Order.boolean), PPValue.hasSymbolicKey); 45 | 46 | /** 47 | * Order instance based on the enumerability of the property to which the value belongs 48 | * (non-enumerable keys first, then enumerable keys) 49 | * 50 | * @category Ordering 51 | */ 52 | export const byEnumerability: Type = Order.mapInput(Order.boolean, PPValue.isEnumerable); 53 | -------------------------------------------------------------------------------- /packages/pretty-print/esm/index.ts: -------------------------------------------------------------------------------- 1 | export * as PPByPasser from './ByPasser.js'; 2 | export * as PPByPassers from './ByPassers.js'; 3 | export * as PPMarkMap from './MarkMap.js'; 4 | export * as PPMarkShower from './MarkShower.js'; 5 | export * as PPMarkShowerConstructor from './MarkShowerConstructor.js'; 6 | export * as PPNonPrimitiveFormatter from './NonPrimitiveFormatter.js'; 7 | export * as PPOption from './Option.js'; 8 | export * as PPPrimitiveFormatter from './PrimitiveFormatter.js'; 9 | export * as PPPropertyFilter from './PropertyFilter.js'; 10 | export * as PPPropertyFilters from './PropertyFilters.js'; 11 | export * as PPPropertyFormatter from './PropertyFormatter.js'; 12 | export * as PPStringifiedProperties from './StringifiedProperties.js'; 13 | export * as PPStringifiedValue from './StringifiedValue.js'; 14 | export * as PPStyleMap from './StyleMap.js'; 15 | export * as PPValue from './Value.js'; 16 | export * as PPValueBasedStyler from './ValueBasedStyler.js'; 17 | export * as PPValueBasedStylerConstructor from './ValueBasedStylerConstructor.js'; 18 | export * as PPValueOrder from './ValueOrder.js'; 19 | export * as PPValues from './Values.js'; 20 | -------------------------------------------------------------------------------- /packages/pretty-print/examples/circularity-handling.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable functional/no-expression-statements */ 2 | import { PPOption, PPStringifiedValue } from '@parischap/pretty-print'; 3 | import { pipe } from 'effect'; 4 | 5 | const stringifier = PPOption.toStringifier( 6 | PPOption.make({ ...PPOption.utilInspectLike, maxDepth: +Infinity }) 7 | ); 8 | 9 | const circular = { a: 1 as unknown, b: { inner: 1 as unknown, circular: 1 as unknown } }; 10 | /* eslint-disable functional/immutable-data */ 11 | circular.a = [circular]; 12 | circular.b.inner = circular.b; 13 | circular.b.circular = circular; 14 | /* eslint-enable functional/immutable-data*/ 15 | 16 | console.log(pipe(circular, stringifier, PPStringifiedValue.toAnsiString())); 17 | -------------------------------------------------------------------------------- /packages/pretty-print/examples/treeify-with-leaves.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable functional/no-expression-statements */ 2 | import { PPOption, PPStringifiedValue } from '@parischap/pretty-print'; 3 | import { pipe } from 'effect'; 4 | 5 | const stringifier = PPOption.toStringifier(PPOption.darkModeTreeify); 6 | 7 | const toPrint = { 8 | Vegetal: { 9 | Trees: { 10 | Oaks: 8, 11 | BirchTree: 3 12 | }, 13 | Fruit: { Apples: 8, Lemons: 5 } 14 | }, 15 | Animal: { 16 | Mammals: { 17 | Dogs: 3, 18 | Cats: 2 19 | } 20 | } 21 | }; 22 | 23 | console.log(pipe(toPrint, stringifier, PPStringifiedValue.toAnsiString())); 24 | -------------------------------------------------------------------------------- /packages/pretty-print/examples/treeify.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable functional/no-expression-statements */ 2 | import { PPOption, PPStringifiedValue } from '@parischap/pretty-print'; 3 | import { HashMap, pipe } from 'effect'; 4 | 5 | const stringifier = PPOption.toStringifier(PPOption.darkModeTreeifyHideLeaves); 6 | 7 | const toPrint = { 8 | A: { 9 | A1: { 10 | A11: null, 11 | A12: [{ A121: null, A122: null, A123: null }, { A124: null }], 12 | A13: null 13 | }, 14 | A2: null, 15 | A3: null 16 | }, 17 | B: HashMap.make(['B1', null], ['B2', null]) 18 | }; 19 | 20 | console.log(pipe(toPrint, stringifier, PPStringifiedValue.toAnsiString())); 21 | -------------------------------------------------------------------------------- /packages/pretty-print/examples/util-inspect-like.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable functional/no-expression-statements */ 2 | import { PPOption, PPStringifiedValue } from '@parischap/pretty-print'; 3 | import { HashMap, pipe } from 'effect'; 4 | 5 | const stringifier = PPOption.toStringifier(PPOption.darkModeUtilInspectLike); 6 | 7 | const toPrint = { 8 | a: [7, 8], 9 | e: HashMap.make(['key1', 3], ['key2', 6]), 10 | b: { a: 5, c: 8 }, 11 | f: Math.max, 12 | d: { 13 | e: true, 14 | f: { a: { k: { z: 'foo', y: 'bar' } } } 15 | } 16 | }; 17 | 18 | console.log(pipe(toPrint, stringifier, PPStringifiedValue.toAnsiString())); 19 | -------------------------------------------------------------------------------- /packages/pretty-print/prettier.config.js: -------------------------------------------------------------------------------- 1 | import * as Configs from '@parischap/configs'; 2 | 3 | export default Configs.prettierconfigBase; 4 | -------------------------------------------------------------------------------- /packages/pretty-print/project.config.js: -------------------------------------------------------------------------------- 1 | import * as Configs from '@parischap/configs'; 2 | 3 | export default Configs.configSubRepo({ 4 | description: 'A functional library to pretty-print and treeify objects', 5 | dependencies: {}, 6 | devDependencies: {}, 7 | internalPeerDependencies: { 8 | 'effect-lib': Configs.constants.effectLibVersion, 9 | 'ansi-styles': Configs.constants.ansiStylesVersion 10 | }, 11 | externalPeerDependencies: { 12 | effect: Configs.constants.effectVersion 13 | }, 14 | examples: [ 15 | 'util-inspect-like.ts', 16 | 'treeify.ts', 17 | 'treeify-with-leaves.ts', 18 | 'circularity-handling.ts' 19 | ], 20 | scripts: {}, 21 | environment: Configs.Environment.Type.Library, 22 | bundled: false, 23 | visibility: Configs.Visibility.Type.Public, 24 | hasStaticFolder: false, 25 | hasDocGen: true, 26 | keywords: [ 27 | 'inspect', 28 | 'util.inspect', 29 | 'object', 30 | 'stringify', 31 | 'pretty', 32 | 'tree', 33 | 'treeify', 34 | 'print', 35 | 'console', 36 | 'visualize', 37 | 'debug', 38 | 'typescript', 39 | 'effect' 40 | ] 41 | }); 42 | -------------------------------------------------------------------------------- /packages/pretty-print/readme-assets/circularity-handling.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/parischap/effect-libs/2f5a1c71c0c4824de2fb8452b2ece6a5d6c54022/packages/pretty-print/readme-assets/circularity-handling.png -------------------------------------------------------------------------------- /packages/pretty-print/readme-assets/treeify-with-leaves.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/parischap/effect-libs/2f5a1c71c0c4824de2fb8452b2ece6a5d6c54022/packages/pretty-print/readme-assets/treeify-with-leaves.png -------------------------------------------------------------------------------- /packages/pretty-print/readme-assets/treeify.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/parischap/effect-libs/2f5a1c71c0c4824de2fb8452b2ece6a5d6c54022/packages/pretty-print/readme-assets/treeify.png -------------------------------------------------------------------------------- /packages/pretty-print/readme-assets/util-inspect-like.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/parischap/effect-libs/2f5a1c71c0c4824de2fb8452b2ece6a5d6c54022/packages/pretty-print/readme-assets/util-inspect-like.png -------------------------------------------------------------------------------- /packages/pretty-print/tests/ByPassers.test.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable functional/no-expression-statements */ 2 | import { ASStyle } from '@parischap/ansi-styles'; 3 | import { 4 | PPByPasser, 5 | PPByPassers, 6 | PPMarkShowerConstructor, 7 | PPOption, 8 | PPStringifiedValue, 9 | PPValue, 10 | PPValueBasedStylerConstructor 11 | } from '@parischap/pretty-print'; 12 | import { TEUtils } from '@parischap/test-utils'; 13 | import { Array, pipe } from 'effect'; 14 | import { describe, it } from 'vitest'; 15 | 16 | describe('ByPassers', () => { 17 | const utilInspectLike = PPOption.darkModeUtilInspectLike; 18 | const valueBasedStylerConstructor = PPValueBasedStylerConstructor.fromOption(utilInspectLike); 19 | const markShowerConstructor = PPMarkShowerConstructor.fromOption(utilInspectLike); 20 | const constructors = { 21 | valueBasedStylerConstructor, 22 | markShowerConstructor 23 | }; 24 | 25 | const byPassers: PPByPassers.Type = Array.make( 26 | PPByPasser.functionToName, 27 | PPByPasser.objectToString 28 | ); 29 | 30 | describe('initializedSyntheticByPasser', () => { 31 | const initializedSyntheticByPasser = PPByPassers.toSyntheticByPasser(byPassers).call( 32 | utilInspectLike, 33 | constructors 34 | ); 35 | 36 | it('Applied to named function', () => { 37 | function foo(): string { 38 | return 'foo'; 39 | } 40 | TEUtils.assertSome( 41 | pipe(foo, PPValue.fromTopValue, initializedSyntheticByPasser), 42 | pipe('[Function: foo]', ASStyle.green, PPStringifiedValue.fromText) 43 | ); 44 | }); 45 | 46 | it('Applied to object with a .toString method', () => { 47 | TEUtils.assertSome( 48 | pipe( 49 | { a: 3, toString: (): string => 'foo\nbar' }, 50 | PPValue.fromTopValue, 51 | initializedSyntheticByPasser 52 | ), 53 | Array.make(ASStyle.yellow('foo'), ASStyle.yellow('bar')) 54 | ); 55 | }); 56 | 57 | it('Applied to primitive', () => { 58 | TEUtils.assertNone(pipe(3, PPValue.fromTopValue, initializedSyntheticByPasser)); 59 | }); 60 | }); 61 | }); 62 | -------------------------------------------------------------------------------- /packages/pretty-print/tests/MarkMap.test.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable functional/no-expression-statements */ 2 | import { PPMarkMap } from '@parischap/pretty-print'; 3 | import { TEUtils } from '@parischap/test-utils'; 4 | import { describe, it } from 'vitest'; 5 | 6 | describe('MarkMap', () => { 7 | const utilInspectLike = PPMarkMap.utilInspectLike; 8 | 9 | describe('Tag, prototype and guards', () => { 10 | it('moduleTag', () => { 11 | TEUtils.assertSome(TEUtils.moduleTagFromTestFilePath(__filename), PPMarkMap.moduleTag); 12 | }); 13 | 14 | describe('Equal.equals', () => { 15 | it('Matching', () => { 16 | TEUtils.assertEquals(utilInspectLike, PPMarkMap.make(utilInspectLike)); 17 | }); 18 | 19 | it('Non-matching', () => { 20 | TEUtils.assertNotEquals( 21 | utilInspectLike, 22 | PPMarkMap.make({ ...utilInspectLike, id: 'Dummy' }) 23 | ); 24 | }); 25 | }); 26 | 27 | it('.toString()', () => { 28 | TEUtils.strictEqual(utilInspectLike.toString(), `UtilInspectLike`); 29 | }); 30 | 31 | it('.pipe()', () => { 32 | TEUtils.strictEqual(utilInspectLike.pipe(PPMarkMap.id), 'UtilInspectLike'); 33 | }); 34 | 35 | describe('has', () => { 36 | it('Matching', () => { 37 | TEUtils.assertTrue(PPMarkMap.has(utilInspectLike)); 38 | }); 39 | it('Non matching', () => { 40 | TEUtils.assertFalse(PPMarkMap.has(new Date())); 41 | }); 42 | }); 43 | }); 44 | }); 45 | -------------------------------------------------------------------------------- /packages/pretty-print/tests/PropertyFilter.test.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable functional/no-expression-statements */ 2 | import { MFunction } from '@parischap/effect-lib'; 3 | import { PPPropertyFilter, PPValue, PPValues } from '@parischap/pretty-print'; 4 | import { TEUtils } from '@parischap/test-utils'; 5 | import { Array, Function, pipe } from 'effect'; 6 | import { describe, it } from 'vitest'; 7 | 8 | describe('PropertyFilter', () => { 9 | const removeFunctions = PPPropertyFilter.removeFunctions; 10 | 11 | const value1 = PPValue.fromTopValue({ 12 | content: 1 13 | }); 14 | const value2 = PPValue.fromTopValue(Math.max); 15 | 16 | const value3 = PPValue.fromNonPrimitiveValueAndKey({ 17 | nonPrimitiveContent: [1, 2], 18 | key: 'length', 19 | depth: 0, 20 | protoDepth: 0 21 | }); 22 | const value4 = PPValue.fromNonPrimitiveValueAndKey({ 23 | nonPrimitiveContent: { [Symbol.iterator]: 1, a: 2 }, 24 | key: Symbol.iterator, 25 | depth: 0, 26 | protoDepth: 0 27 | }); 28 | const values: PPValues.Type = Array.make(value1, value2, value3, value4); 29 | 30 | describe('Tag, prototype and guards', () => { 31 | it('moduleTag', () => { 32 | TEUtils.assertSome(TEUtils.moduleTagFromTestFilePath(__filename), PPPropertyFilter.moduleTag); 33 | }); 34 | 35 | describe('Equal.equals', () => { 36 | it('Matching', () => { 37 | TEUtils.assertEquals( 38 | removeFunctions, 39 | PPPropertyFilter.make({ 40 | id: 'RemoveFunctions', 41 | action: Function.identity 42 | }) 43 | ); 44 | }); 45 | 46 | it('Non-matching', () => { 47 | TEUtils.assertNotEquals(removeFunctions, PPPropertyFilter.removeNonFunctions); 48 | }); 49 | }); 50 | 51 | it('.toString()', () => { 52 | TEUtils.strictEqual(removeFunctions.toString(), `RemoveFunctions`); 53 | }); 54 | 55 | it('.pipe()', () => { 56 | TEUtils.strictEqual(removeFunctions.pipe(PPPropertyFilter.id), 'RemoveFunctions'); 57 | }); 58 | 59 | describe('has', () => { 60 | it('Matching', () => { 61 | TEUtils.assertTrue(PPPropertyFilter.has(removeFunctions)); 62 | }); 63 | it('Non matching', () => { 64 | TEUtils.assertFalse(PPPropertyFilter.has(new Date())); 65 | }); 66 | }); 67 | }); 68 | 69 | it('removeNonFunctions', () => { 70 | TEUtils.deepStrictEqual(pipe(values, PPPropertyFilter.removeNonFunctions), Array.of(value2)); 71 | }); 72 | 73 | it('removeFunctions', () => { 74 | TEUtils.deepStrictEqual( 75 | pipe(values, PPPropertyFilter.removeFunctions), 76 | Array.make(value1, value3, value4) 77 | ); 78 | }); 79 | 80 | it('removeNonEnumerables', () => { 81 | TEUtils.deepStrictEqual(pipe(values, PPPropertyFilter.removeNonEnumerables), Array.of(value4)); 82 | }); 83 | 84 | it('removeEnumerables', () => { 85 | TEUtils.deepStrictEqual( 86 | pipe(values, PPPropertyFilter.removeEnumerables), 87 | Array.make(value1, value2, value3) 88 | ); 89 | }); 90 | 91 | it('removeStringKeys', () => { 92 | TEUtils.deepStrictEqual(pipe(values, PPPropertyFilter.removeStringKeys), Array.of(value4)); 93 | }); 94 | 95 | it('removeSymbolicKeys', () => { 96 | TEUtils.deepStrictEqual( 97 | pipe(values, PPPropertyFilter.removeSymbolicKeys), 98 | Array.make(value1, value2, value3) 99 | ); 100 | }); 101 | 102 | it('removeNotFulfillingKeyPredicateMaker', () => { 103 | TEUtils.deepStrictEqual( 104 | pipe( 105 | values, 106 | PPPropertyFilter.removeNotFulfillingKeyPredicateMaker({ 107 | id: 'OnlyLength', 108 | predicate: MFunction.strictEquals('length') 109 | }) 110 | ), 111 | Array.of(value3) 112 | ); 113 | }); 114 | }); 115 | -------------------------------------------------------------------------------- /packages/pretty-print/tests/PropertyFilters.test.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable functional/no-expression-statements */ 2 | import { PPPropertyFilter, PPPropertyFilters, PPValue, PPValues } from '@parischap/pretty-print'; 3 | import { TEUtils } from '@parischap/test-utils'; 4 | import { Array, pipe } from 'effect'; 5 | import { describe, it } from 'vitest'; 6 | 7 | describe('PropertyFilters', () => { 8 | const value1 = PPValue.fromTopValue({ 9 | content: 1 10 | }); 11 | const value2 = PPValue.fromTopValue(Math.max); 12 | 13 | const value3 = PPValue.fromNonPrimitiveValueAndKey({ 14 | nonPrimitiveContent: [1, 2], 15 | key: 'length', 16 | depth: 0, 17 | protoDepth: 0 18 | }); 19 | const value4 = PPValue.fromNonPrimitiveValueAndKey({ 20 | nonPrimitiveContent: { [Symbol.iterator]: 1, a: 2 }, 21 | key: Symbol.iterator, 22 | depth: 0, 23 | protoDepth: 0 24 | }); 25 | const values: PPValues.Type = Array.make(value1, value2, value3, value4); 26 | 27 | it('toSyntheticPropertyFilter', () => { 28 | const filters: PPPropertyFilters.Type = Array.make( 29 | PPPropertyFilter.removeFunctions, 30 | PPPropertyFilter.removeNonEnumerables 31 | ); 32 | const removeFunctionsAndNonEnumerables = PPPropertyFilters.toSyntheticPropertyFilter(filters); 33 | TEUtils.deepStrictEqual(pipe(values, removeFunctionsAndNonEnumerables), Array.of(value4)); 34 | }); 35 | }); 36 | -------------------------------------------------------------------------------- /packages/pretty-print/tests/StringifiedValue.test.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable functional/no-expression-statements */ 2 | import { ASText } from '@parischap/ansi-styles'; 3 | import { PPStringifiedValue } from '@parischap/pretty-print'; 4 | import { TEUtils } from '@parischap/test-utils'; 5 | import { Array, pipe } from 'effect'; 6 | import { describe, it } from 'vitest'; 7 | 8 | describe('StringifiedValue', () => { 9 | const test1: PPStringifiedValue.Type = Array.make( 10 | ASText.fromString('foo'), 11 | ASText.fromString('bar') 12 | ); 13 | 14 | describe('equivalence', () => { 15 | it('Matching', () => { 16 | TEUtils.assertTrue( 17 | PPStringifiedValue.equivalence( 18 | test1, 19 | Array.make(ASText.fromString('foo'), ASText.fromString('bar')) 20 | ) 21 | ); 22 | }); 23 | 24 | it('Non-matching', () => { 25 | TEUtils.assertFalse(PPStringifiedValue.equivalence(test1, PPStringifiedValue.empty)); 26 | }); 27 | }); 28 | 29 | it('toSingleLine', () => { 30 | TEUtils.assertEquals( 31 | PPStringifiedValue.toSingleLine(test1), 32 | Array.of(ASText.fromString('foobar')) 33 | ); 34 | }); 35 | 36 | describe('isEmpty', () => { 37 | it('Matching', () => { 38 | TEUtils.assertTrue(pipe(PPStringifiedValue.isEmpty(PPStringifiedValue.empty))); 39 | }); 40 | 41 | it('Non-matching', () => { 42 | TEUtils.assertFalse(PPStringifiedValue.isEmpty(test1)); 43 | }); 44 | }); 45 | 46 | describe('length', () => { 47 | it('With empty StringifiedValue', () => { 48 | TEUtils.strictEqual(PPStringifiedValue.toLength(PPStringifiedValue.empty), 0); 49 | }); 50 | 51 | it('With non-empty StringifiedValue', () => { 52 | TEUtils.strictEqual(PPStringifiedValue.toLength(test1), 6); 53 | }); 54 | }); 55 | 56 | it('toAnsiString', () => { 57 | TEUtils.strictEqual(PPStringifiedValue.toAnsiString()(test1), 'foo\nbar'); 58 | }); 59 | 60 | it('toUnstyledStrings', () => { 61 | TEUtils.deepStrictEqual(PPStringifiedValue.toUnstyledStrings(test1), ['foo', 'bar']); 62 | }); 63 | }); 64 | -------------------------------------------------------------------------------- /packages/pretty-print/tests/StyleMap.test.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable functional/no-expression-statements */ 2 | import { ASContextStyler, ASText } from '@parischap/ansi-styles'; 3 | import { PPStyleMap, PPValue } from '@parischap/pretty-print'; 4 | import { TEUtils } from '@parischap/test-utils'; 5 | import { Function, pipe } from 'effect'; 6 | import { describe, it } from 'vitest'; 7 | 8 | describe('StyleMap', () => { 9 | const none = PPStyleMap.none; 10 | describe('Tag, prototype and guards', () => { 11 | it('moduleTag', () => { 12 | TEUtils.assertSome(TEUtils.moduleTagFromTestFilePath(__filename), PPStyleMap.moduleTag); 13 | }); 14 | 15 | describe('Equal.equals', () => { 16 | it('Matching', () => { 17 | TEUtils.assertEquals(none, PPStyleMap.make(none)); 18 | }); 19 | 20 | it('Non-matching', () => { 21 | TEUtils.assertNotEquals(none, PPStyleMap.darkMode); 22 | }); 23 | }); 24 | 25 | it('.toString()', () => { 26 | TEUtils.strictEqual(none.toString(), `None`); 27 | }); 28 | 29 | it('.pipe()', () => { 30 | TEUtils.strictEqual(none.pipe(PPStyleMap.id), 'None'); 31 | }); 32 | 33 | describe('has', () => { 34 | it('Matching', () => { 35 | TEUtils.assertTrue(PPStyleMap.has(none)); 36 | }); 37 | it('Non matching', () => { 38 | TEUtils.assertFalse(PPStyleMap.has(new Date())); 39 | }); 40 | }); 41 | }); 42 | 43 | describe('get', () => { 44 | it('Existing partname', () => { 45 | TEUtils.assertTrue(pipe(PPStyleMap.darkMode, PPStyleMap.get('Message'), ASContextStyler.has)); 46 | }); 47 | 48 | it('Missing partname', () => { 49 | TEUtils.strictEqual( 50 | pipe( 51 | none, 52 | PPStyleMap.get('Message'), 53 | Function.apply(PPValue.fromTopValue(null)), 54 | Function.apply('foo'), 55 | ASText.toAnsiString 56 | ), 57 | 'foo' 58 | ); 59 | }); 60 | }); 61 | }); 62 | -------------------------------------------------------------------------------- /packages/pretty-print/tests/Value.test.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable functional/no-expression-statements */ 2 | import { PPValue } from '@parischap/pretty-print'; 3 | import { TEUtils } from '@parischap/test-utils'; 4 | import { describe, it } from 'vitest'; 5 | 6 | describe('Value', () => { 7 | const value1 = PPValue.fromTopValue(3); 8 | const value2 = PPValue.fromTopValue(3); 9 | const value3 = PPValue.fromTopValue(2); 10 | 11 | describe('Tag, prototype and guards', () => { 12 | it('moduleTag', () => { 13 | TEUtils.assertSome(TEUtils.moduleTagFromTestFilePath(__filename), PPValue.moduleTag); 14 | }); 15 | 16 | describe('Equal.equals', () => { 17 | it('Matching', () => { 18 | TEUtils.assertEquals(value1, value2); 19 | }); 20 | 21 | it('Non-matching', () => { 22 | TEUtils.assertNotEquals(value1, value3); 23 | }); 24 | }); 25 | 26 | it('.toString()', () => { 27 | TEUtils.strictEqual( 28 | value1.toString(), 29 | `{ 30 | "_id": "@parischap/pretty-print/Value/", 31 | "content": 3, 32 | "contentType": 1, 33 | "depth": 0, 34 | "protoDepth": 0, 35 | "stringKey": [ 36 | "" 37 | ], 38 | "oneLineStringKey": "", 39 | "hasSymbolicKey": false, 40 | "isEnumerable": false 41 | }` 42 | ); 43 | }); 44 | 45 | it('.pipe()', () => { 46 | TEUtils.strictEqual(value1.pipe(PPValue.content), 3); 47 | }); 48 | 49 | describe('has', () => { 50 | it('Matching', () => { 51 | TEUtils.assertTrue(PPValue.has(value1)); 52 | }); 53 | it('Non matching', () => { 54 | TEUtils.assertFalse(PPValue.has(new Date())); 55 | }); 56 | }); 57 | }); 58 | 59 | describe('fromNonPrimitiveValueAndKey', () => { 60 | it('Enumerable property', () => { 61 | TEUtils.strictEqual( 62 | PPValue.fromNonPrimitiveValueAndKey({ 63 | nonPrimitiveContent: { a: 1, b: 'foo' }, 64 | key: 'a', 65 | depth: 1, 66 | protoDepth: 0 67 | }).toString(), 68 | `{ 69 | "_id": "@parischap/pretty-print/Value/", 70 | "content": 1, 71 | "contentType": 1, 72 | "depth": 1, 73 | "protoDepth": 0, 74 | "stringKey": [ 75 | "a" 76 | ], 77 | "oneLineStringKey": "a", 78 | "hasSymbolicKey": false, 79 | "isEnumerable": true 80 | }` 81 | ); 82 | }); 83 | 84 | it('Non-enumerable property', () => { 85 | TEUtils.strictEqual( 86 | PPValue.fromNonPrimitiveValueAndKey({ 87 | nonPrimitiveContent: [1, 2], 88 | key: 'length', 89 | depth: 1, 90 | protoDepth: 0 91 | }).toString(), 92 | `{ 93 | "_id": "@parischap/pretty-print/Value/", 94 | "content": 2, 95 | "contentType": 1, 96 | "depth": 1, 97 | "protoDepth": 0, 98 | "stringKey": [ 99 | "length" 100 | ], 101 | "oneLineStringKey": "length", 102 | "hasSymbolicKey": false, 103 | "isEnumerable": false 104 | }` 105 | ); 106 | }); 107 | }); 108 | }); 109 | 110 | it('fromIterable', () => { 111 | TEUtils.strictEqual( 112 | PPValue.fromIterable({ 113 | content: 'foo', 114 | stringKey: ['a : 1', 'b : 2'], 115 | depth: 1 116 | }).toString(), 117 | `{ 118 | "_id": "@parischap/pretty-print/Value/", 119 | "content": "foo", 120 | "contentType": 0, 121 | "depth": 1, 122 | "protoDepth": 0, 123 | "stringKey": [ 124 | "a : 1", 125 | "b : 2" 126 | ], 127 | "oneLineStringKey": "a : 1b : 2", 128 | "hasSymbolicKey": false, 129 | "isEnumerable": true 130 | }` 131 | ); 132 | }); 133 | -------------------------------------------------------------------------------- /packages/pretty-print/tests/ValueBasedStyler.test.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable functional/no-expression-statements */ 2 | import { ASPalette, ASStyle } from '@parischap/ansi-styles'; 3 | import { PPValue, PPValueBasedStyler } from '@parischap/pretty-print'; 4 | import { TEUtils } from '@parischap/test-utils'; 5 | import { describe, it } from 'vitest'; 6 | 7 | describe('ValueBasedStyler', () => { 8 | const palette = ASPalette.make(ASStyle.black, ASStyle.red, ASStyle.green, ASStyle.blue); 9 | 10 | const symbolicKey: unique symbol = Symbol.for('symbolicKey'); 11 | const context = PPValue.fromNonPrimitiveValueAndKey({ 12 | nonPrimitiveContent: { [symbolicKey]: 3, b: 'foo' }, 13 | key: symbolicKey, 14 | depth: 2, 15 | protoDepth: 0 16 | }); 17 | 18 | it('makeDepthIndexed', () => { 19 | TEUtils.assertEquals( 20 | PPValueBasedStyler.makeDepthIndexed(palette)(context)('foo'), 21 | ASStyle.green('foo') 22 | ); 23 | }); 24 | 25 | it('makeTypeIndexed', () => { 26 | TEUtils.assertEquals( 27 | PPValueBasedStyler.makeTypeIndexed(palette)(context)('foo'), 28 | ASStyle.red('foo') 29 | ); 30 | }); 31 | 32 | it('makeKeyTypeIndexed', () => { 33 | TEUtils.assertEquals( 34 | PPValueBasedStyler.makeKeyTypeIndexed(palette)(context)('foo'), 35 | ASStyle.red('foo') 36 | ); 37 | }); 38 | }); 39 | -------------------------------------------------------------------------------- /packages/pretty-print/tsconfig.base.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "_version": "20.1.0", 4 | "extends": [ 5 | "@tsconfig/strictest/tsconfig.json", 6 | "@tsconfig/node20/tsconfig.json" 7 | ], 8 | "compilerOptions": { 9 | "allowJs": false, 10 | "checkJs": false, 11 | "moduleDetection": "force", 12 | "composite": true, 13 | "resolveJsonModule": true, 14 | "esModuleInterop": false, 15 | "declaration": true, 16 | "skipLibCheck": true, 17 | "emitDecoratorMetadata": true, 18 | "experimentalDecorators": true, 19 | "moduleResolution": "NodeNext", 20 | "isolatedModules": true, 21 | "sourceMap": true, 22 | "declarationMap": true, 23 | "noEmitOnError": false, 24 | "noErrorTruncation": true, 25 | "target": "ES2022", 26 | "module": "NodeNext", 27 | "incremental": true, 28 | "removeComments": false, 29 | "lib": [], 30 | "types": [], 31 | "plugins": [{ "name": "@effect/language-service" }] 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /packages/pretty-print/tsconfig.check.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.esm.json", 3 | "compilerOptions": { 4 | "noEmit": true, 5 | "tsBuildInfoFile": ".tsbuildinfo/check.tsbuildinfo" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/pretty-print/tsconfig.docgen.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.base.json", 3 | "include": ["**/*.ts", "**/*.mts", "**/*.cts"], 4 | "compilerOptions": { "noEmit": true, "lib": ["ESNext"], "types": ["node"] } 5 | } 6 | -------------------------------------------------------------------------------- /packages/pretty-print/tsconfig.esm.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.base.json", 3 | "include": ["esm/**/*.ts", "esm/**/*.mts", "esm/**/*.cts"], 4 | "compilerOptions": { 5 | "rootDir": "esm", 6 | "tsBuildInfoFile": ".tsbuildinfo/esm.tsbuildinfo", 7 | "declarationDir": "dist/dts", 8 | "declarationMap": true, 9 | "outDir": "dist/esm", 10 | "lib": ["ESNext"] 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /packages/pretty-print/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.base.json", 3 | "include": [], 4 | "references": [ 5 | { "path": "./tsconfig.esm.json" }, 6 | { "path": "./tsconfig.others.json" } 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /packages/pretty-print/tsconfig.others.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.base.json", 3 | "include": [ 4 | "**/*.ts", 5 | "**/*.mts", 6 | "**/*.cts", 7 | "**/*.js", 8 | "**/*.mjs", 9 | "**/*.cjs" 10 | ], 11 | "exclude": [ 12 | "esm/**/*.ts", 13 | "esm/**/*.mts", 14 | "esm/**/*.cts", 15 | "**/node_modules/**/*.ts", 16 | "**/node_modules/**/*.mts", 17 | "**/node_modules/**/*.cts", 18 | "**/node_modules/**/*.js", 19 | "**/node_modules/**/*.mjs", 20 | "**/node_modules/**/*.cjs", 21 | "dist/**/*.ts", 22 | "dist/**/*.mts", 23 | "dist/**/*.cts", 24 | "dist/**/*.js", 25 | "dist/**/*.mjs", 26 | "dist/**/*.cjs", 27 | "vite.config.ts.timestamp-*.mjs" 28 | ], 29 | "compilerOptions": { 30 | "rootDir": ".", 31 | "tsBuildInfoFile": ".tsbuildinfo/others.tsbuildinfo", 32 | "outDir": "dist/others", 33 | "lib": ["ESNext"], 34 | "types": ["node"], 35 | "allowJs": true, 36 | "checkJs": true 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /packages/pretty-print/vite.config.ts: -------------------------------------------------------------------------------- 1 | import type { UserConfig } from 'vite'; 2 | 3 | export default { 4 | test: { 5 | include: ['./tests/*.test.ts'], 6 | isolate: false, 7 | fileParallelism: false, 8 | pool: 'threads' 9 | } 10 | } satisfies UserConfig; 11 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - "packages/*" 3 | - "packages/*/dist" 4 | -------------------------------------------------------------------------------- /prettier.config.js: -------------------------------------------------------------------------------- 1 | import * as Configs from '@parischap/configs'; 2 | 3 | export default Configs.prettierconfigBase; 4 | -------------------------------------------------------------------------------- /project.config.js: -------------------------------------------------------------------------------- 1 | import * as Configs from '@parischap/configs'; 2 | 3 | export default Configs.configMonorepo; -------------------------------------------------------------------------------- /tests/dummy.test.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable functional/no-expression-statements */ 2 | import { describe, it } from 'vitest'; 3 | 4 | describe('Dummy', () => { 5 | it('dummy', () => { 6 | TEUtils.strictEqual(1,1); 7 | }); 8 | }); 9 | -------------------------------------------------------------------------------- /tsconfig.base.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/tsconfig", 3 | "_version": "20.1.0", 4 | "extends": [ 5 | "@tsconfig/strictest/tsconfig.json", 6 | "@tsconfig/node20/tsconfig.json" 7 | ], 8 | "compilerOptions": { 9 | "allowJs": false, 10 | "checkJs": false, 11 | "moduleDetection": "force", 12 | "composite": true, 13 | "resolveJsonModule": true, 14 | "esModuleInterop": false, 15 | "declaration": true, 16 | "skipLibCheck": true, 17 | "emitDecoratorMetadata": true, 18 | "experimentalDecorators": true, 19 | "moduleResolution": "NodeNext", 20 | "isolatedModules": true, 21 | "sourceMap": true, 22 | "declarationMap": true, 23 | "noEmitOnError": false, 24 | "noErrorTruncation": true, 25 | "target": "ES2022", 26 | "module": "NodeNext", 27 | "incremental": true, 28 | "removeComments": false, 29 | "lib": [], 30 | "types": [], 31 | "plugins": [{ "name": "@effect/language-service" }] 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /tsconfig.check.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.esm.json", 3 | "compilerOptions": { 4 | "noEmit": true, 5 | "tsBuildInfoFile": ".tsbuildinfo/check.tsbuildinfo" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /tsconfig.esm.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.base.json", 3 | "include": ["esm/**/*.ts", "esm/**/*.mts", "esm/**/*.cts"], 4 | "compilerOptions": { 5 | "rootDir": "esm", 6 | "tsBuildInfoFile": ".tsbuildinfo/esm.tsbuildinfo", 7 | "declarationDir": "dist/dts", 8 | "declarationMap": true, 9 | "outDir": "dist/esm", 10 | "lib": ["ESNext"], 11 | "types": ["node"] 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.base.json", 3 | "include": [], 4 | "references": [ 5 | { "path": "./tsconfig.esm.json" }, 6 | { "path": "./tsconfig.others.json" } 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /tsconfig.others.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.base.json", 3 | "include": [ 4 | "**/*.ts", 5 | "**/*.mts", 6 | "**/*.cts", 7 | "**/*.js", 8 | "**/*.mjs", 9 | "**/*.cjs" 10 | ], 11 | "exclude": [ 12 | "esm/**/*.ts", 13 | "esm/**/*.mts", 14 | "esm/**/*.cts", 15 | "**/node_modules/**/*.ts", 16 | "**/node_modules/**/*.mts", 17 | "**/node_modules/**/*.cts", 18 | "**/node_modules/**/*.js", 19 | "**/node_modules/**/*.mjs", 20 | "**/node_modules/**/*.cjs", 21 | "dist/**/*.ts", 22 | "dist/**/*.mts", 23 | "dist/**/*.cts", 24 | "dist/**/*.js", 25 | "dist/**/*.mjs", 26 | "dist/**/*.cjs", 27 | "vite.config.ts.timestamp-*.mjs" 28 | ], 29 | "compilerOptions": { 30 | "rootDir": ".", 31 | "tsBuildInfoFile": ".tsbuildinfo/others.tsbuildinfo", 32 | "outDir": "dist/others", 33 | "lib": ["ESNext"], 34 | "types": ["node"], 35 | "allowJs": true, 36 | "checkJs": true 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import type { UserConfig } from 'vite'; 2 | 3 | export default { 4 | test: { 5 | include: ['./tests/*.test.ts'], 6 | isolate: false, 7 | fileParallelism: false, 8 | pool: 'threads' 9 | } 10 | } satisfies UserConfig; 11 | --------------------------------------------------------------------------------