├── .github └── workflows │ └── npm-publish.yml ├── .gitignore ├── .swcrc ├── CHANGELOG.md ├── LICENSE ├── README.md ├── jest.config.js ├── package.json ├── scripts ├── copy.js ├── mv.js └── rm.js ├── src ├── index.ts ├── match-pattern.ts ├── optional-pattern.ts └── string │ ├── index.ts │ └── naming-convention.ts ├── tests ├── match-pattern.test.ts └── optional-pattern.test.ts ├── tsconfig.cjs.json └── tsconfig.json /.github/workflows/npm-publish.yml: -------------------------------------------------------------------------------- 1 | name: Build Boost TS 2 | 3 | on: 4 | pull_request: 5 | types: [opened, synchronize, closed] 6 | branches: 7 | - main 8 | push: 9 | branches: 10 | - main 11 | 12 | jobs: 13 | build: 14 | runs-on: ubuntu-latest 15 | 16 | steps: 17 | - name: Checkout code 18 | uses: actions/checkout@v3 19 | 20 | - name: Use Node.js ${{ matrix.node-version }} 21 | uses: actions/setup-node@v3 22 | with: 23 | node-version: '18.10.0' 24 | 25 | - name: Install Dependencies 26 | working-directory: ${{github.workspace}} 27 | run: npm install 28 | 29 | - name: Unit Tests 30 | working-directory: ${{github.workspace}}/tests 31 | run: npm test 32 | env: 33 | CI: true 34 | 35 | # Generate changelog 36 | - name: Generate changelog 37 | id: changelog 38 | working-directory: ${{ github.workspace }} 39 | run: | 40 | # extract the changelog 41 | echo "::set-output name=tag::$(grep -oP '^## \[(\d+\.\d+\.\d+)\]' CHANGELOG.md | head -n 1 | cut -d '[' -f 2 | cut -d ']' -f 1)" 42 | echo v${{ steps.changelog.outputs.tag }} 43 | 44 | # Create git release 45 | - name: Github release 46 | uses: actions/create-release@v1 47 | if: github.event.pull_request.merged == true 48 | env: 49 | GITHUB_TOKEN: ${{ github.token }} 50 | with: 51 | tag_name: v${{ steps.changelog.outputs.tag }} 52 | release_name: ${{ steps.changelog.outputs.tag }} 53 | body: | 54 | Changes in this Release 55 | - ${{ steps.changelog.outputs.tag }} 56 | draft: false 57 | prerelease: false 58 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Regular folders 2 | dist/ 3 | node_modules/ 4 | lib 5 | 6 | # Ignore lock files 7 | *-lock.json 8 | 9 | # main used for test 10 | main.ts 11 | 12 | # Ignore other files 13 | *.tgz 14 | 15 | -------------------------------------------------------------------------------- /.swcrc: -------------------------------------------------------------------------------- 1 | { 2 | "jsc": { 3 | "parser": { 4 | "syntax": "typescript", 5 | "decorators": true 6 | }, 7 | "transform": { 8 | "legacyDecorator": true, 9 | "decoratorMetadata": true 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## 1.3.0 (2024-8-1) 4 | 5 | 6 | ### Features 7 | 8 | * adjust release script ([8601abf](https://github.com/expressots/boost-ts/commit/8601abf693049009387522ea77a9f0304f62a179)) 9 | * update scripts and build system ([#7](https://github.com/expressots/boost-ts/issues/7)) ([ac38096](https://github.com/expressots/boost-ts/commit/ac38096f6f0dc10a4341f5fb85a625f2eb843140)) 10 | 11 | 12 | ### Code Refactoring 13 | 14 | * update deps and scripts ([61556dc](https://github.com/expressots/boost-ts/commit/61556dcbdaa9d180778ca9ee499bff26663bc3a9)) 15 | * update engine version update ([183d15e](https://github.com/expressots/boost-ts/commit/183d15ea617ce983302b1fe2467c0840c3435881)) 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Richard Zampieri 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | [![Codecov][codecov-shield]][codecov-url] 6 | [![NPM][npm-shield]][npm-url] 7 | ![Build][build-shield] 8 | [![Contributors][contributors-shield]][contributors-url] 9 | [![Forks][forks-shield]][forks-url] 10 | [![Stargazers][stars-shield]][stars-url] 11 | [![Issues][issues-shield]][issues-url] 12 | [![MIT License][license-shield]][license-url] 13 | [![LinkedIn][linkedin-shield]][linkedin-url] 14 | 15 | 16 |
17 |
18 | 19 | Logo 20 | 21 | 22 |

