├── .husky ├── .gitignore └── pre-commit ├── packages ├── .placeholder └── fastify │ ├── CHANGELOG.md │ ├── .gitignore │ ├── README.md │ ├── tsconfig.json │ ├── tsconfig.es6.json │ ├── tsconfig.build.json │ ├── package.json │ ├── test │ └── index.test.ts │ └── src │ └── index.ts ├── .ci.npmrc ├── codecov.yml ├── scripts └── jest-setup.ts ├── README.md ├── .gitignore ├── .gitpod.yml ├── tsconfig.json ├── .prettierrc.js ├── .editorconfig ├── pre-commit.sh ├── .vscode ├── newtype.code-snippets ├── operators.code-snippets ├── launch.json ├── settings.json ├── morphic.code-snippets └── common-imports.code-snippets ├── lerna.json ├── tsconfig.jest.json ├── examples ├── handler.ts └── basic-auth.ts ├── .github └── workflows │ └── test.yml ├── jest.config.js ├── tsconfig.base.json ├── LICENSE.md ├── .eslintrc.js └── package.json /.husky/.gitignore: -------------------------------------------------------------------------------- 1 | _ 2 | -------------------------------------------------------------------------------- /packages/.placeholder: -------------------------------------------------------------------------------- 1 | // -------------------------------------------------------------------------------- /packages/fastify/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | -------------------------------------------------------------------------------- /.ci.npmrc: -------------------------------------------------------------------------------- 1 | //registry.npmjs.org/:_authToken=${NPM_TOKEN} -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | sh pre-commit.sh -------------------------------------------------------------------------------- /packages/fastify/.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | scripts/merge.ts 3 | scripts/genp.ts 4 | bench/ -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | status: 3 | project: yes 4 | patch: no 5 | changes: no 6 | -------------------------------------------------------------------------------- /scripts/jest-setup.ts: -------------------------------------------------------------------------------- 1 | import "isomorphic-fetch" 2 | import "@effect-ts/core/Tracing/Enable" 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## effect-ts-fastify 2 | Refer to https://github.com/effect-ts/fastify for more updates. 3 | -------------------------------------------------------------------------------- /packages/fastify/README.md: -------------------------------------------------------------------------------- 1 | ### Effect-TS Fastify 2 | 3 | This package is a work in progress wrapper around `fastify` 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules 3 | .DS_Store 4 | /.log/ 5 | /packages/express/.ultra.cache.json 6 | *.tsbuildinfo 7 | *.cache.json 8 | /coverage/ 9 | -------------------------------------------------------------------------------- /.gitpod.yml: -------------------------------------------------------------------------------- 1 | tasks: 2 | - init: yarn && yarn build 3 | github: 4 | prebuilds: 5 | addCheck: true 6 | vscode: 7 | extensions: 8 | - dbaeumer.vscode-eslint 9 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.base.json", 3 | "files": [], 4 | "compilerOptions": { 5 | "baseUrl": ".", 6 | "target": "ES2018" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | semi: false, 3 | trailingComma: 'none', 4 | singleQuote: false, 5 | printWidth: 88, 6 | tabWidth: 2, 7 | endOfLine: 'auto' 8 | }; 9 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: http://EditorConfig.org 2 | 3 | # top-most EditorConfig file 4 | root = true 5 | 6 | [*] 7 | indent_style = space 8 | indent_size = 2 9 | end_of_line = lf 10 | -------------------------------------------------------------------------------- /pre-commit.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | if yarn yarn-deduplicate -fl; then 3 | echo "No duplicates found. Pursuing..." 4 | else 5 | echo "ERROR: Lockfile contains duplicates!" 6 | echo "deduplicating..." 7 | yarn yarn-deduplicate 8 | yarn 9 | echo "deduplication finished" 10 | exit 1 11 | fi 12 | -------------------------------------------------------------------------------- /packages/fastify/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "lib": ["DOM"], 5 | "target": "ES2018", 6 | "emitDecoratorMetadata": true, 7 | "experimentalDecorators": true 8 | }, 9 | "include": ["src/**/*", "scripts/**/*", "bench/**/*", "test/**/*", "demo/**/*"] 10 | } 11 | -------------------------------------------------------------------------------- /.vscode/newtype.code-snippets: -------------------------------------------------------------------------------- 1 | { 2 | "Classic Newtype": { 3 | "prefix": "newtype", 4 | "body": [ 5 | "const $1_ = NT.typeDef<$0>()(\"@newtype/$1\")", 6 | "export interface $1 extends NT.TypeOf {}", 7 | "export const $1 = NT.newtype<$1>()($1_)" 8 | ], 9 | "description": "Defines a Morphic newtype via iso" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /lerna.json: -------------------------------------------------------------------------------- 1 | { 2 | "packages": [ 3 | "packages/*", 4 | "packages_be/*", 5 | "packages_fe/*", 6 | "packages_http/*", 7 | "packages_inc/*", 8 | "packages_sys/*" 9 | ], 10 | "npmClient": "yarn", 11 | "useWorkspaces": true, 12 | "version": "independent", 13 | "command": { 14 | "version": { 15 | "message": "chore(release): publish" 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /tsconfig.jest.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "files": [], 4 | "compilerOptions": { 5 | "baseUrl": ".", 6 | "target": "ES2018", 7 | "plugins": [ 8 | { 9 | "transform": "@effect-ts/tracing-plugin", 10 | "moduleMap": { 11 | "^packages/fastify/src/(.*)": "(@tcmlabs/effect-ts-fastify): src/$1", 12 | "^packages/fastify/test/(.*)": "(@tcmlabs/effect-ts-fastify): test/$1" 13 | } 14 | } 15 | ] 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /packages/fastify/tsconfig.es6.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "outDir": "build/esm", 5 | "module": "ES6", 6 | "target": "ES2018", 7 | "incremental": true, 8 | "tsBuildInfoFile": "tsbuildinfo/es6.tsbuildinfo", 9 | "removeComments": false, 10 | "plugins": [ 11 | { 12 | "transform": "@effect-ts/tracing-plugin", 13 | "moduleMap": { 14 | "^src/(.*)": "(@tcmlabs/effect-ts-fastify): _src/$1" 15 | } 16 | } 17 | ] 18 | }, 19 | "include": ["src/**/*"], 20 | "exclude": ["test", "node_modules"] 21 | } 22 | -------------------------------------------------------------------------------- /packages/fastify/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "compilerOptions": { 4 | "outDir": "build", 5 | "target": "ES2018", 6 | "module": "CommonJS", 7 | "incremental": true, 8 | "tsBuildInfoFile": "tsbuildinfo/build.tsbuildinfo", 9 | "removeComments": false, 10 | "plugins": [ 11 | { 12 | "transform": "@effect-ts/tracing-plugin", 13 | "moduleMap": { 14 | "^src/(.*)": "(@tcmlabs/effect-ts-fastify): _src/$1" 15 | } 16 | } 17 | ] 18 | }, 19 | "include": ["src/**/*"], 20 | "exclude": ["test", "node_modules"] 21 | } 22 | -------------------------------------------------------------------------------- /examples/handler.ts: -------------------------------------------------------------------------------- 1 | import { pipe } from "@effect-ts/core" 2 | import * as T from "@effect-ts/core/Effect" 3 | import * as N from "@effect-ts/node/Runtime" 4 | import { get, listen, FastifyLive } from "../packages/fastify/src" 5 | 6 | pipe( 7 | T.gen(function* (_) { 8 | yield* _( 9 | get("/", (_request, _reply) => 10 | T.succeedWith(() => { 11 | return "OK" 12 | }) 13 | ) 14 | ) 15 | 16 | yield* _(listen(3000, "localhost")) 17 | console.log("listening to localhost:3000!") 18 | yield* _(T.never) 19 | }), 20 | T.provideSomeLayer(FastifyLive), 21 | N.runMain 22 | ) 23 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Node CI 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - master 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - uses: actions/checkout@v3 14 | - name: Use Node.js 16.x 15 | uses: actions/setup-node@v3 16 | with: 17 | node-version: 16.x 18 | - name: npm install, build, and test 19 | run: | 20 | npm install -g yarn 21 | yarn 22 | yarn run ci 23 | env: 24 | CI: "true" 25 | 26 | - uses: codecov/codecov-action@v1.0.3 27 | with: 28 | token: ${{secrets.CODECOV_TOKEN}} 29 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line 2 | module.exports = { 3 | preset: "ts-jest", 4 | testEnvironment: "node", 5 | rootDir: "./", 6 | clearMocks: true, 7 | collectCoverage: false, 8 | coverageDirectory: "coverage", 9 | collectCoverageFrom: ["packages/**/src/**/*.ts"], 10 | setupFiles: ["./scripts/jest-setup.ts"], 11 | modulePathIgnorePatterns: [ 12 | "/packages/.*/build", 13 | "/packages/.*/compiler-debug", 14 | "/_tmp" 15 | ], 16 | verbose: true, 17 | moduleNameMapper: { 18 | "@tcmlabs/effect-ts-fastify/(.*)$": "/packages/fastify/build/$1", 19 | "@tcmlabs/effect-ts-fastify$": "/packages/fastify/build" 20 | }, 21 | globals: { 22 | "ts-jest": { 23 | tsconfig: "/tsconfig.jest.json", 24 | compiler: "ttypescript" 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /.vscode/operators.code-snippets: -------------------------------------------------------------------------------- 1 | { 2 | "Pipe Operator |": { 3 | "prefix": "|", 4 | "body": ["[\"|>\"]()"], 5 | "description": "Pipe Operator" 6 | }, 7 | "Flow Operator": { 8 | "prefix": ">", 9 | "body": ["[\">>\"]()"], 10 | "description": "Flow Operator" 11 | }, 12 | "Operators Import": { 13 | "prefix": "op", 14 | "body": ["import \"@effect-ts/core/Operators\""], 15 | "description": "Operators Import" 16 | }, 17 | "Gen Function": { 18 | "prefix": "gen", 19 | "body": ["function* (_) {}"], 20 | "description": "Generator FUnction with _ input" 21 | }, 22 | "Gen Function $": { 23 | "prefix": "gen$", 24 | "body": ["function* ($) {}"], 25 | "description": "Generator FUnction with _ input" 26 | }, 27 | "Gen Yield *": { 28 | "prefix": "!", 29 | "body": ["yield* _()"], 30 | "description": "Yield generator calling _()" 31 | }, 32 | "Gen Yield $": { 33 | "prefix": "$", 34 | "body": ["yield* $()"], 35 | "description": "Yield generator calling $()" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /tsconfig.base.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "downlevelIteration": true, 4 | "resolveJsonModule": true, 5 | "esModuleInterop": true, 6 | "declaration": true, 7 | "skipLibCheck": true, 8 | "emitDecoratorMetadata": true, 9 | "experimentalDecorators": true, 10 | "preserveSymlinks": true, 11 | "moduleResolution": "node", 12 | "noEmit": false, 13 | "lib": ["es2020", "DOM"], 14 | "sourceMap": true, 15 | "declarationMap": true, 16 | "strict": true, 17 | "noImplicitReturns": false, 18 | "noUnusedLocals": true, 19 | "noUnusedParameters": false, 20 | "noFallthroughCasesInSwitch": true, 21 | "noEmitOnError": false, 22 | "noErrorTruncation": true, 23 | "allowJs": false, 24 | "checkJs": false, 25 | "forceConsistentCasingInFileNames": true, 26 | "suppressImplicitAnyIndexErrors": true, 27 | "stripInternal": true, 28 | "noImplicitAny": true, 29 | "noImplicitThis": true, 30 | "strictNullChecks": true, 31 | "baseUrl": ".", 32 | "target": "ES2018" 33 | }, 34 | "exclude": ["node_modules", "build", "lib"] 35 | } 36 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "type": "node", 6 | "request": "launch", 7 | "name": "Jest All", 8 | "program": "${workspaceFolder}/node_modules/.bin/jest", 9 | "args": ["--runInBand"], 10 | "console": "integratedTerminal", 11 | "internalConsoleOptions": "neverOpen", 12 | "disableOptimisticBPs": true, 13 | "windows": { 14 | "program": "${workspaceFolder}/node_modules/jest/bin/jest", 15 | } 16 | }, 17 | { 18 | "type": "node", 19 | "request": "launch", 20 | "name": "Jest Current File", 21 | "program": "${workspaceFolder}/node_modules/.bin/jest", 22 | "args": [ 23 | "${fileBasenameNoExtension}", 24 | "--config", 25 | "jest.config.js" 26 | ], 27 | "console": "integratedTerminal", 28 | "internalConsoleOptions": "neverOpen", 29 | "disableOptimisticBPs": true, 30 | "windows": { 31 | "program": "${workspaceFolder}/node_modules/jest/bin/jest", 32 | } 33 | } 34 | ] 35 | } -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019 Michael Arnaldi. 2 | 3 | Copyright (c) 2020 Matechs Garage Ltd. 4 | 5 | Copyright (c) 2020 Matechs Holdings Ltd. 6 | 7 | Copyright (c) 2020 The Contributors. 8 | 9 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 14 | -------------------------------------------------------------------------------- /examples/basic-auth.ts: -------------------------------------------------------------------------------- 1 | import { pipe } from "@effect-ts/core" 2 | import * as T from "@effect-ts/core/Effect" 3 | import * as N from "@effect-ts/node/Runtime" 4 | 5 | import FastifyBasicAuth from "@fastify/basic-auth" 6 | 7 | import { FastifyLive, listen, get, server, after } from "../packages/fastify/src" 8 | 9 | import { FastifyReply, FastifyRequest } from "fastify" 10 | 11 | const handler = (_request: FastifyRequest, reply: FastifyReply) => 12 | T.gen(function* (_) { 13 | yield* _( 14 | T.succeedWith(() => { 15 | reply.send("OK") 16 | }) 17 | ) 18 | }) 19 | 20 | async function validate(username: string, password: string) { 21 | if (username !== "admin" || password !== "admin") { 22 | return new Error("Not an admin!") 23 | } 24 | } 25 | 26 | pipe( 27 | T.gen(function* (_) { 28 | const fastify = yield* _(server) 29 | 30 | fastify.register(FastifyBasicAuth, { validate, authenticate: true }) 31 | 32 | yield* _(after()) 33 | yield* _(get("/", { onRequest: fastify.basicAuth }, handler)) 34 | 35 | yield* _(listen(3000, "localhost")) 36 | console.log("listening to localhost:3000!") 37 | yield* _(T.never) 38 | }), 39 | T.provideSomeLayer(FastifyLive), 40 | N.runMain 41 | ) 42 | -------------------------------------------------------------------------------- /packages/fastify/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@tcmlabs/effect-ts-fastify", 3 | "version": "0.0.1", 4 | "license": "MIT", 5 | "repository": "https://github.com/@tcmlabs/effect-ts-fastify.git", 6 | "private": false, 7 | "scripts": { 8 | "clean": "rimraf build tsbuildinfo", 9 | "build:lib": "ttsc --build tsconfig.build.json && build-utils package", 10 | "build:es6": "ttsc --build tsconfig.es6.json && babel build/esm/ -d build/esm/ -s --plugins annotate-pure-calls && build-utils package-esm", 11 | "build:post-process": "build-utils rewrite-source-maps && build-utils rewrite-locals && yarn prettier:lib", 12 | "build": "yarn run build:lib && yarn run build:es6 && yarn run build:post-process", 13 | "lint": "eslint . --ext .ts,.tsx", 14 | "autofix": "yarn prettier && yarn lint --fix && yarn prettier", 15 | "prettier": "prettier --write \"./{src,test,demo}/**/*.ts\"", 16 | "prettier:lib": "prettier --write \"./build/**/*.js\"", 17 | "tc": "tsc --noEmit", 18 | "demo": "ts-node --compiler ttypescript --project tsconfig.build.json" 19 | }, 20 | "main": "build/index.js", 21 | "types": "build/index.d.ts", 22 | "files": ["/build"], 23 | "publishConfig": { 24 | "access": "public", 25 | "directory": "build" 26 | }, 27 | "sideEffects": false, 28 | "config": { 29 | "modules": [] 30 | }, 31 | "peerDependencies": { 32 | "@effect-ts/core": "^0.60.2", 33 | "fastify": ">= 4.0.2" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "typescript.tsdk": "node_modules/typescript/lib", 3 | "editor.formatOnSave": true, 4 | "eslint.format.enable": true, 5 | "[javascript]": { 6 | "editor.defaultFormatter": "dbaeumer.vscode-eslint" 7 | }, 8 | "[javascriptreact]": { 9 | "editor.defaultFormatter": "dbaeumer.vscode-eslint" 10 | }, 11 | "[typescript]": { 12 | "editor.defaultFormatter": "dbaeumer.vscode-eslint" 13 | }, 14 | "[typescriptreact]": { 15 | "editor.defaultFormatter": "dbaeumer.vscode-eslint" 16 | }, 17 | "prettier.disableLanguages": [ 18 | "javascript", 19 | "javascriptreact", 20 | "typescript", 21 | "typescriptreact" 22 | ], 23 | "eslint.validate": [ 24 | "markdown", 25 | "javascript", 26 | "typescript" 27 | ], 28 | "editor.codeActionsOnSave": { 29 | "source.fixAll.eslint": true 30 | }, 31 | "editor.quickSuggestions": { 32 | "other": true, 33 | "comments": false, 34 | "strings": false 35 | }, 36 | "editor.acceptSuggestionOnCommitCharacter": true, 37 | "editor.acceptSuggestionOnEnter": "on", 38 | "editor.quickSuggestionsDelay": 10, 39 | "editor.suggestOnTriggerCharacters": true, 40 | "editor.tabCompletion": "off", 41 | "editor.suggest.localityBonus": true, 42 | "editor.suggestSelection": "recentlyUsed", 43 | "editor.wordBasedSuggestions": true, 44 | "editor.parameterHints.enabled": true, 45 | "files.watcherExclude": { 46 | "**/target": true 47 | }, 48 | "files.insertFinalNewline": true 49 | } 50 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-undef */ 2 | module.exports = { 3 | ignorePatterns: ["build", "dist"], 4 | parser: "@typescript-eslint/parser", 5 | parserOptions: { 6 | ecmaVersion: 2018, 7 | sourceType: "module" 8 | }, 9 | settings: { 10 | "import/parsers": { 11 | "@typescript-eslint/parser": [".ts", ".tsx"] 12 | }, 13 | "import/resolver": { 14 | typescript: { 15 | alwaysTryTypes: true 16 | } 17 | } 18 | }, 19 | extends: [ 20 | "eslint:recommended", 21 | "plugin:@typescript-eslint/eslint-recommended", 22 | "plugin:@typescript-eslint/recommended", 23 | "plugin:prettier/recommended" 24 | ], 25 | plugins: ["import", "sort-destructure-keys", "simple-import-sort", "codegen"], 26 | rules: { 27 | // codegen 28 | "codegen/codegen": "error", 29 | 30 | // eslint built-in rules, sorted alphabetically 31 | "no-fallthrough": "off", 32 | "no-irregular-whitespace": "off", 33 | "object-shorthand": "error", 34 | "prefer-destructuring": "off", 35 | "sort-imports": "off", 36 | 37 | // all other rules, sorted alphabetically 38 | "@typescript-eslint/ban-ts-comment": "off", 39 | "@typescript-eslint/ban-types": "off", 40 | "@typescript-eslint/camelcase": "off", 41 | "@typescript-eslint/consistent-type-imports": "error", 42 | "@typescript-eslint/explicit-function-return-type": "off", 43 | "@typescript-eslint/explicit-module-boundary-types": "off", 44 | "@typescript-eslint/interface-name-prefix": "off", 45 | "@typescript-eslint/no-empty-interface": "off", 46 | "@typescript-eslint/no-explicit-any": "off", 47 | "@typescript-eslint/no-unused-vars": "off", 48 | "@typescript-eslint/no-use-before-define": "off", 49 | "import/first": "error", 50 | "import/newline-after-import": "error", 51 | "import/no-duplicates": "error", 52 | "import/no-unresolved": "error", 53 | "import/order": "off", 54 | "simple-import-sort/imports": "error", 55 | "sort-destructure-keys/sort-destructure-keys": "error", 56 | "@typescript-eslint/no-empty-function": "off" 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /packages/fastify/test/index.test.ts: -------------------------------------------------------------------------------- 1 | import { pipe } from "@effect-ts/core" 2 | import * as T from "@effect-ts/core/Effect" 3 | import * as L from "@effect-ts/core/Effect/Layer" 4 | import { tag } from "@effect-ts/core/Has" 5 | import type { FastifyReply, FastifyRequest } from "fastify" 6 | 7 | import * as Fastify from "../src" 8 | 9 | describe("fastify", () => { 10 | test("Should infer handler environment", async () => { 11 | interface MessageService { 12 | _tag: "@demo/MessageService" 13 | makeMessage: T.UIO 14 | } 15 | 16 | const MessageService = tag() 17 | 18 | const LiveMessageService = L.fromEffect(MessageService)( 19 | T.succeedWith(() => ({ 20 | _tag: "@demo/MessageService", 21 | makeMessage: T.succeedWith(() => "OK") 22 | })) 23 | ) 24 | 25 | const handler = (_request: FastifyRequest, reply: FastifyReply) => 26 | T.gen(function* (_) { 27 | const messageService = yield* _(MessageService) 28 | const message = yield* _(messageService.makeMessage) 29 | reply.send(message) 30 | }) 31 | 32 | const response = await pipe( 33 | T.gen(function* (_) { 34 | yield* _(Fastify.get("/", handler)) 35 | return yield* _(Fastify.inject({ method: "GET", url: "/" })) 36 | }), 37 | T.provideSomeLayer(Fastify.FastifyLive), 38 | T.provideSomeLayer(LiveMessageService), 39 | T.runPromise 40 | ) 41 | expect(response.statusCode).toEqual(200) 42 | expect(response.body).toEqual("OK") 43 | }) 44 | 45 | test("Should listen", async () => { 46 | const host = "127.0.0.1" 47 | const port = 3115 48 | 49 | const response = await pipe( 50 | T.gen(function* (_) { 51 | yield* _( 52 | Fastify.get("/", (_request, reply) => 53 | T.gen(function* (_) { 54 | return yield* _( 55 | T.succeedWith(() => { 56 | reply.send("OK") 57 | }) 58 | ) 59 | }) 60 | ) 61 | ) 62 | 63 | yield* _(Fastify.listen(port, host)) 64 | const response = yield* _( 65 | T.tryPromise(() => fetch(`http://${host}:${port}/`).then((x) => x.text())) 66 | ) 67 | yield* _(Fastify.close()) 68 | 69 | return response 70 | }), 71 | T.provideSomeLayer(Fastify.FastifyLive), 72 | T.runPromise 73 | ) 74 | 75 | expect(response).toEqual("OK") 76 | }) 77 | }) 78 | -------------------------------------------------------------------------------- /.vscode/morphic.code-snippets: -------------------------------------------------------------------------------- 1 | { 2 | "Morphic opaque": { 3 | "prefix": "moo", 4 | "body": [ 5 | "export interface $1 extends MO.AType {}", 6 | "export interface $1E extends MO.EType {}", 7 | "export const $1 = MO.opaque<$1E, $1>()($1_)", 8 | "" 9 | ], 10 | "description": "Defines a Morphic opaque signature" 11 | }, 12 | "Morphic": { 13 | "prefix": "mo", 14 | "body": ["const $1_ = MO.make(F => F.$0 )", ""], 15 | "description": "Defines a Morphic interface" 16 | }, 17 | "Import Morphic": { 18 | "prefix": "+MO", 19 | "body": ["import * as MO from \"@effect-ts/morphic\""], 20 | "description": "Imports Morphic as MO" 21 | }, 22 | "Morphic Newtype": { 23 | "prefix": "mont", 24 | "body": [ 25 | "export interface $1 extends NT.Newtype<\"$1\", $2> {}", 26 | "", 27 | "export const $1Iso = Iso.newtype<$1>()", 28 | "", 29 | "export const $1 = MO.make((F) => F.newtypeIso($1Iso, F.$3()))", 30 | "" 31 | ], 32 | "description": "Defines a Morphic newtype via iso" 33 | }, 34 | "Morphic Newtype Prism": { 35 | "prefix": "mont-pri", 36 | "body": [ 37 | "export interface $1 extends NT.Newtype<\"$1\", $2> {}", 38 | "", 39 | "export const $1Prism = Prism.newtype<$1>((_) => $3)", 40 | "export const $1Iso = Iso.newtype<$1>()", 41 | "", 42 | "export const $1 = MO.make((F) => F.newtypePrism($1Prism, F.$4()))", 43 | "" 44 | ], 45 | "description": "Defines a Morphic newtype via prism" 46 | }, 47 | "Morphic interface": { 48 | "prefix": "moi", 49 | "body": [ 50 | "const $1_ = MO.make(F => F.interface({ $0 }, { name: \"$1\" }))", 51 | "", 52 | "export interface $1 extends MO.AType {}", 53 | "export interface $1E extends MO.EType {}", 54 | "export const $1 = MO.opaque<$1E, $1>()($1_)", 55 | "" 56 | ], 57 | "description": "Defines a Morphic interface" 58 | }, 59 | "Morphic intersection": { 60 | "prefix": "mo&", 61 | "body": [ 62 | "const $1_ = MO.make(F => F.intersection($0)({ name: \"$1\" }))", 63 | "", 64 | "export interface $1 extends MO.AType {}", 65 | "export interface $1E extends MO.EType {}", 66 | "export const $1 = MO.opaque<$1E, $1>()($1_)", 67 | "" 68 | ], 69 | "description": "Defines a Morphic intersection" 70 | }, 71 | "Morphic opaque for tagged unions": { 72 | "prefix": "mo|", 73 | "body": [ 74 | "export const $1 = MO.makeADT('$2')({ $3 })", 75 | "export type $1 = MO.AType", 76 | "" 77 | ], 78 | "description": "Defines a Morphic tagged union" 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "effect-ts", 3 | "private": true, 4 | "repository": "https://github.com/tcmlabs/effect-ts-fastify.git", 5 | "workspaces": [ 6 | "packages/*", 7 | "incubation/*" 8 | ], 9 | "scripts": { 10 | "prepublishOnly": "yarn prettier --write \"./packages*/*/build/**/*.ts\"", 11 | "ci": "yarn run clean && yarn run build && yarn run tc && yarn run lint && yarn run test", 12 | "clean": "ultra -r clean", 13 | "build": "ultra -r build", 14 | "test": "jest --detectOpenHandles", 15 | "autofix": "ultra -r autofix", 16 | "lint": "ultra -r lint", 17 | "tc": "ultra -r tc", 18 | "postinstall": "husky install", 19 | "version:alpha": "lerna version prerelease --conventional-commits --preid=alpha", 20 | "version:preminor": "lerna version preminor --conventional-commits", 21 | "version:minor": "lerna version minor --conventional-commits", 22 | "version:prepatch": "lerna version prepatch --conventional-commits", 23 | "version:patch": "lerna version patch --conventional-commits", 24 | "version:graduate": "lerna version --conventional-commits --conventional-graduate", 25 | "commit": "cz" 26 | }, 27 | "config": { 28 | "commitizen": { 29 | "path": "cz-conventional-changelog" 30 | } 31 | }, 32 | "devDependencies": { 33 | "@babel/cli": "^7.18.6", 34 | "@babel/core": "^7.18.6", 35 | "@effect-ts/build-utils": "^0.27.0", 36 | "@effect-ts/core": ">= 0.60.2", 37 | "@effect-ts/node": "^0.39.0", 38 | "@effect-ts/tracing-plugin": "^0.14.19", 39 | "@fastify/basic-auth": "~4.0.0", 40 | "@types/cpx": "^1.5.2", 41 | "@types/jest": "^28.1.4", 42 | "@types/node": "^18.0.3", 43 | "@typescript-eslint/eslint-plugin": "^5.29.0", 44 | "@typescript-eslint/parser": "^5.29.0", 45 | "babel-plugin-annotate-pure-calls": "^0.4.0", 46 | "chalk": "^4.1.2", 47 | "cpx": "^1.5.0", 48 | "cz-conventional-changelog": "^3.3.0", 49 | "eslint": "^8.18.0", 50 | "eslint-config-prettier": "^8.3.0", 51 | "eslint-import-resolver-typescript": "^3.4.2", 52 | "eslint-plugin-codegen": "^0.16.1", 53 | "eslint-plugin-import": "^2.26.0", 54 | "eslint-plugin-jest": "^26.5.3", 55 | "eslint-plugin-prettier": "^4.0.0", 56 | "eslint-plugin-simple-import-sort": "^7.0.0", 57 | "eslint-plugin-sort-destructure-keys": "^1.4.0", 58 | "fast-check": "^3.1.1", 59 | "fast-equals": "^4.0.1", 60 | "fast-safe-stringify": "^2.1.1", 61 | "fastify": ">= 4.0.2", 62 | "husky": "8.0.1", 63 | "isomorphic-fetch": "^3.0.0", 64 | "jest": "^28.1.2", 65 | "lerna": "^5.4.3", 66 | "prettier": "^2.7.1", 67 | "ts-jest": "^28.0.5", 68 | "ts-node": "^10.8.2", 69 | "ttypescript": "^1.5.13", 70 | "typescript": "4.7.4", 71 | "ultra-runner": "^3.10.5", 72 | "yarn-deduplicate": "^5.0.0" 73 | }, 74 | "dependencies": {} 75 | } 76 | -------------------------------------------------------------------------------- /.vscode/common-imports.code-snippets: -------------------------------------------------------------------------------- 1 | { 2 | "Effect": { 3 | "prefix": "+T", 4 | "body": ["import * as T from \"@effect-ts/core/Effect\""], 5 | "description": "Effect as T" 6 | }, 7 | "Managed": { 8 | "prefix": "+M", 9 | "body": ["import * as M from \"@effect-ts/core/Effect/Managed\""], 10 | "description": "Managed as M" 11 | }, 12 | "Layer": { 13 | "prefix": "+L", 14 | "body": ["import * as L from \"@effect-ts/core/Effect/Layer\""], 15 | "description": "Layer as L" 16 | }, 17 | "Ref": { 18 | "prefix": "+Ref", 19 | "body": ["import * as Ref from \"@effect-ts/core/Effect/Ref\""], 20 | "description": "Ref" 21 | }, 22 | "RefM": { 23 | "prefix": "+RefM", 24 | "body": ["import * as RefM from \"@effect-ts/core/Effect/RefM\""], 25 | "description": "RefM" 26 | }, 27 | "Map": { 28 | "prefix": "+Map", 29 | "body": ["import * as Map from \"@effect-ts/core/Classic/Map\""], 30 | "description": "Map" 31 | }, 32 | "Array": { 33 | "prefix": "+A", 34 | "body": ["import * as A from \"@effect-ts/core/Classic/Array\""], 35 | "description": "Array as A" 36 | }, 37 | "Record": { 38 | "prefix": "+R", 39 | "body": ["import * as R from \"@effect-ts/core/Classic/Record\""], 40 | "description": "Record as R" 41 | }, 42 | "NonEmptyArray": { 43 | "prefix": "+NA", 44 | "body": ["import * as NA from \"@effect-ts/core/Classic/NonEmptyArray\""], 45 | "description": "NonEmptyArray as NA" 46 | }, 47 | "Prelude": { 48 | "prefix": "+P", 49 | "body": ["import * as P from \"@effect-ts/core/Prelude\""], 50 | "description": "Prelude as NA" 51 | }, 52 | "DSL": { 53 | "prefix": "+DSL", 54 | "body": ["import * as DSL from \"@effect-ts/core/Prelude/DSL\""], 55 | "description": "Prelude DSL as DSL" 56 | }, 57 | "Queue": { 58 | "prefix": "+Q", 59 | "body": ["import * as Q from \"@effect-ts/core/Effect/Queue\""], 60 | "description": "Queue as Q" 61 | }, 62 | "Semaphore": { 63 | "prefix": "+Q", 64 | "body": ["import * as Sem from \"@effect-ts/core/Effect/Semaphore\""], 65 | "description": "Semaphore as Sem" 66 | }, 67 | "Stream": { 68 | "prefix": "+S", 69 | "body": ["import * as S from \"@effect-ts/core/Effect/Stream\""], 70 | "description": "Semaphore as S" 71 | }, 72 | "Async": { 73 | "prefix": "+As", 74 | "body": ["import * as As from \"@effect-ts/core/Async\""], 75 | "description": "Async as As" 76 | }, 77 | "Sync": { 78 | "prefix": "+Sy", 79 | "body": ["import * as Sy from \"@effect-ts/core/Sync\""], 80 | "description": "Sync as Sy" 81 | }, 82 | "Either": { 83 | "prefix": "+E", 84 | "body": ["import * as E from \"@effect-ts/core/Classic/Either\""], 85 | "description": "Either as E" 86 | }, 87 | "Exit": { 88 | "prefix": "+Ex", 89 | "body": ["import * as Ex from \"@effect-ts/core/Effect/Exit\""], 90 | "description": "Exit as Ex" 91 | }, 92 | "Cause": { 93 | "prefix": "+C", 94 | "body": ["import * as C from \"@effect-ts/core/Effect/Cause\""], 95 | "description": "Cause as C" 96 | }, 97 | "Option": { 98 | "prefix": "+O", 99 | "body": ["import * as O from \"@effect-ts/core/Classic/Option\""], 100 | "description": "Option as O" 101 | }, 102 | "Fiber": { 103 | "prefix": "+F", 104 | "body": ["import * as F from \"@effect-ts/core/Effect/Fiber\""], 105 | "description": "Fiber as F" 106 | }, 107 | "Iso": { 108 | "prefix": "+Iso", 109 | "body": ["import * as Iso from \"@effect-ts/monocle/Iso\""], 110 | "description": "Iso as Iso" 111 | }, 112 | "Prism": { 113 | "prefix": "+Prism", 114 | "body": ["import * as Prism from \"@effect-ts/monocle/Prism\""], 115 | "description": "Prism as Prism" 116 | }, 117 | "Lens": { 118 | "prefix": "+Lens", 119 | "body": ["import * as Lens from \"@effect-ts/monocle/Lens\""], 120 | "description": "Lens as Lens" 121 | }, 122 | "Optional": { 123 | "prefix": "+Opt", 124 | "body": ["import * as Op from \"@effect-ts/monocle/Optional\""], 125 | "description": "Optional as Op" 126 | }, 127 | "Traversal": { 128 | "prefix": "+Tr", 129 | "body": ["import * as Tr from \"@effect-ts/monocle/Traversal\""], 130 | "description": "Traversal as Tr" 131 | }, 132 | "Newtype": { 133 | "prefix": "+NT", 134 | "body": ["import * as NT from \"@effect-ts/core/Newtype\""], 135 | "description": "Newtype as NT" 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /packages/fastify/src/index.ts: -------------------------------------------------------------------------------- 1 | import { pipe } from "@effect-ts/core" 2 | import * as T from "@effect-ts/core/Effect" 3 | import * as L from "@effect-ts/core/Effect/Layer" 4 | import type { Has } from "@effect-ts/core/Has" 5 | import { tag } from "@effect-ts/core/Has" 6 | import type { _A, _R } from "@effect-ts/core/Utils" 7 | import { Tagged } from "@effect-ts/system/Case" 8 | import type { 9 | ContextConfigDefault, 10 | FastifyLoggerInstance, 11 | FastifyReply, 12 | FastifyRequest, 13 | FastifySchema, 14 | FastifyTypeProvider, 15 | HTTPMethods, 16 | InjectOptions, 17 | LightMyRequestResponse, 18 | RawReplyDefaultExpression, 19 | RawRequestDefaultExpression, 20 | RawServerBase, 21 | RawServerDefault, 22 | RouteShorthandOptions 23 | } from "fastify" 24 | import fastify from "fastify" 25 | import type { RouteGenericInterface } from "fastify/types/route" 26 | import type { 27 | FastifyRequestType, 28 | FastifyTypeProviderDefault, 29 | ResolveFastifyReplyReturnType, 30 | ResolveFastifyRequestType 31 | } from "fastify/types/type-provider" 32 | 33 | export type EffectHandler< 34 | R, 35 | RawServer extends RawServerBase = RawServerDefault, 36 | RawRequest extends RawRequestDefaultExpression = RawRequestDefaultExpression, 37 | RawReply extends RawReplyDefaultExpression = RawReplyDefaultExpression, 38 | RouteGeneric extends RouteGenericInterface = RouteGenericInterface, 39 | ContextConfig = ContextConfigDefault, 40 | SchemaCompiler extends FastifySchema = FastifySchema, 41 | TypeProvider extends FastifyTypeProvider = FastifyTypeProviderDefault, 42 | RequestType extends FastifyRequestType = ResolveFastifyRequestType< 43 | TypeProvider, 44 | SchemaCompiler, 45 | RouteGeneric 46 | >, 47 | Logger extends FastifyLoggerInstance = FastifyLoggerInstance 48 | > = ( 49 | request: FastifyRequest< 50 | RouteGeneric, 51 | RawServer, 52 | RawRequest, 53 | SchemaCompiler, 54 | TypeProvider, 55 | ContextConfig, 56 | RequestType, 57 | Logger 58 | >, 59 | reply: FastifyReply< 60 | RawServer, 61 | RawRequest, 62 | RawReply, 63 | RouteGeneric, 64 | ContextConfig, 65 | SchemaCompiler, 66 | TypeProvider 67 | > 68 | ) => T.Effect< 69 | Has & R, 70 | never, 71 | ResolveFastifyReplyReturnType 72 | > 73 | 74 | function runHandler>( 75 | handler: Handler 76 | ) { 77 | return pipe( 78 | server, 79 | T.chain((server) => 80 | T.map_( 81 | T.runtime< 82 | _R< 83 | [Handler] extends [EffectHandler] 84 | ? T.RIO 85 | : never 86 | > 87 | >(), 88 | (r) => { 89 | return (request: FastifyRequest, reply: FastifyReply) => 90 | r.runPromise(handler.call(server, request, reply)) 91 | } 92 | ) 93 | ) 94 | ) 95 | } 96 | 97 | export class FastifyListenError extends Tagged("FastifyListenError") {} 98 | 99 | export class FastifyInjectError extends Tagged("FastofyInjectError")<{ 100 | readonly error: Error | null 101 | }> {} 102 | 103 | export class FastifyPluginError extends Tagged("FastifyPluginError")<{ 104 | readonly error: Error | null 105 | }> {} 106 | 107 | const FastifySymbol = Symbol.for("@tcmlabs/effect-ts-fastify") 108 | 109 | export const makeFastify = T.succeedWith(() => { 110 | const server = fastify() 111 | 112 | const listen = (port: number | string, address: string) => 113 | T.effectAsync((resume) => { 114 | server.listen(port, address, (error) => { 115 | if (error) { 116 | resume(T.fail(new FastifyListenError(error))) 117 | } else { 118 | resume(T.unit) 119 | } 120 | }) 121 | }) 122 | 123 | const inject = (opts: InjectOptions | string) => 124 | T.effectAsync((resume) => { 125 | server.inject(opts, function (error: Error, response: LightMyRequestResponse) { 126 | if (error) { 127 | resume(T.fail(new FastifyInjectError({ error }))) 128 | } else { 129 | resume(T.succeed(response)) 130 | } 131 | }) 132 | }) 133 | 134 | const close = () => 135 | T.effectAsync, never, void>((resume) => { 136 | server.close(() => resume(T.unit)) 137 | }) 138 | 139 | const after = () => 140 | T.effectAsync((cb) => { 141 | server.after().then( 142 | () => cb(T.unit), 143 | (error) => cb(T.fail(new FastifyPluginError({ error }))) 144 | ) 145 | }) 146 | 147 | return { 148 | _tag: FastifySymbol, 149 | instance: server, 150 | server, 151 | listen, 152 | close, 153 | inject, 154 | after 155 | } 156 | }) 157 | export const Fastify = tag(FastifySymbol) 158 | export interface Fastify extends _A {} 159 | export const FastifyLive = L.fromEffect(Fastify)(makeFastify) 160 | 161 | export const { after, close, inject, listen, server } = T.deriveLifted(Fastify)( 162 | ["listen", "close", "inject", "after"], 163 | [], 164 | ["server"] 165 | ) 166 | 167 | const match = 168 | (method: HTTPMethods) => 169 | >( 170 | url: string, 171 | opts: RouteShorthandOptions | Handler, 172 | handler?: Handler 173 | ): T.RIO< 174 | Has & 175 | _R< 176 | [Handler] extends [EffectHandler] 177 | ? T.RIO 178 | : never 179 | >, 180 | void 181 | > => 182 | T.gen(function* (_) { 183 | const _server = yield* _(server) 184 | const _handler = (handler ? handler : opts) as any 185 | const _opts = (handler ? opts : {}) as any 186 | const rawHandler = yield* _(runHandler(_handler)) 187 | _server.route({ ..._opts, ...{ method, url, handler: rawHandler } }) 188 | }) 189 | 190 | export const get = match("GET") 191 | export const post = match("POST") 192 | const delete_ = match("DELETE") 193 | export { delete_ as delete } 194 | export const put = match("PUT") 195 | export const patch = match("PATCH") 196 | export const options = match("OPTIONS") 197 | export const head = match("HEAD") 198 | --------------------------------------------------------------------------------