ExpressoTS Framework

23 | 24 |

25 | Everything you need to know to build applications with ExpressoTS 26 |
27 | Explore the docs » 28 |
29 |
30 | Let's discuss 31 | · 32 | Report Bug 33 | · 34 | Request Feature 35 |

36 |
37 | 38 | 39 |
40 | Table of Contents 41 |
    42 |
  1. About The Project
  2. 43 |
  3. Getting Started
  4. 44 |
  5. Contributing
  6. 45 |
  7. Support the project
  8. 46 |
  9. License
  10. 47 |
48 |
49 | 50 | 51 | 52 | # About The Project 53 | 54 | ExpressoTS is a [Typescript](https://www.typescriptlang.org/) + [Node.js](https://nodejs.org/en/) lightweight framework for quick building scalable, easy to read and maintain, server-side applications 🐎 55 | 56 | ## Getting Started 57 | 58 | - Here is our [Site](https://expresso-ts.com/) 59 | - You can find our [Documentation here](https://doc.expresso-ts.com/) 60 | - Checkout our [First Steps documentation](https://doc.expresso-ts.com/docs/overview/first-steps) 61 | - Our [CLI Documentation](https://doc.expresso-ts.com/docs/cli/overview) 62 | 63 | ## Boost-TS 64 | 65 | Boost is a collection of libraries for the TypeScript programming language designed to enhance its capabilities across various domains. These libraries provide a wide range of functionality, enabling developers to leverage the full potential of TypeScript and streamline their development process. With Boost, TypeScript developers can efficiently tackle complex tasks, improve code readability, and maintainability, while also benefiting from advanced features and best practices. 66 | 67 | ## Available Libraries 68 | 69 | - Match Pattern 70 | - Optional Pattern 71 | 72 | ## Installation 73 | 74 | ```bash 75 | npm i @expressots/boost-ts 76 | ``` 77 | 78 | ## Match Pattern Usage 79 | 80 | - Using Match pattern with enums 81 | 82 | ```typescript 83 | import { match } from "./match-pattern"; 84 | 85 | const enum Coin { 86 | Penny, 87 | Nickel, 88 | Dime, 89 | Quarter, 90 | } 91 | 92 | function valueInCents(coin: Coin): number { 93 | return match(coin, { 94 | [Coin.Penny]: () => 1, 95 | [Coin.Nickel]: () => 5, 96 | [Coin.Dime]: () => 10, 97 | [Coin.Quarter]: () => 25, 98 | }); 99 | } 100 | 101 | console.log(valueInCents(Coin.Penny)); // 1 102 | ``` 103 | 104 | - Using Match pattern with numbers 105 | 106 | ```typescript 107 | let isNumber: number = 1; 108 | 109 | const result = match(isNUmber, { 110 | 1: () => 1, 111 | 2: () => 2, 112 | _: () => "No number found", 113 | }); 114 | 115 | console.log(result); // 1 116 | ``` 117 | 118 | - Using Match pattern with boolean 119 | 120 | ```typescript 121 | let isOn: boolean = true; 122 | 123 | const result = match(isOn, { 124 | true: () => "The light is on", 125 | false: () => "The light is off", 126 | }); 127 | 128 | console.log(result); // The light is off 129 | ``` 130 | 131 | - Using Match pattern with Optional pattern 132 | 133 | ```typescript 134 | import { Some, None, Optional, matchOptional } from "./optional-pattern"; 135 | import { match } from "./match-pattern"; 136 | 137 | const v1: Optional = Some(1); 138 | const v2: Optional = None(); 139 | 140 | const result = match(v1, { 141 | Some: (x) => x + 1, 142 | None: 0, 143 | }); 144 | 145 | console.log(result); // 2 146 | ``` 147 | 148 | - Other possible combinations 149 | - "expressions..=expressions" -> numbers or characters 150 | - "isOr: this | that | other" 151 | - "Regex: "/[a-z]/" 152 | 153 | ```typescript 154 | const result = match("a", { 155 | "1..=13": () => "Between 1 and 13", 156 | "25 | 50 | 100": () => "A bill", 157 | "a..=d": () => "A letter", 158 | "/[a-z]/": () => "A lowercase letter", 159 | _: () => "Default", 160 | }); 161 | console.log(result); // A letter 162 | ``` 163 | 164 | ## Optional Pattern Usage 165 | 166 | ```typescript 167 | const someValue: Optional = Some(1); 168 | 169 | function plusOne(x: Optional): number { 170 | return match(x, { 171 | Some: (x) => x + 3, 172 | None: 0, 173 | }); 174 | } 175 | 176 | console.log(plusOne(None())); // 0 177 | ``` 178 | 179 | ## Contributing 180 | 181 | Welcome to the ExpressoTS community, a place bustling with innovative minds just like yours. We're absolutely thrilled to have you here! 182 | ExpressoTS is more than just a TypeScript framework; it's a collective effort by developers who are passionate about creating a more efficient, secure, and robust web ecosystem. We firmly believe that the best ideas come from a diversity of perspectives, backgrounds, and skills. 183 | 184 | Why Contribute to Documentation? 185 | 186 | - **Share Knowledge**: If you've figured out something cool, why keep it to yourself? 187 | - **Build Your Portfolio**: Contributing to an open-source project like ExpressoTS is a great way to showcase your skills. 188 | - **Join a Network**: Get to know a community of like-minded developers. 189 | - **Improve the Product**: Help us fill in the gaps, correct errors, or make complex topics easier to understand. 190 | 191 | Ready to contribute? 192 | 193 | - [Contributing Guidelines](https://github.com/expressots/expressots/blob/main/CONTRIBUTING.md) 194 | - [How to Contribute](https://github.com/expressots/expressots/blob/main/CONTRIBUTING_HOWTO.md) 195 | - [Coding Guidelines](https://github.com/rsaz/TypescriptCodingGuidelines) 196 | 197 | ## Support the project 198 | 199 | ExpressoTS is an independent open source project with ongoing development made possible thanks to your support. If you'd like to help, please consider: 200 | 201 | - Become a **[sponsor on GitHub](https://github.com/sponsors/expressots)** 202 | - Follow the **[organization](https://github.com/expressots)** on GitHub and Star ⭐ the project 203 | - Subscribe to the Twitch channel: **[Richard Zampieri](https://www.twitch.tv/richardzampieri)** 204 | - Join our **[Discord](https://discord.com/invite/PyPJfGK)** 205 | - Contribute submitting **[issues and pull requests](https://github.com/expressots/expressots/issues)** 206 | - Share the project with your friends and colleagues 207 | 208 | ## License 209 | 210 | Distributed under the MIT License. See [`LICENSE.txt`](https://github.com/expressots/expressots/blob/main/LICENSE) for more information. 211 | 212 |

(back to top)

213 | 214 | 215 | 216 | 217 | [codecov-url]: https://codecov.io/gh/expressots/boost-ts 218 | [codecov-shield]: https://img.shields.io/codecov/c/gh/expressots/boost-ts/main?style=for-the-badge&logo=codecov&labelColor=FB9AD1 219 | [npm-url]: https://www.npmjs.com/package/@expressots/boost-ts 220 | [npm-shield]: https://img.shields.io/npm/v/@expressots/boost-ts?style=for-the-badge&logo=npm&color=9B3922 221 | [build-shield]: https://img.shields.io/github/actions/workflow/status/expressots/boost-ts/build.yaml?branch=main&style=for-the-badge&logo=github 222 | [contributors-shield]: https://img.shields.io/github/contributors/expressots/boost-ts?style=for-the-badge 223 | [contributors-url]: https://github.com/expressots/boost-ts/graphs/contributors 224 | [forks-shield]: https://img.shields.io/github/forks/expressots/boost-ts?style=for-the-badge 225 | [forks-url]: https://github.com/expressots/boost-ts/forks 226 | [stars-shield]: https://img.shields.io/github/stars/expressots/boost-ts?style=for-the-badge 227 | [stars-url]: https://github.com/expressots/boost-ts/stargazers 228 | [issues-shield]: https://img.shields.io/github/issues/expressots/boost-ts?style=for-the-badge 229 | [issues-url]: https://github.com/expressots/boost-ts/issues 230 | [license-shield]: https://img.shields.io/github/license/expressots/boost-ts?style=for-the-badge 231 | [license-url]: https://github.com/expressots/boost-ts/blob/main/LICENSE 232 | [linkedin-shield]: https://img.shields.io/badge/-LinkedIn-black.svg?style=for-the-badge&logo=linkedin&colorB=555 233 | [linkedin-url]: https://www.linkedin.com/company/expresso-ts/ 234 | [product-screenshot]: images/screenshot.png 235 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | transform: { 3 | "^.+\\.(t|j)sx?$": "@swc/jest", 4 | }, 5 | testEnvironment: "node", 6 | testMatch: ["**/*.spec.ts", "**/*.test.ts"], 7 | }; 8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@expressots/boost-ts", 3 | "version": "1.3.0", 4 | "description": "Expressots: Boost is a collection of libraries for the TypeScript programming language designed to enhance its capabilities across various domains. (@boost-ts)", 5 | "main": "./lib/cjs/index.js", 6 | "types": "./lib/cjs/types/index.d.ts", 7 | "exports": { 8 | ".": { 9 | "import": { 10 | "types": "./lib/esm/types/index.d.ts", 11 | "default": "./lib/esm/index.mjs" 12 | }, 13 | "require": { 14 | "types": "./lib/cjs/types/index.d.ts", 15 | "default": "./lib/cjs/index.js" 16 | } 17 | } 18 | }, 19 | "files": [ 20 | "lib/**/*" 21 | ], 22 | "license": "MIT", 23 | "keywords": [ 24 | "pattern-matching", 25 | "text-utils", 26 | "expressots" 27 | ], 28 | "repository": { 29 | "type": "git", 30 | "url": "git+https://github.com/expressots/boost-ts.git" 31 | }, 32 | "publishConfig": { 33 | "access": "public" 34 | }, 35 | "engines": { 36 | "node": ">=18.0.0" 37 | }, 38 | "author": "Richard Zampieri", 39 | "scripts": { 40 | "prepare": "husky", 41 | "clean": "node scripts/rm.js lib", 42 | "copy": "node scripts/copy.js package.json README.md CHANGELOG.md lib", 43 | "build": "npm run clean && npm run build:cjs && npm run copy", 44 | "build:cjs": "tsc -p tsconfig.cjs.json", 45 | "release": "release-it", 46 | "prepublish": "npm run build && npm pack", 47 | "publish": "npm publish --tag latest", 48 | "test": "vitest run --reporter default", 49 | "test:watch": "vitest run --watch", 50 | "coverage": "vitest run --coverage", 51 | "format": "prettier --write \"src/**/*.ts\" --cache", 52 | "lint": "eslint \"src/**/*.ts\"", 53 | "lint:fix": "eslint \"src/**/*.ts\" --fix" 54 | }, 55 | "devDependencies": { 56 | "@swc/core": "1.3.37", 57 | "@swc/jest": "0.2.24", 58 | "@types/jest": "29.5.0", 59 | "jest": "29.5.0", 60 | "ts-node-dev": "2.0.0", 61 | "@codecov/vite-plugin": "0.0.1-beta.6", 62 | "@commitlint/cli": "19.2.1", 63 | "@commitlint/config-conventional": "19.2.2", 64 | "@expressots/core": "2.15.0", 65 | "@release-it/conventional-changelog": "8.0.1", 66 | "@types/express": "4.17.21", 67 | "@types/node": "22.0.2", 68 | "@typescript-eslint/eslint-plugin": "7.16.1", 69 | "@typescript-eslint/parser": "7.16.1", 70 | "@vitest/coverage-v8": "2.0.3", 71 | "eslint": "8.57.0", 72 | "eslint-config-prettier": "9.1.0", 73 | "husky": "9.1.1", 74 | "prettier": "3.3.3", 75 | "release-it": "17.6.0", 76 | "typescript": "5.5.3", 77 | "vite": "5.3.4", 78 | "vite-tsconfig-paths": "4.3.2", 79 | "vitest": "2.0.3" 80 | }, 81 | "dependencies": { 82 | "rimraf": "6.0.1" 83 | }, 84 | "release-it": { 85 | "git": { 86 | "commitMessage": "chore(release): ${version}" 87 | }, 88 | "github": { 89 | "release": true 90 | }, 91 | "npm": { 92 | "publish": false 93 | }, 94 | "plugins": { 95 | "@release-it/conventional-changelog": { 96 | "infile": "CHANGELOG.md", 97 | "preset": { 98 | "name": "conventionalcommits", 99 | "types": [ 100 | { 101 | "type": "feat", 102 | "section": "Features" 103 | }, 104 | { 105 | "type": "fix", 106 | "section": "Bug Fixes" 107 | }, 108 | { 109 | "type": "perf", 110 | "section": "Performance Improvements" 111 | }, 112 | { 113 | "type": "revert", 114 | "section": "Reverts" 115 | }, 116 | { 117 | "type": "docs", 118 | "section": "Documentation" 119 | }, 120 | { 121 | "type": "refactor", 122 | "section": "Code Refactoring" 123 | }, 124 | { 125 | "type": "test", 126 | "section": "Tests" 127 | }, 128 | { 129 | "type": "build", 130 | "section": "Build System" 131 | }, 132 | { 133 | "type": "ci", 134 | "section": "Continuous Integrations" 135 | } 136 | ] 137 | } 138 | } 139 | } 140 | } 141 | } 142 | -------------------------------------------------------------------------------- /scripts/copy.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs"); 2 | const path = require("path"); 3 | 4 | function copyRecursiveSync(src, dest) { 5 | const exists = fs.existsSync(src); 6 | const stats = exists && fs.statSync(src); 7 | const isDirectory = exists && stats.isDirectory(); 8 | 9 | if (isDirectory) { 10 | fs.mkdirSync(dest); 11 | fs.readdirSync(src).forEach(function (childItemName) { 12 | copyRecursiveSync(path.join(src, childItemName), path.join(dest, childItemName)); 13 | }); 14 | } else { 15 | fs.copyFileSync(src, dest); 16 | } 17 | } 18 | 19 | if (process.argv.length < 4) { 20 | process.stderr.write("Usage: node copy.js ... \n"); 21 | process.exit(1); 22 | } 23 | 24 | const destination = process.argv[process.argv.length - 1]; 25 | 26 | for (let i = 2; i < process.argv.length - 1; i++) { 27 | const origin = process.argv[i]; 28 | copyRecursiveSync(origin, path.join(destination, path.basename(origin))); 29 | process.stdout.write(`Copied: ${origin} to ${destination}\n`); 30 | } 31 | -------------------------------------------------------------------------------- /scripts/mv.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs").promises; 2 | 3 | const moveFile = async (origin, destination) => { 4 | try { 5 | await fs.access(origin); 6 | } catch (error) { 7 | process.stderr.write(`Error: Origin '${origin}' not found\n`); 8 | process.exit(1); 9 | } 10 | 11 | try { 12 | await fs.rename(origin, destination); 13 | process.stdout.write(`Move: ${origin} to ${destination}\n`); 14 | } catch (error) { 15 | process.stderr.write(`Error: Unable to move '${origin}' to '${destination}'\n`); 16 | process.exit(1); 17 | } 18 | }; 19 | 20 | if (process.argv.length !== 4) { 21 | process.stderr.write("Usage: node mv.js \n"); 22 | process.exit(1); 23 | } 24 | 25 | const origin = process.argv[2]; 26 | const destination = process.argv[3]; 27 | 28 | moveFile(origin, destination); 29 | -------------------------------------------------------------------------------- /scripts/rm.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs").promises; 2 | 3 | const removeTarget = async (target) => { 4 | try { 5 | await fs.rm(target, { recursive: true, force: true }); 6 | process.stdout.write(`Removed: ${target}\n`); 7 | } catch (error) { 8 | process.stderr.write(`Error: Unable to remove '${target}'\n`); 9 | process.exit(1); 10 | } 11 | }; 12 | 13 | if (process.argv.length !== 3) { 14 | process.stderr.write("Usage: node rm.js \n"); 15 | process.exit(1); 16 | } 17 | 18 | const target = process.argv[2]; 19 | removeTarget(target); 20 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./match-pattern"; 2 | export * from "./optional-pattern"; 3 | export * from "./string"; -------------------------------------------------------------------------------- /src/match-pattern.ts: -------------------------------------------------------------------------------- 1 | import { None, Some } from "./optional-pattern"; 2 | 3 | function isSome(value: any): value is Some { 4 | return value && value.tag === "Some"; 5 | } 6 | 7 | function isNone(value: any): value is None { 8 | return value && value.tag === "None"; 9 | } 10 | 11 | type OptionalPattern = { 12 | Some?: (value: U) => T; 13 | None?: T; 14 | }; 15 | 16 | type MatchPatterns = 17 | | { 18 | [key: string]: () => T; 19 | } 20 | | OptionalPattern; 21 | 22 | export function match(value: any, patterns: MatchPatterns): T { 23 | if (isSome(value) && patterns.Some) { 24 | return patterns.Some(value.value); 25 | } else if (isNone(value) && patterns.None !== undefined) { 26 | return patterns.None as T; 27 | } 28 | 29 | for (const p in patterns) { 30 | if (p === "Some" || p === "None") continue; 31 | 32 | const patternKey = String(p); 33 | const isRange = patternKey.includes("..="); 34 | const isOr = patternKey.includes("|"); 35 | const isRegex = patternKey.startsWith("/") && patternKey.endsWith("/"); 36 | 37 | if (isRange) { 38 | const [start, end] = patternKey 39 | .split("..=") 40 | .map((s) => (isNaN(Number(s)) ? s : Number(s))); 41 | 42 | if ( 43 | (typeof value === "number" && 44 | typeof start === "number" && 45 | typeof end === "number" && 46 | value >= start && 47 | value <= end) || 48 | (typeof value === "string" && 49 | typeof start === "string" && 50 | typeof end === "string" && 51 | value >= start && 52 | value <= end) 53 | ) { 54 | return patterns[p](); 55 | } 56 | } else if (isOr) { 57 | const patternValues = patternKey 58 | .split("|") 59 | .map((s) => (isNaN(Number(s)) ? s : Number(s))); 60 | 61 | if (patternValues.includes(value)) { 62 | return patterns[p](); 63 | } 64 | } else if (isRegex) { 65 | const regex = new RegExp(patternKey.slice(1, -1)); 66 | 67 | if (regex.test(value)) { 68 | return patterns[p](); 69 | } 70 | } else { 71 | if (value.toString() === patternKey) { 72 | return patterns[p](); 73 | } 74 | } 75 | } 76 | 77 | if (patterns["_"]) { 78 | return patterns["_"](); 79 | } 80 | 81 | throw new Error(`No match found for the given value ${value}`); 82 | } 83 | -------------------------------------------------------------------------------- /src/optional-pattern.ts: -------------------------------------------------------------------------------- 1 | export type None = { tag: "None" }; 2 | export type Some = { tag: "Some", value: T}; 3 | export type Optional = Some | None; 4 | 5 | export function Some(value: T): Some { 6 | return { tag: "Some", value }; 7 | } 8 | 9 | export function None(): None { 10 | return { tag: "None" }; 11 | } 12 | 13 | type MatchPatterns = { 14 | Some?: (value: T extends Some ? V : never) => U; 15 | None?: U; 16 | } 17 | 18 | export function matchOptional, U>(value: T, patterns: MatchPatterns): U { 19 | 20 | if (patterns[value.tag]) { 21 | if (value.tag === "Some") { 22 | return (patterns[value.tag] as (value: T extends Some ? V : never) => U)(value.value); 23 | } else { 24 | return patterns[value.tag] as U; 25 | } 26 | } 27 | 28 | if (patterns["_"]) { 29 | return patterns["_"] as U; 30 | } 31 | 32 | throw new Error(`No match found for the given value ${value}`); 33 | } 34 | 35 | -------------------------------------------------------------------------------- /src/string/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./naming-convention"; -------------------------------------------------------------------------------- /src/string/naming-convention.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Converts a string from any case (camelCase, PascalCase, kebab-case, snake_case) to camelCase. 3 | * @param str - The input string to be converted. 4 | * @returns The converted string in camelCase. 5 | */ 6 | export function anyCaseToCamelCase(str: string): string { 7 | return str 8 | .replace(/[-_]+(.)?/g, (_, char) => (char ? char.toUpperCase() : '')) 9 | .replace(/^[A-Z]/, (char) => char.toLowerCase()); 10 | } 11 | 12 | /** 13 | * Converts a string from any case (camelCase, PascalCase, kebab-case, snake_case) to kebab-case. 14 | * @param str - The input string to be converted. 15 | * @returns The converted string in kebab-case. 16 | */ 17 | export function anyCaseToKebabCase(str: string): string { 18 | return str 19 | .replace(/([a-z0-9])([A-Z])/g, '$1-$2') // Convert camelCase and PascalCase to kebab-case 20 | .replace(/_/g, '-') // Convert snake_case to kebab-case 21 | .toLowerCase(); // Ensure all characters are lowercase 22 | } 23 | 24 | /** 25 | * Converts a string from any case (camelCase, PascalCase, kebab-case, snake_case) to PascalCase. 26 | * @param str - The input string to be converted. 27 | * @returns The converted string in PascalCase. 28 | */ 29 | export function anyCaseToPascalCase(str: string): string { 30 | return str 31 | .replace(/[-_]+(.)?/g, (_, char) => (char ? char.toUpperCase() : '')) 32 | .replace(/^[a-z]/, (char) => char.toUpperCase()); 33 | } 34 | 35 | /** 36 | * Converts a string from any case (camelCase, PascalCase, kebab-case, snake_case) to snake_case. 37 | * @param str - The input string to be converted. 38 | * @returns The converted string in snake_case. 39 | */ 40 | export function anyCaseToSnakeCase(str: string): string { 41 | return str 42 | .replace(/([a-z0-9])([A-Z])/g, '$1_$2') 43 | .replace(/[-]+/g, '_') 44 | .toLowerCase(); 45 | } 46 | 47 | /** 48 | * Converts a string from any case (camelCase, PascalCase, kebab-case, snake_case) to UPPER CASE. 49 | * @param str - The input string to be converted. 50 | * @returns The converted string in UPPER CASE. 51 | */ 52 | export function anyCaseToUpperCase(str: string): string { 53 | return str.replace(/[-_]+(.)?/g, (_, char) => (char ? char.toUpperCase() : '')).toUpperCase(); 54 | } 55 | 56 | /** 57 | * Converts a string from any case (camelCase, PascalCase, kebab-case, snake_case) to lower case. 58 | * @param str - The input string to be converted. 59 | * @returns The converted string in lower case. 60 | */ 61 | export function anyCaseToLowerCase(str: string): string { 62 | return str.replace(/[-_]+(.)?/g, (_, char) => (char ? char.toLowerCase() : '')).toLowerCase(); 63 | } 64 | 65 | -------------------------------------------------------------------------------- /tests/match-pattern.test.ts: -------------------------------------------------------------------------------- 1 | import { match } from "../src/match-pattern"; 2 | import { describe, test, expect } from "vitest"; 3 | 4 | describe("testing match pattern function", () => { 5 | const matchValidates = { 6 | 0: () => "this is 0", 7 | 99: () => "this is 99", 8 | "11..=22": () => "this number is in between 11 and 22", 9 | "/abc/": () => "this is a regex", 10 | "2 | 3 | 5 | 8": () => "this number is a prime number", 11 | false: () => "this is a boolean", 12 | "a..=d": () => "this is a char", 13 | "1..=13": () => "Between 1 and 13", 14 | "25 | 50 | 100": () => "A bill", 15 | "/[a-z]/": () => "A lowercase letter", 16 | _: () => "default", 17 | }; 18 | 19 | const enum Coin { 20 | Penny, 21 | Nickel, 22 | Dime, 23 | Quarter, 24 | } 25 | 26 | test("match pattern - validates", () => { 27 | expect(match(0, matchValidates)).toBe("this is 0"); 28 | expect(match(99, matchValidates)).toBe("this is 99"); 29 | expect(match(11, matchValidates)).toBe("this number is in between 11 and 22"); 30 | expect(match(/abc/, matchValidates)).toBe("this is a regex"); 31 | expect(match(false, matchValidates)).toBe("this is a boolean"); 32 | expect(match("a", matchValidates)).toBe("this is a char"); 33 | expect(match(5, matchValidates)).toBe("this number is a prime number"); 34 | expect(match(10, matchValidates)).toBe("Between 1 and 13"); 35 | expect(match(25, matchValidates)).toBe("A bill"); 36 | expect(match("z", matchValidates)).toBe("A lowercase letter"); 37 | expect(match("1", matchValidates)).toBe("default"); 38 | }); 39 | 40 | test("match pattern - enum", () => { 41 | expect( 42 | match(Coin.Dime, { 43 | [Coin.Penny]: () => 1, 44 | [Coin.Nickel]: () => 5, 45 | [Coin.Dime]: () => 10, 46 | [Coin.Quarter]: () => 25, 47 | }) 48 | ).toBe(10); 49 | }); 50 | }); 51 | -------------------------------------------------------------------------------- /tests/optional-pattern.test.ts: -------------------------------------------------------------------------------- 1 | import { Some, None, Optional } from "../src/optional-pattern"; 2 | import { match } from "../src/match-pattern"; 3 | import { describe, test, expect } from "vitest"; 4 | 5 | describe("testing optional pattern function", () => { 6 | const v1: Optional = Some(1); 7 | const v2: Optional = None(); 8 | 9 | test("optional pattern", () => { 10 | expect( 11 | match(v1, { 12 | Some: (x) => x + 1, 13 | None: 0, 14 | }) 15 | ).toBe(2); 16 | 17 | expect( 18 | match(v2, { 19 | Some: (x) => x + 1, 20 | None: 0, 21 | }) 22 | ).toBe(0); 23 | }); 24 | }); 25 | -------------------------------------------------------------------------------- /tsconfig.cjs.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "module": "commonjs", 5 | "target": "ES2021", 6 | "moduleResolution": "node", 7 | "outDir": "lib/cjs", 8 | "declarationDir": "lib/cjs/types" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strict": true, 4 | "esModuleInterop": true, 5 | "forceConsistentCasingInFileNames": true, 6 | "skipLibCheck": true, 7 | "strictNullChecks": false, 8 | "checkJs": true, 9 | "allowJs": true, 10 | "declaration": true, 11 | "noImplicitAny": false, 12 | "allowSyntheticDefaultImports": true, 13 | "experimentalDecorators": true, 14 | "emitDecoratorMetadata": true, 15 | "types": ["node", "vitest/globals"] 16 | }, 17 | "include": ["./src/**/*.ts"], 18 | "exclude": ["node_modules", "**/__tests__/*.spec.ts", "scripts/**/*"] 19 | } 20 | --------------------------------------------------------------------------------