├── .changeset └── config.json ├── .github ├── actions │ └── setup │ │ └── action.yml └── workflows │ ├── ci.yml │ └── release.yml ├── .gitignore ├── .madgerc ├── .prettierignore ├── .prettierrc ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── RELEASE.md ├── docker-compose.yml ├── eslint.config.mjs ├── package.json ├── packages ├── effect-mongodb │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── dtslint │ │ ├── AggregationCursor.ts │ │ ├── Collection.ts │ │ ├── Db.ts │ │ ├── DocumentAggregationCursor.ts │ │ ├── DocumentCollection.ts │ │ ├── DocumentFindCursor.ts │ │ ├── FindCursor.ts │ │ ├── ListCollectionsCursor.ts │ │ ├── MongoClient.ts │ │ └── tsconfig.json │ ├── examples │ │ ├── README.md │ │ ├── aggregate.ts │ │ ├── copy-between-dbs.ts │ │ ├── elaborate-stream-with-partitioned-errors.ts │ │ ├── find-with-cursor-builder.ts │ │ ├── find-without-cursor-builder.ts │ │ └── project.ts │ ├── package.json │ ├── src │ │ ├── AggregationCursor.ts │ │ ├── Collection.ts │ │ ├── Db.ts │ │ ├── DocumentAggregationCursor.ts │ │ ├── DocumentCollection.ts │ │ ├── DocumentFindCursor.ts │ │ ├── FindCursor.ts │ │ ├── ListCollectionsCursor.ts │ │ ├── MongoClient.ts │ │ ├── MongoError.ts │ │ ├── index.ts │ │ └── internal │ │ │ ├── filter.ts │ │ │ ├── modify-result.ts │ │ │ ├── mongo-error.ts │ │ │ ├── schema.ts │ │ │ └── version.ts │ ├── test │ │ ├── Collection.test.ts │ │ ├── DocumentCollection.test.ts │ │ ├── DocumentFindCursor.test.ts │ │ ├── FindCursor.test.ts │ │ ├── MongoClient.test.ts │ │ ├── globalSetup │ │ │ └── mongodb.ts │ │ ├── support │ │ │ └── describe-mongo.ts │ │ └── testcontainer.test.ts │ ├── tsconfig.build.json │ ├── tsconfig.examples.json │ ├── tsconfig.json │ ├── tsconfig.src.json │ ├── tsconfig.test.json │ └── vitest.config.ts └── services │ ├── CHANGELOG.md │ ├── LICENSE │ ├── README.md │ ├── dtslint │ ├── DbInstance.ts │ ├── DbService.ts │ ├── MongoClientService.ts │ └── tsconfig.json │ ├── examples │ ├── db-instance-legacy-mongo-client.ts │ ├── db-instance-multiple.ts │ ├── db-instance.ts │ ├── layers.ts │ └── multiple-mongodb-instances.ts │ ├── package.json │ ├── src │ ├── DbInstance.ts │ ├── DbService.ts │ ├── MongoClientService.ts │ └── index.ts │ ├── test │ ├── globalSetup │ │ └── mongodb.ts │ ├── support │ │ └── describe-mongo.ts │ └── testcontainer.test.ts │ ├── tsconfig.build.json │ ├── tsconfig.examples.json │ ├── tsconfig.json │ ├── tsconfig.src.json │ ├── tsconfig.test.json │ └── vitest.config.ts ├── patches └── babel-plugin-annotate-pure-calls@0.4.0.patch ├── pnpm-lock.yaml ├── pnpm-workspace.yaml ├── scripts ├── circular.mjs ├── clean.mjs ├── docs.mjs ├── version.mjs └── version.template.txt ├── tsconfig.base.json ├── tsconfig.build.json ├── tsconfig.json ├── vitest.shared.ts └── vitest.workspace.ts /.changeset/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://unpkg.com/@changesets/config@1.6.4/schema.json", 3 | "changelog": ["@changesets/changelog-github", { "repo": "doubleloop-io/effect-mongodb" }], 4 | "commit": false, 5 | "linked": [], 6 | "access": "restricted", 7 | "baseBranch": "main", 8 | "updateInternalDependencies": "patch", 9 | "ignore": [], 10 | "snapshot": { 11 | "useCalculatedVersion": false, 12 | "prereleaseTemplate": "{tag}-{commit}" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /.github/actions/setup/action.yml: -------------------------------------------------------------------------------- 1 | name: Setup 2 | description: Perform standard setup and install dependencies using pnpm. 3 | inputs: 4 | node-version: 5 | description: The version of Node.js to install 6 | required: true 7 | default: 20.14.0 8 | 9 | runs: 10 | using: composite 11 | steps: 12 | - name: Install pnpm 13 | uses: pnpm/action-setup@v3 14 | - name: Install node 15 | uses: actions/setup-node@v4 16 | with: 17 | cache: pnpm 18 | node-version: ${{ inputs.node-version }} 19 | - name: Install dependencies 20 | shell: bash 21 | run: pnpm install 22 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: 7 | - main 8 | 9 | concurrency: 10 | group: ${{ github.workflow }}-${{ github.ref }} 11 | cancel-in-progress: true 12 | 13 | jobs: 14 | build: 15 | runs-on: ubuntu-latest 16 | timeout-minutes: 10 17 | steps: 18 | - name: Checkout code 19 | uses: actions/checkout@v4 20 | 21 | - name: Install dependencies 22 | uses: ./.github/actions/setup 23 | 24 | - name: Build 25 | run: | 26 | pnpm codegen 27 | 28 | - name: Check source state 29 | run: pnpm check-source-state 30 | 31 | - name: Types 32 | run: | 33 | pnpm check 34 | pnpm dtslint 35 | 36 | - name: Lint 37 | run: | 38 | pnpm circular 39 | pnpm lint 40 | 41 | - name: Test 42 | run: pnpm test 43 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | on: 3 | push: 4 | branches: [main] 5 | 6 | concurrency: 7 | group: ${{ github.workflow }}-${{ github.ref }} 8 | 9 | permissions: {} 10 | 11 | jobs: 12 | release: 13 | if: github.repository_owner == 'doubleloop-io' 14 | name: Release 15 | runs-on: ubuntu-latest 16 | timeout-minutes: 30 17 | permissions: 18 | contents: write 19 | id-token: write 20 | pull-requests: write 21 | steps: 22 | - uses: actions/checkout@v4 23 | - name: Install dependencies 24 | uses: ./.github/actions/setup 25 | - name: Create Release Pull Request or Publish 26 | uses: changesets/action@v1 27 | with: 28 | version: pnpm changeset-version 29 | publish: pnpm changeset-publish 30 | env: 31 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 32 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 33 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | coverage/ 2 | *.tsbuildinfo 3 | node_modules/ 4 | .DS_Store 5 | tmp/ 6 | dist/ 7 | build/ 8 | docs/ 9 | scratchpad/ 10 | .env* 11 | .idea/ 12 | .vscode/ 13 | .envrc -------------------------------------------------------------------------------- /.madgerc: -------------------------------------------------------------------------------- 1 | { 2 | "detectiveOptions": { 3 | "ts": { 4 | "skipTypeImports": true 5 | } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | *.js 2 | *.ts 3 | *.cjs 4 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "trailingComma": "none", 4 | "overrides": [ 5 | { 6 | "files": [ 7 | "*.yaml", 8 | "*.yml", 9 | "*.json" 10 | ], 11 | "options": { 12 | "tabWidth": 2 13 | } 14 | } 15 | ] 16 | } 17 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Welcome, we really appreciate if you're considering to contribute to effect-mongodb! 4 | 5 | - [Initial setup](#initial-setup) 6 | - [Making changes](#making-changes) 7 | - [Tests](#tests) 8 | 9 | ## Initial setup 10 | 11 | Install the following tools to set up your environment: 12 | 13 | - [Node.js](https://nodejs.org/en) 20 or newer 14 | - [pnpm](https://pnpm.io/) 9.4.0 or newer 15 | - [Docker](https://www.docker.com/) for running tests using `@testcontainers/mongodb` 16 | 17 | Then, you can fork the repository and install the dependencies: 18 | 19 | 1. Fork this repository on GitHub 20 | 2. Clone your forked repo: 21 | ```shell 22 | git clone https://github.com//effect-mongodb && cd effect-mongodb 23 | ``` 24 | 3. Install dependencies: 25 | ```shell 26 | pnpm install 27 | ``` 28 | 4. Optionally, verify that everything is working correctly: 29 | ```shell 30 | pnpm check 31 | pnpm test 32 | ``` 33 | 34 | ## Making changes 35 | 36 | Create a new branch for your changes 37 | 38 | ```shell 39 | git checkout -b my-branch 40 | ``` 41 | 42 | Make the changes you propose to the codebase. If your changes impact functionality, please **add corresponding tests** 43 | to validate your updates. 44 | 45 | ### Validate your changes 46 | 47 | Run the following commands to ensure your changes do not introduce any issues: 48 | 49 | - `pnpm codegen` (optional): Re-generate the package entrypoints in case you have changed the structure of a package or 50 | introduced a new module. 51 | - `pnpm check`: Confirm that the code compiles without errors. 52 | - `pnpm test`: Execute all unit tests to ensure your changes haven't broken existing functionality. 53 | - `pnpm circular`: Check for any circular dependencies in imports. 54 | - `pnpm lint`: Ensure the code adheres to our coding standards. 55 | - If you encounter style issues, use `pnpm lint-fix` to automatically correct some of these. 56 | - `pnpm dtslint`: Run type-level tests. 57 | 58 | ### Document your changes 59 | 60 | Before committing your changes, document them with a changeset. This process helps in tracking modifications and 61 | effectively communicating them to the project team and users: 62 | 63 | ```bash 64 | pnpm changeset 65 | ``` 66 | 67 | During the changeset creation process, you will be prompted to select the appropriate level for your changes: 68 | 69 | - **patch**: Opt for this if you are making small fixes or minor changes that do not affect the library's overall 70 | functionality. 71 | - **minor**: Choose this for new features that enhance functionality but do not disrupt existing features. 72 | - **major**: Select this for any changes that result in backward-incompatible modifications to the library. 73 | 74 | ### Commit your changes 75 | 76 | Once you have documented your changes, commit them to the repository: 77 | 78 | ```bash 79 | git commit -am "Add some feature" 80 | ``` 81 | 82 | #### Linking to issues 83 | 84 | If your commit addresses an open issue, reference the issue number directly in your commit message. This helps to link 85 | your contribution clearly to specific tasks or bug reports. 86 | 87 | For example: 88 | 89 | ```bash 90 | # Reference issue #123 91 | git commit -am "Add some feature (#123)" 92 | 93 | # Close the issue #123 94 | git commit -am "Add some feature (close #123)" 95 | ``` 96 | 97 | ### Push to your fork 98 | 99 | Push the changes to your GitHub fork: 100 | 101 | ```bash 102 | git push origin my-branch 103 | ``` 104 | 105 | ### Create a pull request 106 | 107 | Open a pull request against the main branch on the original repository. 108 | 109 | Please be patient! We will do our best to review your pull request as soon as possible. 110 | 111 | ## Tests 112 | 113 | We use [@testcontainers/mongodb](https://node.testcontainers.org/modules/mongodb/) to run MongoDB in a Docker container 114 | during tests. 115 | 116 | ### Write a test 117 | 118 | In the `test` directory of a package, like [packages/effect-mongodb/test](packages/effect-mongodb/test), create a 119 | new file `.test.ts`. 120 | 121 | Use the `describeMongo` function to automatically setup your test suite with a MongoDB connection. 122 | 123 | ```typescript 124 | import * as Db from "effect-mongodb/Db" 125 | import * as DocumentCollection from "effect-mongodb/DocumentCollection" 126 | import * as Effect from "effect/Effect" 127 | import * as O from "effect/Option" 128 | import { ObjectId } from "mongodb" 129 | import { expect, test } from "vitest" 130 | import { describeMongo } from "./support/describe-mongo.js" 131 | 132 | describeMongo("My tests", (ctx) => { 133 | test("find one", async () => { 134 | const program = Effect.gen(function*() { 135 | const db = yield* ctx.database 136 | const collection = Db.documentCollection(db, "find-one") 137 | 138 | yield* DocumentCollection.insertMany(collection, [{ name: "john" }, { name: "alfred" }]) 139 | 140 | return yield* DocumentCollection.findOne(collection, { name: "john" }) 141 | }) 142 | 143 | const result = await Effect.runPromise(program) 144 | 145 | expect(result).toEqual(O.some({ _id: expect.any(ObjectId), name: "john" })) 146 | }) 147 | }) 148 | ``` 149 | 150 | ### Inspect MongoDB test instance 151 | 152 | 1. Export `EFFECT_MONGODB_DEBUG=true` environment variable 153 | 2. Run the tests (by default in watch mode) and you will see the connection string in the console output 154 | ``` 155 | [EFFECT_MONGODB_DEBUG] MongoDB connection string with direct connection: 'mongodb://localhost:32775' 156 | ``` 157 | 3. Copy the connection string `mongodb://localhost:32775` 158 | 4. Open [MongoDB Compass](https://www.mongodb.com/products/tools/compass) 159 | 5. Click **Add new connection** 160 | 6. Paste the copied connection string in the URI field 161 | 7. Click Advanced Connection Options 162 | 8. Enable **Direct Connection** 163 | 9. Click **Save & Connect** 164 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024-present doubleloop.io 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 | # effect-mongodb 2 | 3 | [![CI status](https://github.com/doubleloop-io/effect-mongodb/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/doubleloop-io/effect-mongodb/actions/workflows/ci.yml) 4 | ![effect-mongodb npm version](https://img.shields.io/npm/v/effect-mongodb?label=effect-mongodb%20npm&link=https%3A%2F%2Fwww.npmjs.com%2Fpackage%2Feffect-mongodb) 5 | 6 | A [MongoDB](https://github.com/mongodb/node-mongodb-native) toolkit for [Effect](https://github.com/Effect-TS/effect/). 7 | 8 | ```typescript 9 | import { Effect, Schema } from "effect" 10 | import { Collection, Db, FindCursor, MongoClient } from "effect-mongodb" 11 | 12 | const Person = Schema.Struct({ 13 | name: Schema.String, 14 | age: Schema.Number, 15 | birthday: Schema.Date 16 | }) 17 | 18 | const program = Effect.gen(function*() { 19 | const client = yield* MongoClient.connectScoped("mongodb://localhost:27017") 20 | const db = MongoClient.db(client, "source-db") 21 | const sourceCollection = Db.collection(db, "source", Person) 22 | const destinationCollection = Db.collection(db, "destination", Person) 23 | 24 | const items = yield* Collection.find(sourceCollection).pipe(FindCursor.toArray) 25 | 26 | yield* Collection.insertMany(destinationCollection, items) 27 | }) 28 | 29 | await program.pipe(Effect.scoped, Effect.runPromise) 30 | ``` 31 | 32 | ## Documentation 33 | 34 | [effect-mongodb](packages/effect-mongodb/README.md) is the **core package** that provides effectful APIs to work with 35 | MongoDB. 36 | 37 | [@effect-mongodb/services](packages/services/README.md) is the package that provides Effect **services** for effect-mongodb. 38 | 39 | ## MongoDB driver compatibility 40 | 41 | We adhere to the [MongoDB driver compatibility](https://www.mongodb.com/docs/drivers/node/current/compatibility/) 42 | guidelines, so minor releases might break compatibility with older MongoDB servers. 43 | 44 | For example, upgrading the Node.js driver from 6.8 to 6.10 will make it incompatible with MongoDB server 3.6. 45 | 46 | ## Contributing 47 | 48 | Take a look at the [CONTRIBUTING.md](CONTRIBUTING.md) guidelines. 49 | 50 | ### Found a Bug? 51 | 52 | If you find a bug in the source code, you can help us 53 | by [submitting an issue](https://github.com/doubleloop-io/effect-mongodb/issues/new) to our GitHub Repository. Even 54 | better, you can submit a Pull Request with a fix. 55 | 56 | ### Missing a Feature? 57 | 58 | You can request a new feature 59 | by [submitting a discussion](https://github.com/doubleloop-io/effect-mongodb/discussions/new/choose) to 60 | our GitHub Repository. 61 | If you would like to implement a new feature, please consider the size of the change and reach out to 62 | better coordinate our efforts and prevent duplication of work. 63 | 64 | ## License 65 | 66 | `effect-mongodb` is made available under the terms of the MIT License. 67 | 68 | See the [LICENSE](LICENSE) file for license details. 69 | -------------------------------------------------------------------------------- /RELEASE.md: -------------------------------------------------------------------------------- 1 | # Release 2 | 3 | This is a guide for maintainers on how to release a new version of the library. 4 | 5 | ## Release a new version 6 | 7 | Merge the Pull Request automatically created on GitHub with the name **Version Packages** by changeset bot. -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | mongo: 3 | image: mongo:8.0.4 4 | ports: 5 | - "27017:27017" 6 | mongo2: 7 | image: mongo:8.0.4 8 | ports: 9 | - "37017:27017" 10 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import { fixupPluginRules } from "@eslint/compat" 2 | import { FlatCompat } from "@eslint/eslintrc" 3 | import js from "@eslint/js" 4 | import tsParser from "@typescript-eslint/parser" 5 | import codegen from "eslint-plugin-codegen" 6 | import deprecation from "eslint-plugin-deprecation" 7 | import _import from "eslint-plugin-import" 8 | import simpleImportSort from "eslint-plugin-simple-import-sort" 9 | import sortDestructureKeys from "eslint-plugin-sort-destructure-keys" 10 | import path from "node:path" 11 | import { fileURLToPath } from "node:url" 12 | 13 | const __filename = fileURLToPath(import.meta.url) 14 | const __dirname = path.dirname(__filename) 15 | const compat = new FlatCompat({ 16 | baseDirectory: __dirname, 17 | recommendedConfig: js.configs.recommended, 18 | allConfig: js.configs.all 19 | }) 20 | 21 | export default [ 22 | { 23 | ignores: ["**/dist", "**/build", "**/docs", "**/*.md"] 24 | }, 25 | ...compat.extends( 26 | "eslint:recommended", 27 | "plugin:@typescript-eslint/eslint-recommended", 28 | "plugin:@typescript-eslint/recommended", 29 | "plugin:@effect/recommended" 30 | ), 31 | { 32 | plugins: { 33 | deprecation, 34 | import: fixupPluginRules(_import), 35 | "sort-destructure-keys": sortDestructureKeys, 36 | "simple-import-sort": simpleImportSort, 37 | codegen 38 | }, 39 | 40 | languageOptions: { 41 | parser: tsParser, 42 | ecmaVersion: 2018, 43 | sourceType: "module" 44 | }, 45 | 46 | settings: { 47 | "import/parsers": { 48 | "@typescript-eslint/parser": [".ts", ".tsx"] 49 | }, 50 | 51 | "import/resolver": { 52 | typescript: { 53 | alwaysTryTypes: true 54 | } 55 | } 56 | }, 57 | 58 | rules: { 59 | "codegen/codegen": "error", 60 | "no-fallthrough": "off", 61 | "no-irregular-whitespace": "off", 62 | "object-shorthand": "error", 63 | "prefer-destructuring": "off", 64 | "sort-imports": "off", 65 | 66 | "no-restricted-syntax": [ 67 | "error", 68 | { 69 | selector: "CallExpression[callee.property.name='push'] > SpreadElement.arguments", 70 | message: "Do not use spread arguments in Array.push" 71 | } 72 | ], 73 | 74 | "no-unused-vars": "off", 75 | "prefer-rest-params": "off", 76 | "prefer-spread": "off", 77 | "import/first": "error", 78 | "import/newline-after-import": "error", 79 | "import/no-duplicates": "error", 80 | "import/no-unresolved": "off", 81 | "import/order": "off", 82 | "simple-import-sort/imports": "off", 83 | "sort-destructure-keys/sort-destructure-keys": "error", 84 | "deprecation/deprecation": "off", 85 | 86 | "@typescript-eslint/array-type": [ 87 | "warn", 88 | { 89 | default: "generic", 90 | readonly: "generic" 91 | } 92 | ], 93 | 94 | "@typescript-eslint/member-delimiter-style": 0, 95 | "@typescript-eslint/no-non-null-assertion": "off", 96 | "@typescript-eslint/ban-types": "off", 97 | "@typescript-eslint/no-explicit-any": "off", 98 | "@typescript-eslint/no-empty-interface": "off", 99 | "@typescript-eslint/consistent-type-imports": "warn", 100 | 101 | "@typescript-eslint/no-unused-vars": [ 102 | "error", 103 | { 104 | argsIgnorePattern: "^_", 105 | varsIgnorePattern: "^_" 106 | } 107 | ], 108 | 109 | "@typescript-eslint/ban-ts-comment": "off", 110 | "@typescript-eslint/camelcase": "off", 111 | "@typescript-eslint/explicit-function-return-type": "off", 112 | "@typescript-eslint/explicit-module-boundary-types": "off", 113 | "@typescript-eslint/interface-name-prefix": "off", 114 | "@typescript-eslint/no-array-constructor": "off", 115 | "@typescript-eslint/no-use-before-define": "off", 116 | "@typescript-eslint/no-namespace": "off", 117 | 118 | "@effect/dprint": [ 119 | "error", 120 | { 121 | config: { 122 | indentWidth: 2, 123 | lineWidth: 120, 124 | semiColons: "asi", 125 | quoteStyle: "alwaysDouble", 126 | trailingCommas: "never", 127 | operatorPosition: "maintain", 128 | "arrowFunction.useParentheses": "force" 129 | } 130 | } 131 | ] 132 | } 133 | }, 134 | { 135 | files: ["packages/*/src/**/*"], 136 | rules: { 137 | "no-console": "error" 138 | } 139 | } 140 | ] 141 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "private": true, 3 | "type": "module", 4 | "packageManager": "pnpm@9.4.0", 5 | "workspaces": [ 6 | "packages/*" 7 | ], 8 | "scripts": { 9 | "clean": "node scripts/clean.mjs", 10 | "codegen": "pnpm --recursive --parallel run codegen", 11 | "build": "tsc -b tsconfig.build.json && pnpm --recursive --parallel run build", 12 | "circular": "node scripts/circular.mjs", 13 | "test": "vitest", 14 | "coverage": "vitest --coverage", 15 | "check": "tsc -b tsconfig.json", 16 | "check-recursive": "pnpm --recursive exec tsc -b tsconfig.json", 17 | "lint": "eslint \"**/{src,test,examples,scripts,dtslint}/**/*.{ts,mjs}\"", 18 | "lint-fix": "pnpm lint --fix", 19 | "docgen": "pnpm --recursive --parallel exec docgen && node scripts/docs.mjs", 20 | "dtslint": "pnpm --recursive --parallel run dtslint", 21 | "dtslint-clean": "dtslint --clean", 22 | "changeset-version": "changeset version && node scripts/version.mjs", 23 | "changeset-publish": "pnpm lint-fix && pnpm build && TEST_DIST= pnpm vitest && changeset publish", 24 | "check-source-state": "git add packages/*/src && git diff-index --cached HEAD --exit-code packages/*/src", 25 | "infra": "docker compose up" 26 | }, 27 | "resolutions": { 28 | "dependency-tree": "^10.0.9", 29 | "detective-amd": "^5.0.2", 30 | "detective-cjs": "^5.0.1", 31 | "detective-es6": "^4.0.1", 32 | "detective-less": "^1.0.2", 33 | "detective-postcss": "^6.1.3", 34 | "detective-sass": "^5.0.3", 35 | "detective-scss": "^4.0.3", 36 | "detective-stylus": "^4.0.0", 37 | "detective-typescript": "^11.1.0", 38 | "@types/node": "^22.5.4" 39 | }, 40 | "devDependencies": { 41 | "@babel/cli": "^7.24.8", 42 | "@babel/core": "^7.25.2", 43 | "@babel/plugin-transform-export-namespace-from": "^7.24.7", 44 | "@babel/plugin-transform-modules-commonjs": "^7.24.8", 45 | "@changesets/changelog-github": "^0.5.1", 46 | "@changesets/cli": "^2.28.1", 47 | "@effect/build-utils": "^0.7.8", 48 | "@effect/docgen": "^0.5.0", 49 | "@effect/dtslint": "^0.1.2", 50 | "@effect/eslint-plugin": "^0.2.0", 51 | "@effect/language-service": "^0.2.0", 52 | "@eslint/compat": "1.1.1", 53 | "@eslint/eslintrc": "3.1.0", 54 | "@eslint/js": "9.9.1", 55 | "@types/node": "^22.5.4", 56 | "@typescript-eslint/eslint-plugin": "^8.13.0", 57 | "@typescript-eslint/parser": "^8.13.0", 58 | "@vitest/coverage-v8": "^2.1.9", 59 | "@vitest/expect": "^2.1.9", 60 | "ast-types": "^0.14.2", 61 | "babel-plugin-annotate-pure-calls": "^0.4.0", 62 | "eslint": "^9.9.1", 63 | "eslint-import-resolver-typescript": "^3.6.3", 64 | "eslint-plugin-codegen": "^0.28.0", 65 | "eslint-plugin-deprecation": "^3.0.0", 66 | "eslint-plugin-import": "^2.30.0", 67 | "eslint-plugin-simple-import-sort": "^12.1.1", 68 | "eslint-plugin-sort-destructure-keys": "^2.0.0", 69 | "fast-check": "^3.21.0", 70 | "glob": "^11.0.0", 71 | "madge": "^8.0.0", 72 | "prettier": "^3.3.3", 73 | "rimraf": "^6.0.1", 74 | "tsx": "^4.17.0", 75 | "typescript": "^5.6.2", 76 | "vite": "^5.4.0", 77 | "vitest": "^2.1.9", 78 | "@testcontainers/mongodb": "^10.10.4" 79 | }, 80 | "pnpm": { 81 | "overrides": { 82 | "vitest": "^2.1.9" 83 | }, 84 | "patchedDependencies": { 85 | "babel-plugin-annotate-pure-calls@0.4.0": "patches/babel-plugin-annotate-pure-calls@0.4.0.patch" 86 | } 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /packages/effect-mongodb/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # effect-mongodb 2 | 3 | ## 0.3.1 4 | 5 | ### Patch Changes 6 | 7 | - [`191c6c6`](https://github.com/doubleloop-io/effect-mongodb/commit/191c6c695c52b2e62f09ef8ba25a208223e258b1) Thanks [@VenomAV](https://github.com/VenomAV)! - Bump patch to work around changeset issue in the previous release 8 | 9 | ## 0.3.0 10 | 11 | ### Minor Changes 12 | 13 | - [`4c9d190`](https://github.com/doubleloop-io/effect-mongodb/commit/4c9d190b1fedcb31c15bbceecbabfa019f234354) Thanks [@VenomAV](https://github.com/VenomAV)! - Export internal Filter type to allow it to be used by clients 14 | 15 | ## 0.2.0 16 | 17 | ### Minor Changes 18 | 19 | - [`ec45cb5`](https://github.com/doubleloop-io/effect-mongodb/commit/ec45cb5e1b8b4d8731a9cc041fa9b1bcc82c82bc) Thanks [@VenomAV](https://github.com/VenomAV)! - Wrap mongodb driver MongoClient type into a TaggedClass 20 | 21 | - [`89b1063`](https://github.com/doubleloop-io/effect-mongodb/commit/89b1063fa0ae52b4dbfccef507f481b43330ce6e) Thanks [@VenomAV](https://github.com/VenomAV)! - Wrap mongodb driver Db type into a TaggedClass 22 | 23 | ### Patch Changes 24 | 25 | - [`7faa2c5`](https://github.com/doubleloop-io/effect-mongodb/commit/7faa2c5732f6297b7515787529bfac58c97d6082) Thanks [@devmatteini](https://github.com/devmatteini)! - Export `MongoClient.connectScoped` options type `MongoClientScopedOptions` 26 | 27 | ## 0.1.4 28 | 29 | ### Patch Changes 30 | 31 | - [`9b50f17`](https://github.com/doubleloop-io/effect-mongodb/commit/9b50f179f2c209ba24b8fbcd4753ce1708522f8f) Thanks [@VenomAV](https://github.com/VenomAV)! - Make cursor and collection classes pipeable 32 | 33 | ## 0.1.3 34 | 35 | ### Patch Changes 36 | 37 | - [`f4750b7`](https://github.com/doubleloop-io/effect-mongodb/commit/f4750b7f43f395d137f3d59f4f6e829553475c77) Thanks [@devmatteini](https://github.com/devmatteini)! - Fix `Collection.updateMany` success return type `UpdateResult` to allow custom defined `_id` types 38 | 39 | - [`b5469c5`](https://github.com/doubleloop-io/effect-mongodb/commit/b5469c5ce92b40483feff0e3541b0acffcf4db9e) Thanks [@devmatteini](https://github.com/devmatteini)! - Fix `Collection.replaceOne` success return type `UpdateResult` to allow custom defined `_id` types 40 | 41 | - [`3f9d487`](https://github.com/doubleloop-io/effect-mongodb/commit/3f9d4877c8dcf1926da175fd6ff5b345d7926591) Thanks [@devmatteini](https://github.com/devmatteini)! - Fix `Collection.insertOne` success return type `InsertOneResult` to allow custom defined `_id` types 42 | 43 | - [`8332e0c`](https://github.com/doubleloop-io/effect-mongodb/commit/8332e0c76faf9f7594364ba73b54a37d73f98c79) Thanks [@devmatteini](https://github.com/devmatteini)! - `Collection/DocumentCollection.dropIndex` return type is the same as mongodb driver 44 | 45 | - [`891b6bf`](https://github.com/doubleloop-io/effect-mongodb/commit/891b6bf32e3483707166656db1d116e75bd51122) Thanks [@devmatteini](https://github.com/devmatteini)! - Swap `Collection.aggregate` pipeline and schema parameter positions 46 | 47 | ```typescript 48 | // Before 49 | Collection.aggregate(pipeline, schema) 50 | 51 | // After 52 | Collection.aggregate(schema, pipeline) 53 | ``` 54 | 55 | - [`4e17b58`](https://github.com/doubleloop-io/effect-mongodb/commit/4e17b584406669236a0e9c2375749685981e70f0) Thanks [@VenomAV](https://github.com/VenomAV)! - Review all errors, providing contextual information and standardizing error messages 56 | 57 | - [`bd026cf`](https://github.com/doubleloop-io/effect-mongodb/commit/bd026cf25fcbbdf3f6ae2fed4f240651626d0b7a) Thanks [@devmatteini](https://github.com/devmatteini)! - Fix `Collection.insertMany` success return type `InsertManyResult` to allow custom defined `_id` types 58 | 59 | ## 0.1.2 60 | 61 | ### Patch Changes 62 | 63 | - [`5de41db`](https://github.com/doubleloop-io/effect-mongodb/commit/5de41dbbacb1fcfd4c38cc3e9c24a992741d94b6) Thanks [@devmatteini](https://github.com/devmatteini)! - Fix errors on `DocumentCollection`: 64 | 65 | - `insertOne` data-first overload when only passing collection and doc 66 | - `insertMany` return type is always `InsertManyResult` 67 | - `rename` now returns a new `DocumentCollection` (like `Collection.rename`) 68 | - `dropIndex` now returns void (like `Collection.dropIndex`) 69 | 70 | ## 0.1.1 71 | 72 | ### Patch Changes 73 | 74 | - [`e167c6f`](https://github.com/doubleloop-io/effect-mongodb/commit/e167c6fe94cda5b9ee04f17496e4dd303a06769d) Thanks [@devmatteini](https://github.com/devmatteini)! - fix: set base tsconfig types to node and remove DOM from lib 75 | 76 | - [`468805d`](https://github.com/doubleloop-io/effect-mongodb/commit/468805d21bc921d7690060e95e4dd447aeca149b) Thanks [@devmatteini](https://github.com/devmatteini)! - Add close to MongoClient 77 | 78 | - [`a0c6e2e`](https://github.com/doubleloop-io/effect-mongodb/commit/a0c6e2e37bb72e96e999c416602ef34b5264e2a9) Thanks [@devmatteini](https://github.com/devmatteini)! - Improve the composability of functions by returning mutable arrays and accepting readonly arrays as input parameters. 79 | 80 | - [`122d816`](https://github.com/doubleloop-io/effect-mongodb/commit/122d816a53c6ea41b254e8ea76d7a8d17a44ce8f) Thanks [@devmatteini](https://github.com/devmatteini)! - Add connectScoped to MongoClient 81 | 82 | ## 0.1.0 83 | 84 | ### Minor Changes 85 | 86 | - [`e52f494`](https://github.com/doubleloop-io/effect-mongodb/commit/e52f4944ccae2dea261138781460b3d40567eb53) Thanks [@devmatteini](https://github.com/devmatteini)! - upgrade mongodb version to 6.9.0 to fully support mongodb server 8 87 | 88 | ## 0.0.5 89 | 90 | ### Patch Changes 91 | 92 | - [`4287f85`](https://github.com/doubleloop-io/effect-mongodb/commit/4287f85efbd7aa91e96d0a382622b4cc46bbe748) Thanks [@devmatteini](https://github.com/devmatteini)! - Remove services related types from Db and MongoClient that are moved to the new package `@effect-mongodb/services` 93 | 94 | - [`00874e9`](https://github.com/doubleloop-io/effect-mongodb/commit/00874e936a7e54925c848749a54df536171587ac) Thanks [@VenomAV](https://github.com/VenomAV)! - Add estimetedDocumentCount and countDocuments in Collection and DocumentCollection 95 | 96 | ## 0.0.4 97 | 98 | ### Patch Changes 99 | 100 | - [`a8530e7`](https://github.com/doubleloop-io/effect-mongodb/commit/a8530e703a9b065f660f31db5cf9ea9dca12bd69) Thanks [@devmatteini](https://github.com/devmatteini)! - Add dropIndex function in Collection and DocumentCollection 101 | 102 | - [`6cc9c61`](https://github.com/doubleloop-io/effect-mongodb/commit/6cc9c6108cab2d4c8ed2555fb603df5791f75f1c) Thanks [@devmatteini](https://github.com/devmatteini)! - Add createIndex function in Collection and DocumentCollection 103 | 104 | - [`16c906a`](https://github.com/doubleloop-io/effect-mongodb/commit/16c906af4ef5afc5b62522c73c9f571176ee5048) Thanks [@devmatteini](https://github.com/devmatteini)! - Add findOneAndReplace function in Collection and DocumentCollection 105 | 106 | - [`6f0a8d4`](https://github.com/doubleloop-io/effect-mongodb/commit/6f0a8d4404a74238fbc901fae5a212c1c7c6b2bc) Thanks [@devmatteini](https://github.com/devmatteini)! - Use Filter type in FindCursor and DocumentFindCursor filter function. 107 | Add optional filter parameter to find functions in Collection and DocumentCollection 108 | 109 | - [`d50d85d`](https://github.com/doubleloop-io/effect-mongodb/commit/d50d85db297b5d6e8e4b7db2f151c64bf9ac3c9e) Thanks [@devmatteini](https://github.com/devmatteini)! - Add drop function in Collection and DocumentCollection 110 | 111 | ## 0.0.3 112 | 113 | ### Patch Changes 114 | 115 | - [`04a7439`](https://github.com/doubleloop-io/effect-mongodb/commit/04a74397723f0f4ae68af8defba49dff8f31fc31) Thanks [@devmatteini](https://github.com/devmatteini)! - - Db.listCollections with ListCollectionsCursor 116 | - Db.dropCollection 117 | - DocumentCollection/Collection.deleteOne 118 | - DocumentCollection/Collection.deleteMany 119 | - DocumentCollection/Collection.updateMant 120 | - DocumentCollection/Collection.replaceOne 121 | - DocumentCollection/Collection.rename 122 | - DocumentCollection/Collection.createIndexes 123 | - DocumentCollection/Collection.aggregate with DocumentAggregationCursor and AggregationCursor 124 | 125 | ## 0.0.2 126 | 127 | ### Patch Changes 128 | 129 | - [`9f05864`](https://github.com/doubleloop-io/effect-mongodb/commit/9f05864b7119728b0a27f144732b08d437f53c95) Thanks [@VenomAV](https://github.com/VenomAV)! - Setup for first release 130 | 131 | - Add some documentation for contributing and releasing 132 | - Basic functionalities in `effect-mongodb` 133 | -------------------------------------------------------------------------------- /packages/effect-mongodb/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024-present doubleloop.io 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 | -------------------------------------------------------------------------------- /packages/effect-mongodb/README.md: -------------------------------------------------------------------------------- 1 | # effect-mongodb 2 | 3 | ![NPM Version](https://img.shields.io/npm/v/effect-mongodb?link=https%3A%2F%2Fwww.npmjs.com%2Fpackage%2Feffect-mongodb) 4 | ![minzipped size](https://badgen.net/bundlephobia/minzip/effect-mongodb) 5 | ![dependency count](https://badgen.net/bundlephobia/dependency-count/effect-mongodb) 6 | ![tree shaking support](https://badgen.net/bundlephobia/tree-shaking/effect-mongodb) 7 | 8 | A [MongoDB](https://github.com/mongodb/node-mongodb-native) toolkit for [Effect](https://github.com/Effect-TS/effect/). 9 | 10 | ## Install 11 | 12 | ```shell 13 | pnpm install effect-mongodb effect mongodb 14 | ``` 15 | 16 | Note that `effect`, and `mongodb` are requested as peer dependencies. 17 | 18 | ## Usage 19 | 20 | Here is a simple example of how to use this package: 21 | 22 | ```typescript 23 | import { Effect, Schema } from "effect" 24 | import { Collection, Db, FindCursor, MongoClient } from "effect-mongodb" 25 | 26 | const Person = Schema.Struct({ 27 | name: Schema.String, 28 | age: Schema.Number, 29 | birthday: Schema.Date 30 | }) 31 | 32 | const program = Effect.gen(function*() { 33 | const client = yield* MongoClient.connectScoped("mongodb://localhost:27017") 34 | const db = MongoClient.db(client, "source-db") 35 | const sourceCollection = Db.collection(db, "source", Person) 36 | const destinationCollection = Db.collection(db, "destination", Person) 37 | 38 | const items = yield* Collection.find(sourceCollection).pipe(FindCursor.toArray) 39 | 40 | yield* Collection.insertMany(destinationCollection, items) 41 | }) 42 | 43 | await program.pipe(Effect.scoped, Effect.runPromise) 44 | ``` 45 | 46 | Find more examples in the [examples](./examples) folder. 47 | 48 | ## Design decisions 49 | 50 | Here is a list of design decisions made and principles followed while developing his package: 51 | 1. **Effect first**: the package is designed to be used with Effect. It is not a general-purpose MongoDB client. 52 | 2. **Coherent API signature**: the same signatures of the underlying MongoDB driver are used as much as possible, with 53 | few exceptions: 54 | 1. `Option` is used instead of `null` or `undefined` values. 55 | 2. Some operation options, which have an impact on the shape of API response, may be replaced with stricter functions 56 | (e.g. `projection` in `find` operation). 57 | 3. **Document-based API**: these API are based on MongoDB's `Document` type, therefore, without any runtime validation 58 | on the shape of the document. 59 | 60 | Signatures are mostly identical to those of the original APIs. 61 | 62 | All modules belonging to this set are prefixed with `Document`, e.g. `DocumentCollection` or `DocumentFindCursor`. 63 | 64 | These API are mainly useful for incremental adoption in existing codebases, and to allow quick prototyping when validation is redundant. 65 | 4. **Schema-based API**: these API are based on Schema, therefore, provides runtime validation. 66 | 67 | Functions may have slightly different signatures than the original APIs, since they enforce stricter types than 68 | the Document-based API. 69 | 70 | All modules belonging to this set don't have any prefix, e.g. `Collection` or `FindCursor`. 71 | 72 | While developing the package, this set of functions is the one we put more effort into. 73 | 5. **Error handling**: we introduced a new error type `MongoError`, derived from 74 | [`TaggedError`](https://effect.website/docs/data-types/data/#taggederror), to wrap the original MongoDB errors. 75 | The current implementation is basic, but it will be extended to provide more detailed information and a finer error 76 | handling. 77 | 78 | ### Cursors 79 | 80 | All cursor modules provide, at least, two functions: 81 | - `toArray`, which returns an `Effect` that resolves to an array of documents. 82 | This kind of function loads all the documents from the cursor and returns them as an array. 83 | - `toStream`, which returns a [`Stream`](https://effect.website/docs/stream/introduction/) of documents. 84 | Instead, this kind of function leverages the async iterable nature of the underlying MongoDB driver's cursor, and 85 | allows to process the documents one by one, without loading them all in memory. 86 | 87 | For document-based modules, these functions can only fail with a `MongoError`, while for schema-based modules, they can 88 | fail also with a `ParseError`. For this reason, the schema-based modules provide two additional functions: 89 | `toArrayEither` and `toStreamEither`. These functions return, respectively, an array and a stream of `Either`s, allowing 90 | the code to process all the documents, even if some of them are invalid. 91 | 92 | ## Known limitations 93 | 94 | ### Filters for Schema-based operations 95 | 96 | Using `Filter` in schema-based operations (e.g. `find`, `deleteOne`, etc.) is not as straightforward as in 97 | document-based operations. 98 | Given a `Schema`, `A` is the runtime/decoded type of the documents, while `I` is the persisted/encoded type of 99 | the same documents. 100 | Therefore, to filter documents in MongoDB, the client must provide a filter of type `Filter`, where `Filter` is the 101 | type provided by the MongoDB driver. 102 | 103 | A better approach, given the design of the Schema-based operations, would be to provide filters as `Filter`, but 104 | we didn't yet find a straightforward way to map `Filter` to `Filter`. 105 | For this reason, we decided to keep the original MongoDB filter type. 106 | In the future, this may (hopefully) change. 107 | -------------------------------------------------------------------------------- /packages/effect-mongodb/dtslint/AggregationCursor.ts: -------------------------------------------------------------------------------- 1 | import * as AggregationCursor from "effect-mongodb/AggregationCursor" 2 | import * as Schema from "effect/Schema" 3 | 4 | const User = Schema.Struct({ 5 | id: Schema.String 6 | }) 7 | type User = typeof User.Type 8 | type UserEncoded = typeof User.Encoded 9 | type UserContext = typeof User.Context 10 | 11 | declare const cursor: AggregationCursor.AggregationCursor 12 | 13 | // ------------------------------------------------------------------------------------- 14 | // toArray 15 | // ------------------------------------------------------------------------------------- 16 | 17 | // $ExpectType Effect<{ readonly id: string; }[], MongoError | ParseError, never> 18 | AggregationCursor.toArray(cursor) 19 | 20 | // ------------------------------------------------------------------------------------- 21 | // toStream 22 | // ------------------------------------------------------------------------------------- 23 | 24 | // $ExpectType Stream<{ readonly id: string; }, MongoError | ParseError, never> 25 | AggregationCursor.toStream(cursor) 26 | -------------------------------------------------------------------------------- /packages/effect-mongodb/dtslint/Collection.ts: -------------------------------------------------------------------------------- 1 | import * as Collection from "effect-mongodb/Collection" 2 | import * as DocumentCollection from "effect-mongodb/DocumentCollection" 3 | import * as FindCursor from "effect-mongodb/FindCursor" 4 | import * as F from "effect/Function" 5 | import * as Schema from "effect/Schema" 6 | 7 | const MyType = Schema.Struct({ 8 | birthday: Schema.Date 9 | }) 10 | type MyType = typeof MyType.Type 11 | 12 | declare const myType: MyType 13 | 14 | declare const documentCollection: DocumentCollection.DocumentCollection 15 | const collection = DocumentCollection.typed(documentCollection, MyType) 16 | 17 | // ------------------------------------------------------------------------------------- 18 | // find 19 | // ------------------------------------------------------------------------------------- 20 | 21 | // $ExpectType Effect<{ readonly birthday: Date; }[], MongoError | ParseError, never> 22 | FindCursor.toArray(Collection.find(collection, { birthday: "2024-11-28" })) 23 | 24 | // $ExpectType Effect<{ readonly birthday: Date; }[], MongoError | ParseError, never> 25 | F.pipe(collection, Collection.find({ birthday: "2024-11-28" }), FindCursor.toArray) 26 | 27 | // ------------------------------------------------------------------------------------- 28 | // findOne 29 | // ------------------------------------------------------------------------------------- 30 | 31 | // $ExpectType Effect, MongoError | ParseError, never> 32 | Collection.findOne(collection, { birthday: "2024-11-28" }) 33 | 34 | // $ExpectType Effect, MongoError | ParseError, never> 35 | F.pipe(collection, Collection.findOne({ birthday: "2024-11-28" })) 36 | 37 | // ------------------------------------------------------------------------------------- 38 | // insertOne 39 | // ------------------------------------------------------------------------------------- 40 | 41 | // $ExpectType Effect, MongoError | ParseError, never> 42 | Collection.insertOne(collection, myType) 43 | 44 | // $ExpectType Effect, MongoError | ParseError, never> 45 | F.pipe(collection, Collection.insertOne(myType)) 46 | 47 | // ------------------------------------------------------------------------------------- 48 | // insertMany 49 | // ------------------------------------------------------------------------------------- 50 | 51 | // $ExpectType Effect, MongoError | ParseError, never> 52 | Collection.insertMany(collection, [myType]) 53 | 54 | // $ExpectType Effect, MongoError | ParseError, never> 55 | F.pipe(collection, Collection.insertMany([myType])) 56 | 57 | // ------------------------------------------------------------------------------------- 58 | // deleteOne 59 | // ------------------------------------------------------------------------------------- 60 | 61 | // $ExpectType Effect 62 | Collection.deleteOne(collection, { birthday: "2024-11-28" }) 63 | 64 | // $ExpectType Effect 65 | F.pipe(collection, Collection.deleteOne({ birthday: "2024-11-28" })) 66 | 67 | // ------------------------------------------------------------------------------------- 68 | // deleteMany 69 | // ------------------------------------------------------------------------------------- 70 | 71 | // $ExpectType Effect 72 | Collection.deleteMany(collection, { birthday: "2024-11-28" }) 73 | 74 | // $ExpectType Effect 75 | F.pipe(collection, Collection.deleteMany({ birthday: "2024-11-28" })) 76 | 77 | // ------------------------------------------------------------------------------------- 78 | // updateMany 79 | // ------------------------------------------------------------------------------------- 80 | 81 | // $ExpectType Effect, MongoError, never> 82 | Collection.updateMany(collection, { birthday: "2024-11-28" }, { $set: { birthday: "2024-11-29" } }) 83 | 84 | // $ExpectType Effect, MongoError, never> 85 | F.pipe(collection, Collection.updateMany({ birthday: "2024-11-28" }, { $set: { birthday: "2024-11-29" } })) 86 | 87 | // ------------------------------------------------------------------------------------- 88 | // replaceOne 89 | // ------------------------------------------------------------------------------------- 90 | 91 | // $ExpectType Effect, MongoError | ParseError, never> 92 | Collection.replaceOne(collection, { birthday: "2024-11-28" }, myType) 93 | 94 | // $ExpectType Effect, MongoError | ParseError, never> 95 | F.pipe(collection, Collection.replaceOne({ birthday: "2024-11-28" }, myType)) 96 | 97 | // ------------------------------------------------------------------------------------- 98 | // findOneAndReplace 99 | // ------------------------------------------------------------------------------------- 100 | 101 | // $ExpectType Effect, MongoError | ParseError, never> 102 | Collection.findOneAndReplace(collection, { birthday: "2024-11-28" }, myType, { includeResultMetadata: true }) 103 | 104 | // $ExpectType Effect, MongoError | ParseError, never> 105 | F.pipe(collection, Collection.findOneAndReplace({ birthday: "2024-11-28" }, myType, { includeResultMetadata: true })) 106 | 107 | // $ExpectType Effect, MongoError | ParseError, never> 108 | Collection.findOneAndReplace(collection, { birthday: "2024-11-28" }, myType, { includeResultMetadata: false }) 109 | 110 | // $ExpectType Effect, MongoError | ParseError, never> 111 | F.pipe(collection, Collection.findOneAndReplace({ birthday: "2024-11-28" }, myType, { includeResultMetadata: false })) 112 | 113 | // $ExpectType Effect, MongoError | ParseError, never> 114 | Collection.findOneAndReplace(collection, { birthday: "2024-11-28" }, myType, { comment: "any" }) 115 | 116 | // $ExpectType Effect, MongoError | ParseError, never> 117 | F.pipe(collection, Collection.findOneAndReplace({ birthday: "2024-11-28" }, myType, { comment: "any" })) 118 | 119 | // $ExpectType Effect, MongoError | ParseError, never> 120 | Collection.findOneAndReplace(collection, { birthday: "2024-11-28" }, myType) 121 | 122 | // $ExpectType Effect, MongoError | ParseError, never> 123 | F.pipe(collection, Collection.findOneAndReplace({ birthday: "2024-11-28" }, myType)) 124 | 125 | // ------------------------------------------------------------------------------------- 126 | // rename 127 | // ------------------------------------------------------------------------------------- 128 | 129 | // $ExpectType Effect, MongoError, never> 130 | Collection.rename(collection, "new-collection") 131 | 132 | // $ExpectType Effect, MongoError, never> 133 | F.pipe(collection, Collection.rename("new-collection")) 134 | 135 | // ------------------------------------------------------------------------------------- 136 | // drop 137 | // ------------------------------------------------------------------------------------- 138 | 139 | // $ExpectType Effect 140 | Collection.drop(collection) 141 | 142 | // $ExpectType Effect 143 | F.pipe(collection, Collection.drop()) 144 | 145 | // ------------------------------------------------------------------------------------- 146 | // createIndexes 147 | // ------------------------------------------------------------------------------------- 148 | 149 | // $ExpectType Effect 150 | Collection.createIndexes(collection, [{ key: { birthday: 1 } }]) 151 | 152 | // $ExpectType Effect 153 | F.pipe(collection, Collection.createIndexes([{ key: { birthday: 1 } }])) 154 | 155 | // ------------------------------------------------------------------------------------- 156 | // createIndex 157 | // ------------------------------------------------------------------------------------- 158 | 159 | // $ExpectType Effect 160 | Collection.createIndex(collection, { birthday: 1 }) 161 | 162 | // $ExpectType Effect 163 | F.pipe(collection, Collection.createIndex({ birthday: 1 })) 164 | 165 | // ------------------------------------------------------------------------------------- 166 | // dropIndex 167 | // ------------------------------------------------------------------------------------- 168 | 169 | // $ExpectType Effect 170 | Collection.dropIndex(collection, "birthday_1") 171 | 172 | // $ExpectType Effect 173 | F.pipe(collection, Collection.dropIndex("birthday_1")) 174 | 175 | // ------------------------------------------------------------------------------------- 176 | // aggregate 177 | // ------------------------------------------------------------------------------------- 178 | 179 | const MyAggregatedType = Schema.Struct({ 180 | _id: Schema.Date, 181 | birthdays: Schema.Number 182 | }) 183 | const groupByBirthday = [{ $group: { _id: "$birthday", birthdays: { $sum: 1 } } }] 184 | 185 | // $ExpectType AggregationCursor<{ readonly _id: Date; readonly birthdays: number; }, { readonly _id: string; readonly birthdays: number; }, never> 186 | Collection.aggregate(collection, MyAggregatedType, groupByBirthday) 187 | 188 | // $ExpectType AggregationCursor<{ readonly _id: Date; readonly birthdays: number; }, { readonly _id: string; readonly birthdays: number; }, never> 189 | F.pipe(collection, Collection.aggregate(MyAggregatedType, groupByBirthday)) 190 | 191 | // ------------------------------------------------------------------------------------- 192 | // estimatedDocumentCount 193 | // ------------------------------------------------------------------------------------- 194 | 195 | // $ExpectType Effect 196 | Collection.estimatedDocumentCount(collection) 197 | 198 | // $ExpectType Effect 199 | F.pipe(collection, Collection.estimatedDocumentCount()) 200 | 201 | // ------------------------------------------------------------------------------------- 202 | // countDocuments 203 | // ------------------------------------------------------------------------------------- 204 | 205 | // $ExpectType Effect 206 | Collection.countDocuments(collection, { birthday: "2024-11-28" }) 207 | 208 | // $ExpectType Effect 209 | F.pipe(collection, Collection.countDocuments({ birthday: "2024-11-28" })) 210 | -------------------------------------------------------------------------------- /packages/effect-mongodb/dtslint/Db.ts: -------------------------------------------------------------------------------- 1 | import * as Db from "effect-mongodb/Db" 2 | import * as F from "effect/Function" 3 | import * as Schema from "effect/Schema" 4 | 5 | declare const database: Db.Db 6 | 7 | const User = Schema.Struct({ 8 | id: Schema.NumberFromString 9 | }) 10 | 11 | // ------------------------------------------------------------------------------------- 12 | // documentCollection 13 | // ------------------------------------------------------------------------------------- 14 | 15 | // $ExpectType DocumentCollection 16 | Db.documentCollection(database, "users") 17 | 18 | // $ExpectType DocumentCollection 19 | F.pipe(database, Db.documentCollection("users")) 20 | 21 | // ------------------------------------------------------------------------------------- 22 | // collection 23 | // ------------------------------------------------------------------------------------- 24 | 25 | // $ExpectType Collection<{ readonly id: number; }, { readonly id: string; }, never> 26 | Db.collection(database, "users", User) 27 | 28 | // $ExpectType Collection<{ readonly id: number; }, { readonly id: string; }, never> 29 | F.pipe(database, Db.collection("users", User)) 30 | 31 | // ------------------------------------------------------------------------------------- 32 | // listCollections 33 | // ------------------------------------------------------------------------------------- 34 | 35 | // $ExpectType ListCollectionsCursor 36 | Db.listCollections(database) 37 | 38 | // TODO: this currently returns `(db: Db) => ListCollectionsCursor`, why? 39 | // //$ExpectType ListCollectionsCursor 40 | // F.pipe(database, Db.listCollections) 41 | 42 | // $ExpectType ListCollectionsCursor 43 | Db.listCollections(database, { foo: "bar" }) 44 | 45 | // $ExpectType ListCollectionsCursor 46 | F.pipe(database, Db.listCollections({ foo: "bar" })) 47 | 48 | // $ExpectType NameOnlyListCollectionsCursor 49 | Db.listCollections(database, { foo: "bar" }, { nameOnly: true }) 50 | 51 | // $ExpectType NameOnlyListCollectionsCursor 52 | F.pipe(database, Db.listCollections({ foo: "bar" }, { nameOnly: true })) 53 | 54 | // $ExpectType FullListCollectionsCursor 55 | Db.listCollections(database, { foo: "bar" }, { nameOnly: false }) 56 | 57 | // $ExpectType FullListCollectionsCursor 58 | F.pipe(database, Db.listCollections({ foo: "bar" }, { nameOnly: false })) 59 | 60 | // ------------------------------------------------------------------------------------- 61 | // dropCollection 62 | // ------------------------------------------------------------------------------------- 63 | 64 | // $ExpectType Effect 65 | Db.dropCollection(database, "users") 66 | 67 | // $ExpectType Effect 68 | F.pipe(database, Db.dropCollection("users")) 69 | -------------------------------------------------------------------------------- /packages/effect-mongodb/dtslint/DocumentAggregationCursor.ts: -------------------------------------------------------------------------------- 1 | import * as DocumentAggregationCursor from "effect-mongodb/DocumentAggregationCursor" 2 | 3 | declare const cursor: DocumentAggregationCursor.DocumentAggregationCursor 4 | 5 | // ------------------------------------------------------------------------------------- 6 | // toArray 7 | // ------------------------------------------------------------------------------------- 8 | 9 | // $ExpectType Effect 10 | DocumentAggregationCursor.toArray(cursor) 11 | 12 | // ------------------------------------------------------------------------------------- 13 | // toStream 14 | // ------------------------------------------------------------------------------------- 15 | 16 | // $ExpectType Stream 17 | DocumentAggregationCursor.toStream(cursor) 18 | -------------------------------------------------------------------------------- /packages/effect-mongodb/dtslint/DocumentCollection.ts: -------------------------------------------------------------------------------- 1 | import * as DocumentCollection from "effect-mongodb/DocumentCollection" 2 | import * as DocumentFindCursor from "effect-mongodb/DocumentFindCursor" 3 | import * as F from "effect/Function" 4 | import * as Schema from "effect/Schema" 5 | import type { Document } from "mongodb" 6 | 7 | const MyType = Schema.Struct({ 8 | birthday: Schema.Date 9 | }) 10 | type MyType = typeof MyType.Type 11 | 12 | declare const anyDocument: Document 13 | 14 | declare const collection: DocumentCollection.DocumentCollection 15 | 16 | // ------------------------------------------------------------------------------------- 17 | // find 18 | // ------------------------------------------------------------------------------------- 19 | 20 | // $ExpectType Effect 21 | DocumentFindCursor.toArray(DocumentCollection.find(collection, { birthday: "2024-11-28" })) 22 | 23 | // $ExpectType Effect 24 | F.pipe(collection, DocumentCollection.find({ birthday: "2024-11-28" }), DocumentFindCursor.toArray) 25 | 26 | // ------------------------------------------------------------------------------------- 27 | // findOne 28 | // ------------------------------------------------------------------------------------- 29 | 30 | // $ExpectType Effect, MongoError, never> 31 | DocumentCollection.findOne(collection, { birthday: "2024-11-28" }) 32 | 33 | // $ExpectType Effect, MongoError, never> 34 | F.pipe(collection, DocumentCollection.findOne({ birthday: "2024-11-28" })) 35 | 36 | // ------------------------------------------------------------------------------------- 37 | // insertOne 38 | // ------------------------------------------------------------------------------------- 39 | 40 | // $ExpectType Effect, MongoError, never> 41 | DocumentCollection.insertOne(collection, anyDocument) 42 | 43 | // $ExpectType Effect, MongoError, never> 44 | DocumentCollection.insertOne(collection, anyDocument, { comment: "any" }) 45 | 46 | // $ExpectType Effect, MongoError, never> 47 | F.pipe(collection, DocumentCollection.insertOne(anyDocument)) 48 | 49 | // ------------------------------------------------------------------------------------- 50 | // insertMany 51 | // ------------------------------------------------------------------------------------- 52 | 53 | // $ExpectType Effect, MongoError, never> 54 | DocumentCollection.insertMany(collection, [anyDocument]) 55 | 56 | // $ExpectType Effect, MongoError, never> 57 | F.pipe(collection, DocumentCollection.insertMany([anyDocument])) 58 | 59 | // ------------------------------------------------------------------------------------- 60 | // deleteOne 61 | // ------------------------------------------------------------------------------------- 62 | 63 | // $ExpectType Effect 64 | DocumentCollection.deleteOne(collection, { birthday: "2024-11-28" }) 65 | 66 | // $ExpectType Effect 67 | F.pipe(collection, DocumentCollection.deleteOne({ birthday: "2024-11-28" })) 68 | 69 | // ------------------------------------------------------------------------------------- 70 | // deleteMany 71 | // ------------------------------------------------------------------------------------- 72 | 73 | // $ExpectType Effect 74 | DocumentCollection.deleteMany(collection, { birthday: "2024-11-28" }) 75 | 76 | // $ExpectType Effect 77 | F.pipe(collection, DocumentCollection.deleteMany({ birthday: "2024-11-28" })) 78 | 79 | // ------------------------------------------------------------------------------------- 80 | // updateMany 81 | // ------------------------------------------------------------------------------------- 82 | 83 | // $ExpectType Effect, MongoError, never> 84 | DocumentCollection.updateMany(collection, { birthday: "2024-11-28" }, { $set: { birthday: "2024-11-29" } }) 85 | 86 | // $ExpectType Effect, MongoError, never> 87 | F.pipe(collection, DocumentCollection.updateMany({ birthday: "2024-11-28" }, { $set: { birthday: "2024-11-29" } })) 88 | 89 | // ------------------------------------------------------------------------------------- 90 | // replaceOne 91 | // ------------------------------------------------------------------------------------- 92 | 93 | // $ExpectType Effect, MongoError, never> 94 | DocumentCollection.replaceOne(collection, { birthday: "2024-11-28" }, anyDocument) 95 | 96 | // $ExpectType Effect, MongoError, never> 97 | F.pipe(collection, DocumentCollection.replaceOne({ birthday: "2024-11-28" }, anyDocument)) 98 | 99 | // ------------------------------------------------------------------------------------- 100 | // findOneAndReplace 101 | // ------------------------------------------------------------------------------------- 102 | 103 | // $ExpectType Effect, MongoError, never> 104 | DocumentCollection.findOneAndReplace(collection, { birthday: "2024-11-28" }, anyDocument, { 105 | includeResultMetadata: true 106 | }) 107 | 108 | // $ExpectType Effect, MongoError, never> 109 | F.pipe( 110 | collection, 111 | DocumentCollection.findOneAndReplace({ birthday: "2024-11-28" }, anyDocument, { includeResultMetadata: true }) 112 | ) 113 | 114 | // $ExpectType Effect, MongoError, never> 115 | DocumentCollection.findOneAndReplace(collection, { birthday: "2024-11-28" }, anyDocument, { 116 | includeResultMetadata: false 117 | }) 118 | 119 | // $ExpectType Effect, MongoError, never> 120 | F.pipe( 121 | collection, 122 | DocumentCollection.findOneAndReplace({ birthday: "2024-11-28" }, anyDocument, { includeResultMetadata: false }) 123 | ) 124 | 125 | // $ExpectType Effect, MongoError, never> 126 | DocumentCollection.findOneAndReplace(collection, { birthday: "2024-11-28" }, anyDocument, { comment: "any" }) 127 | 128 | // $ExpectType Effect, MongoError, never> 129 | F.pipe(collection, DocumentCollection.findOneAndReplace({ birthday: "2024-11-28" }, anyDocument, { comment: "any" })) 130 | 131 | // $ExpectType Effect, MongoError, never> 132 | DocumentCollection.findOneAndReplace(collection, { birthday: "2024-11-28" }, anyDocument) 133 | 134 | // $ExpectType Effect, MongoError, never> 135 | F.pipe(collection, DocumentCollection.findOneAndReplace({ birthday: "2024-11-28" }, anyDocument)) 136 | 137 | // ------------------------------------------------------------------------------------- 138 | // rename 139 | // ------------------------------------------------------------------------------------- 140 | 141 | // $ExpectType Effect 142 | DocumentCollection.rename(collection, "new-collection") 143 | 144 | // $ExpectType Effect 145 | F.pipe(collection, DocumentCollection.rename("new-collection")) 146 | 147 | // ------------------------------------------------------------------------------------- 148 | // drop 149 | // ------------------------------------------------------------------------------------- 150 | 151 | // $ExpectType Effect 152 | DocumentCollection.drop(collection) 153 | 154 | // $ExpectType Effect 155 | F.pipe(collection, DocumentCollection.drop()) 156 | 157 | // ------------------------------------------------------------------------------------- 158 | // createIndexes 159 | // ------------------------------------------------------------------------------------- 160 | 161 | // $ExpectType Effect 162 | DocumentCollection.createIndexes(collection, [{ key: { birthday: 1 } }]) 163 | 164 | // $ExpectType Effect 165 | F.pipe(collection, DocumentCollection.createIndexes([{ key: { birthday: 1 } }])) 166 | 167 | // ------------------------------------------------------------------------------------- 168 | // createIndex 169 | // ------------------------------------------------------------------------------------- 170 | 171 | // $ExpectType Effect 172 | DocumentCollection.createIndex(collection, { birthday: 1 }) 173 | 174 | // $ExpectType Effect 175 | F.pipe(collection, DocumentCollection.createIndex({ birthday: 1 })) 176 | 177 | // ------------------------------------------------------------------------------------- 178 | // dropIndex 179 | // ------------------------------------------------------------------------------------- 180 | 181 | // $ExpectType Effect 182 | DocumentCollection.dropIndex(collection, "birthday_1") 183 | 184 | // $ExpectType Effect 185 | F.pipe(collection, DocumentCollection.dropIndex("birthday_1")) 186 | 187 | // ------------------------------------------------------------------------------------- 188 | // aggregate 189 | // ------------------------------------------------------------------------------------- 190 | 191 | const groupByBirthday = [{ $group: { _id: "$birthday", birthdays: { $sum: 1 } } }] 192 | 193 | // $ExpectType DocumentAggregationCursor 194 | DocumentCollection.aggregate(collection, groupByBirthday) 195 | 196 | // $ExpectType DocumentAggregationCursor 197 | F.pipe(collection, DocumentCollection.aggregate(groupByBirthday)) 198 | 199 | // ------------------------------------------------------------------------------------- 200 | // estimatedDocumentCount 201 | // ------------------------------------------------------------------------------------- 202 | 203 | // $ExpectType Effect 204 | DocumentCollection.estimatedDocumentCount(collection) 205 | 206 | // $ExpectType Effect 207 | F.pipe(collection, DocumentCollection.estimatedDocumentCount()) 208 | 209 | // ------------------------------------------------------------------------------------- 210 | // countDocuments 211 | // ------------------------------------------------------------------------------------- 212 | 213 | // $ExpectType Effect 214 | DocumentCollection.countDocuments(collection, { birthday: "2024-11-28" }) 215 | 216 | // $ExpectType Effect 217 | F.pipe(collection, DocumentCollection.countDocuments({ birthday: "2024-11-28" })) 218 | 219 | // ------------------------------------------------------------------------------------- 220 | // typed 221 | // ----------------------------------------------------------------- 222 | 223 | // $ExpectType Collection<{ readonly birthday: Date; }, { readonly birthday: string; }, never> 224 | DocumentCollection.typed(collection, MyType) 225 | 226 | // $ExpectType Collection<{ readonly birthday: Date; }, { readonly birthday: string; }, never> 227 | F.pipe(collection, DocumentCollection.typed(MyType)) 228 | 229 | // @ts-expect-error 230 | DocumentCollection.typed(collection, Schema.Date) 231 | 232 | // TODO the following test should work, i.e. array are not acceptable as a collection type 233 | // // @ts-expect-error 234 | // DocumentCollection.typed(collection, Schema.Array(MyType)) 235 | -------------------------------------------------------------------------------- /packages/effect-mongodb/dtslint/DocumentFindCursor.ts: -------------------------------------------------------------------------------- 1 | import * as DocumentFindCursor from "effect-mongodb/DocumentFindCursor" 2 | import * as F from "effect/Function" 3 | import * as Schema from "effect/Schema" 4 | 5 | declare const cursor: DocumentFindCursor.DocumentFindCursor 6 | 7 | // ------------------------------------------------------------------------------------- 8 | // filter 9 | // ------------------------------------------------------------------------------------- 10 | 11 | // $ExpectType DocumentFindCursor 12 | DocumentFindCursor.filter(cursor, { id: "1" }) 13 | 14 | // $ExpectType DocumentFindCursor 15 | F.pipe(cursor, DocumentFindCursor.filter({ id: "1" })) 16 | 17 | // ------------------------------------------------------------------------------------- 18 | // project 19 | // ------------------------------------------------------------------------------------- 20 | 21 | // $ExpectType DocumentFindCursor 22 | DocumentFindCursor.project(cursor, { _id: 0, id: 1 }) 23 | 24 | // $ExpectType DocumentFindCursor 25 | F.pipe(cursor, DocumentFindCursor.project({ _id: 0, id: 1 })) 26 | 27 | // ------------------------------------------------------------------------------------- 28 | // sort 29 | // ------------------------------------------------------------------------------------- 30 | 31 | // $ExpectType DocumentFindCursor 32 | DocumentFindCursor.sort(cursor, { name: 1 }) 33 | 34 | // $ExpectType DocumentFindCursor 35 | F.pipe(cursor, DocumentFindCursor.sort({ name: 1 })) 36 | 37 | // ------------------------------------------------------------------------------------- 38 | // limit 39 | // ------------------------------------------------------------------------------------- 40 | 41 | // $ExpectType DocumentFindCursor 42 | DocumentFindCursor.limit(cursor, 50) 43 | 44 | // $ExpectType DocumentFindCursor 45 | F.pipe(cursor, DocumentFindCursor.limit(50)) 46 | 47 | // ------------------------------------------------------------------------------------- 48 | // toArray 49 | // ------------------------------------------------------------------------------------- 50 | 51 | // $ExpectType Effect 52 | DocumentFindCursor.toArray(cursor) 53 | 54 | // ------------------------------------------------------------------------------------- 55 | // toStream 56 | // ------------------------------------------------------------------------------------- 57 | 58 | // $ExpectType Stream 59 | DocumentFindCursor.toStream(cursor) 60 | 61 | // ------------------------------------------------------------------------------------- 62 | // typed 63 | // ------------------------------------------------------------------------------------- 64 | 65 | const User = Schema.Struct({ name: Schema.String }) 66 | 67 | // $ExpectType FindCursor<{ readonly name: string; }, { readonly name: string; }, never> 68 | DocumentFindCursor.typed(cursor, User) 69 | 70 | // $ExpectType FindCursor<{ readonly name: string; }, { readonly name: string; }, never> 71 | F.pipe(cursor, DocumentFindCursor.typed(User)) 72 | -------------------------------------------------------------------------------- /packages/effect-mongodb/dtslint/FindCursor.ts: -------------------------------------------------------------------------------- 1 | import * as FindCursor from "effect-mongodb/FindCursor" 2 | import * as F from "effect/Function" 3 | import * as Schema from "effect/Schema" 4 | 5 | const User = Schema.Struct({ 6 | id: Schema.NumberFromString, 7 | name: Schema.String 8 | }) 9 | type User = typeof User.Type 10 | type UserEncoded = typeof User.Encoded 11 | type UserContext = typeof User.Context 12 | 13 | declare const cursor: FindCursor.FindCursor 14 | 15 | // ------------------------------------------------------------------------------------- 16 | // filter 17 | // ------------------------------------------------------------------------------------- 18 | 19 | // $ExpectType FindCursor<{ readonly id: number; readonly name: string; }, { readonly id: string; readonly name: string; }, never> 20 | FindCursor.filter(cursor, { id: "1" }) 21 | 22 | // $ExpectType FindCursor<{ readonly id: number; readonly name: string; }, { readonly id: string; readonly name: string; }, never> 23 | F.pipe(cursor, FindCursor.filter({ id: "1" })) 24 | 25 | // ------------------------------------------------------------------------------------- 26 | // project 27 | // ------------------------------------------------------------------------------------- 28 | 29 | const UserProjection = Schema.Struct({ 30 | id: User.fields.id 31 | }) 32 | 33 | // $ExpectType FindCursor<{ readonly id: number; }, { readonly id: string; }, never> 34 | FindCursor.project(cursor, UserProjection, { _id: 0, id: 1 }) 35 | 36 | // $ExpectType FindCursor<{ readonly id: number; }, { readonly id: string; }, never> 37 | F.pipe(cursor, FindCursor.project(UserProjection, { _id: 0, id: 1 })) 38 | 39 | // ------------------------------------------------------------------------------------- 40 | // sort 41 | // ------------------------------------------------------------------------------------- 42 | 43 | // $ExpectType FindCursor<{ readonly id: number; readonly name: string; }, { readonly id: string; readonly name: string; }, never> 44 | FindCursor.sort(cursor, { name: 1 }) 45 | 46 | // $ExpectType FindCursor<{ readonly id: number; readonly name: string; }, { readonly id: string; readonly name: string; }, never> 47 | F.pipe(cursor, FindCursor.sort({ name: 1 })) 48 | 49 | // ------------------------------------------------------------------------------------- 50 | // limit 51 | // ------------------------------------------------------------------------------------- 52 | 53 | // $ExpectType FindCursor<{ readonly id: number; readonly name: string; }, { readonly id: string; readonly name: string; }, never> 54 | FindCursor.limit(cursor, 50) 55 | 56 | // $ExpectType FindCursor<{ readonly id: number; readonly name: string; }, { readonly id: string; readonly name: string; }, never> 57 | F.pipe(cursor, FindCursor.limit(50)) 58 | 59 | // ------------------------------------------------------------------------------------- 60 | // toArray 61 | // ------------------------------------------------------------------------------------- 62 | 63 | // $ExpectType Effect<{ readonly id: number; readonly name: string; }[], MongoError | ParseError, never> 64 | FindCursor.toArray(cursor) 65 | 66 | // ------------------------------------------------------------------------------------- 67 | // toArrayEither 68 | // ------------------------------------------------------------------------------------- 69 | 70 | // $ExpectType Effect[], MongoError, never> 71 | FindCursor.toArrayEither(cursor) 72 | 73 | // ------------------------------------------------------------------------------------- 74 | // toStream 75 | // ------------------------------------------------------------------------------------- 76 | 77 | // $ExpectType Stream<{ readonly id: number; readonly name: string; }, MongoError | ParseError, never> 78 | FindCursor.toStream(cursor) 79 | 80 | // ------------------------------------------------------------------------------------- 81 | // toStreamEither 82 | // ------------------------------------------------------------------------------------- 83 | 84 | // $ExpectType Stream, MongoError, never> 85 | FindCursor.toStreamEither(cursor) 86 | -------------------------------------------------------------------------------- /packages/effect-mongodb/dtslint/ListCollectionsCursor.ts: -------------------------------------------------------------------------------- 1 | import * as ListCollectionsCursor from "effect-mongodb/ListCollectionsCursor" 2 | 3 | declare const cursor: ListCollectionsCursor.ListCollectionsCursor 4 | 5 | // ------------------------------------------------------------------------------------- 6 | // toArray 7 | // ------------------------------------------------------------------------------------- 8 | 9 | // $ExpectType Effect 10 | ListCollectionsCursor.toArray(cursor) 11 | 12 | // ------------------------------------------------------------------------------------- 13 | // toStream 14 | // ------------------------------------------------------------------------------------- 15 | 16 | // $ExpectType Stream 17 | ListCollectionsCursor.toStream(cursor) 18 | -------------------------------------------------------------------------------- /packages/effect-mongodb/dtslint/MongoClient.ts: -------------------------------------------------------------------------------- 1 | import * as MongoClient from "effect-mongodb/MongoClient" 2 | import * as F from "effect/Function" 3 | 4 | declare const client: MongoClient.MongoClient 5 | 6 | // ------------------------------------------------------------------------------------- 7 | // connect 8 | // ------------------------------------------------------------------------------------- 9 | 10 | // $ExpectType Effect 11 | MongoClient.connect("mongodb://localhost:27017") 12 | 13 | // ------------------------------------------------------------------------------------- 14 | // close 15 | // ------------------------------------------------------------------------------------- 16 | 17 | // $ExpectType Effect 18 | MongoClient.close(client) 19 | 20 | // $ExpectType Effect 21 | F.pipe(client, MongoClient.close) 22 | 23 | // $ExpectType Effect 24 | MongoClient.close(client, true) 25 | 26 | // $ExpectType Effect 27 | F.pipe(client, MongoClient.close(true)) 28 | 29 | // ------------------------------------------------------------------------------------- 30 | // connectScoped 31 | // ------------------------------------------------------------------------------------- 32 | 33 | // $ExpectType Effect 34 | MongoClient.connectScoped("mongodb://localhost:27017") 35 | 36 | // ------------------------------------------------------------------------------------- 37 | // db 38 | // ------------------------------------------------------------------------------------- 39 | 40 | // $ExpectType Db 41 | MongoClient.db(client, "my-db") 42 | 43 | // $ExpectType Db 44 | F.pipe(client, MongoClient.db("my-db")) 45 | -------------------------------------------------------------------------------- /packages/effect-mongodb/dtslint/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../tsconfig.base.json", 3 | "include": ["."], 4 | "compilerOptions": { 5 | "incremental": false, 6 | "composite": false, 7 | "noUnusedLocals": false 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/effect-mongodb/examples/README.md: -------------------------------------------------------------------------------- 1 | # effect-mongodb Examples 2 | 3 | This directory contains some examples of `effect-mongodb` package. 4 | 5 | In root directory of this repository you can find a `docker-compose.yml` that sets up a local MongoDB instance. 6 | 7 | ```shell 8 | pnpm run infra 9 | ``` 10 | 11 | Then you can run the examples with: 12 | 13 | ```shell 14 | cd packages/effect-mongodb 15 | pnpm tsx examples/.ts 16 | ``` -------------------------------------------------------------------------------- /packages/effect-mongodb/examples/aggregate.ts: -------------------------------------------------------------------------------- 1 | import * as AggregationCursor from "effect-mongodb/AggregationCursor" 2 | import * as Collection from "effect-mongodb/Collection" 3 | import * as Db from "effect-mongodb/Db" 4 | import * as MongoClient from "effect-mongodb/MongoClient" 5 | import * as Console from "effect/Console" 6 | import * as Effect from "effect/Effect" 7 | import * as Schema from "effect/Schema" 8 | 9 | /** 10 | * Aggregate 11 | * 12 | * Highlights: 13 | * The `Collection.aggregate` function requires a new schema as a parameter 14 | * because, in most cases, the aggregation result will differ from the original schema. 15 | */ 16 | const program = Effect.gen(function*() { 17 | const sourceInstance = yield* MongoClient.connectScoped("mongodb://localhost:27017") 18 | const sourceDb = MongoClient.db(sourceInstance, "aggregate") 19 | const sourceCollection = Db.collection(sourceDb, "records", MyType) 20 | yield* Collection.insertMany(sourceCollection, [ 21 | MyType.make({ id: 1, source: "A" }), 22 | MyType.make({ id: 2, source: "B" }), 23 | MyType.make({ id: 3, source: "B" }), 24 | MyType.make({ id: 4, source: "C" }), 25 | MyType.make({ id: 5, source: "B" }), 26 | MyType.make({ id: 6, source: "C" }), 27 | MyType.make({ id: 7, source: "A" }) 28 | ]) 29 | 30 | const items = yield* Collection.aggregate(sourceCollection, MyTypeAggregation, [ 31 | { 32 | $group: { 33 | _id: "$source", 34 | elements: { $addToSet: "$id" } 35 | } 36 | }, 37 | { 38 | $sort: { _id: 1 } 39 | } 40 | ]).pipe(AggregationCursor.toArray) 41 | 42 | yield* Console.log(items) 43 | }) 44 | 45 | const Source = Schema.Literal("A", "B", "C") 46 | 47 | const MyType = Schema.Struct({ 48 | id: Schema.Int, 49 | source: Source 50 | }) 51 | 52 | const MyTypeAggregation = Schema.Struct({ 53 | _id: Source, 54 | elements: Schema.Array(Schema.Int) 55 | }) 56 | 57 | await program.pipe(Effect.scoped, Effect.runPromise) 58 | -------------------------------------------------------------------------------- /packages/effect-mongodb/examples/copy-between-dbs.ts: -------------------------------------------------------------------------------- 1 | import * as Collection from "effect-mongodb/Collection" 2 | import * as Db from "effect-mongodb/Db" 3 | import * as DocumentCollection from "effect-mongodb/DocumentCollection" 4 | import * as DocumentFindCursor from "effect-mongodb/DocumentFindCursor" 5 | import * as FindCursor from "effect-mongodb/FindCursor" 6 | import * as MongoClient from "effect-mongodb/MongoClient" 7 | import * as Effect from "effect/Effect" 8 | import * as Schema from "effect/Schema" 9 | 10 | /** 11 | * Copy between dbs 12 | * 13 | * Highlights: 14 | * At line 27, convert a Document-based collection into a Schema-based collection to validate its documents. 15 | */ 16 | const program = Effect.gen(function*() { 17 | const sourceInstance = yield* MongoClient.connectScoped("mongodb://localhost:27017") 18 | const sourceDb = MongoClient.db(sourceInstance, "source") 19 | const sourceCollection = Db.documentCollection(sourceDb, "records") 20 | yield* DocumentCollection.insertMany(sourceCollection, [ 21 | { name: "User 1", age: 30, birthday: "1994-03-10T00:00:00Z" }, 22 | { name: "User 2", age: 80, birthday: "1944-07-21T00:00:00Z" }, 23 | { name: "User 3", age: 4, birthday: "2020-11-03T00:00:00Z" } 24 | ]) 25 | 26 | const sourceItems = yield* DocumentCollection.find(sourceCollection).pipe( 27 | DocumentFindCursor.typed(MyType), 28 | FindCursor.toArray 29 | ) 30 | 31 | const destinationInstance = yield* MongoClient.connectScoped("mongodb://localhost:27017") 32 | const destinationDb = MongoClient.db(destinationInstance, "destination") 33 | const destinationCollection = Db.collection(destinationDb, "records", MyType) 34 | 35 | yield* Collection.insertMany(destinationCollection, sourceItems) 36 | }) 37 | 38 | const MyType = Schema.Struct({ 39 | name: Schema.String, 40 | age: Schema.Number, 41 | birthday: Schema.Date 42 | }) 43 | type MyType = typeof MyType.Type 44 | 45 | await program.pipe(Effect.scoped, Effect.runPromise) 46 | -------------------------------------------------------------------------------- /packages/effect-mongodb/examples/elaborate-stream-with-partitioned-errors.ts: -------------------------------------------------------------------------------- 1 | import * as Db from "effect-mongodb/Db" 2 | import * as DocumentCollection from "effect-mongodb/DocumentCollection" 3 | import * as DocumentFindCursor from "effect-mongodb/DocumentFindCursor" 4 | import * as FindCursor from "effect-mongodb/FindCursor" 5 | import * as MongoClient from "effect-mongodb/MongoClient" 6 | import * as Effect from "effect/Effect" 7 | import * as E from "effect/Either" 8 | import * as ParseResult from "effect/ParseResult" 9 | import * as Schema from "effect/Schema" 10 | import * as Stream from "effect/Stream" 11 | 12 | /** 13 | * Elaborate stream with partitioned errors 14 | * 15 | * Highlights: 16 | * Use `toStreamEither` or `toArrayEither` to prevent the Stream/Effect from short-circuiting when the first error occur. 17 | * In this example, we log the error and continue processing the remaining documents. 18 | */ 19 | const program = Effect.gen(function*() { 20 | const sourceInstance = yield* MongoClient.connectScoped("mongodb://localhost:27017") 21 | const sourceDb = MongoClient.db(sourceInstance, "elaborate-stream-with-partitioned-errors") 22 | const sourceCollection = Db.documentCollection(sourceDb, "records") 23 | yield* DocumentCollection.insertMany(sourceCollection, [ 24 | { name: "User 1", age: 30, birthday: "1994-03-10T00:00:00Z" }, 25 | { name: "User 2", age: "24", birthday: "2000-04-25T00:00:00Z" }, 26 | { name: "User 3", age: 29, birthday: "802828800000" }, 27 | { name: "User 4", age: 80, birthday: "1944-07-21T00:00:00Z" }, 28 | { name: "User 5", age: 4, birthday: "2020-11-03T00:00:00Z" }, 29 | { name: "User 6", age: 30, birthday: "19940310T000000Z" } 30 | ]) 31 | 32 | yield* DocumentCollection.find(sourceCollection).pipe( 33 | DocumentFindCursor.typed(MyType), 34 | FindCursor.toStreamEither, 35 | Stream.mapEffect( 36 | E.match({ 37 | onLeft: ([document, error]) => 38 | ParseResult.TreeFormatter.formatError(error).pipe( 39 | Effect.flatMap((error) => Effect.logError(`Unable to decode item`, { document, error })) 40 | ), 41 | onRight: (x) => Effect.log(`Elaborated ${x.name}`) 42 | }) 43 | ), 44 | Stream.runDrain 45 | ) 46 | }) 47 | 48 | const MyType = Schema.Struct({ 49 | name: Schema.String, 50 | age: Schema.Number, 51 | birthday: Schema.Date 52 | }) 53 | type MyType = typeof MyType.Type 54 | 55 | await program.pipe(Effect.scoped, Effect.runPromise) 56 | -------------------------------------------------------------------------------- /packages/effect-mongodb/examples/find-with-cursor-builder.ts: -------------------------------------------------------------------------------- 1 | import * as Collection from "effect-mongodb/Collection" 2 | import * as Db from "effect-mongodb/Db" 3 | import * as FindCursor from "effect-mongodb/FindCursor" 4 | import * as MongoClient from "effect-mongodb/MongoClient" 5 | import * as Arbitrary from "effect/Arbitrary" 6 | import * as Console from "effect/Console" 7 | import * as Effect from "effect/Effect" 8 | import * as FastCheck from "effect/FastCheck" 9 | import * as Schema from "effect/Schema" 10 | 11 | /** 12 | * Find with cursor builder 13 | * 14 | * Highlights: 15 | * At line 25, just like the MongoDB driver's `FindCursor`, you can use builder functions to customize the cursor options. 16 | */ 17 | const program = Effect.gen(function*() { 18 | const sourceInstance = yield* MongoClient.connectScoped("mongodb://localhost:27017") 19 | const sourceDb = MongoClient.db(sourceInstance, "find-with-cursor-builder") 20 | const sourceCollection = Db.collection(sourceDb, "records", MyType) 21 | const myTypes = FastCheck.sample(anyMyType, 100) 22 | yield* Collection.insertMany(sourceCollection, myTypes) 23 | 24 | const items = yield* Collection.find(sourceCollection).pipe( 25 | FindCursor.sort({ value: -1 }), 26 | FindCursor.limit(50), 27 | FindCursor.toArray 28 | ) 29 | 30 | yield* Console.log(items) 31 | }) 32 | 33 | const MyType = Schema.Struct({ 34 | value: Schema.Int 35 | }) 36 | type MyType = typeof MyType.Type 37 | 38 | const anyMyType = Arbitrary.make(MyType) 39 | 40 | await program.pipe(Effect.scoped, Effect.runPromise) 41 | -------------------------------------------------------------------------------- /packages/effect-mongodb/examples/find-without-cursor-builder.ts: -------------------------------------------------------------------------------- 1 | import * as Collection from "effect-mongodb/Collection" 2 | import * as Db from "effect-mongodb/Db" 3 | import * as FindCursor from "effect-mongodb/FindCursor" 4 | import * as MongoClient from "effect-mongodb/MongoClient" 5 | import * as Arbitrary from "effect/Arbitrary" 6 | import * as Console from "effect/Console" 7 | import * as Effect from "effect/Effect" 8 | import * as FastCheck from "effect/FastCheck" 9 | import * as Schema from "effect/Schema" 10 | 11 | /** 12 | * Find without cursor builder 13 | * 14 | * Highlights: 15 | * At line 25, just like the MongoDB driver's `collection.find`, 16 | * you can directly set the find options without relying on the `FindCursor` builder. 17 | */ 18 | const program = Effect.gen(function*() { 19 | const sourceInstance = yield* MongoClient.connectScoped("mongodb://localhost:27017") 20 | const sourceDb = MongoClient.db(sourceInstance, "find-without-cursor-builder") 21 | const sourceCollection = Db.collection(sourceDb, "records", MyType) 22 | const myTypes = FastCheck.sample(anyMyType, 100) 23 | yield* Collection.insertMany(sourceCollection, myTypes) 24 | 25 | const items = yield* Collection.find(sourceCollection, {}, { sort: { value: -1 }, limit: 50 }).pipe( 26 | FindCursor.toArray 27 | ) 28 | 29 | yield* Console.log(items) 30 | }) 31 | 32 | const MyType = Schema.Struct({ 33 | value: Schema.Int 34 | }) 35 | type MyType = typeof MyType.Type 36 | 37 | const anyMyType = Arbitrary.make(MyType) 38 | 39 | await program.pipe(Effect.scoped, Effect.runPromise) 40 | -------------------------------------------------------------------------------- /packages/effect-mongodb/examples/project.ts: -------------------------------------------------------------------------------- 1 | import * as Collection from "effect-mongodb/Collection" 2 | import * as Db from "effect-mongodb/Db" 3 | import * as FindCursor from "effect-mongodb/FindCursor" 4 | import * as MongoClient from "effect-mongodb/MongoClient" 5 | import * as Console from "effect/Console" 6 | import * as Effect from "effect/Effect" 7 | import * as Schema from "effect/Schema" 8 | 9 | /** 10 | * Project 11 | * 12 | * Highlights: 13 | * The `FindCursor.project` function requires a new schema as a parameter 14 | * because, in most cases, the projection result will differ from the original schema. 15 | */ 16 | const program = Effect.gen(function*() { 17 | const sourceInstance = yield* MongoClient.connectScoped("mongodb://localhost:27017") 18 | const sourceDb = MongoClient.db(sourceInstance, "project") 19 | const sourceCollection = Db.collection(sourceDb, "records", MyType) 20 | yield* Collection.insertMany(sourceCollection, [ 21 | { id: 1, values: [1, 20, 13] }, 22 | { id: 2, values: [4, 5] }, 23 | { id: 3, values: [1, 5, 33, 96] } 24 | ]) 25 | 26 | const items = yield* Collection.find(sourceCollection).pipe( 27 | FindCursor.project(MyTypeProjection, { 28 | id: 1, 29 | valuesCount: { $size: "$values" }, 30 | valuesMax: { $max: "$values" } 31 | }), 32 | FindCursor.toArray 33 | ) 34 | 35 | yield* Console.log(items) 36 | }) 37 | 38 | const MyType = Schema.Struct({ 39 | id: Schema.Int, 40 | values: Schema.Array(Schema.Int) 41 | }) 42 | 43 | const MyTypeProjection = Schema.Struct({ 44 | id: Schema.Int, 45 | valuesCount: Schema.Int, 46 | valuesMax: Schema.Int 47 | }) 48 | 49 | await program.pipe(Effect.scoped, Effect.runPromise) 50 | -------------------------------------------------------------------------------- /packages/effect-mongodb/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "effect-mongodb", 3 | "author": "doubleloop.io", 4 | "version": "0.3.1", 5 | "type": "module", 6 | "license": "MIT", 7 | "description": "A MongoDB toolkit for Effect", 8 | "homepage": "https://github.com/doubleloop-io/effect-mongodb", 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/doubleloop-io/effect-mongodb", 12 | "directory": "packages/effect-mongodb" 13 | }, 14 | "bugs": { 15 | "url": "https://github.com/doubleloop-io/effect-mongodb/issues" 16 | }, 17 | "tags": [ 18 | "typescript", 19 | "mongodb", 20 | "effect", 21 | "functional-programming" 22 | ], 23 | "keywords": [ 24 | "typescript", 25 | "mongodb", 26 | "effect", 27 | "functional-programming" 28 | ], 29 | "publishConfig": { 30 | "access": "public", 31 | "directory": "dist", 32 | "provenance": true 33 | }, 34 | "packageManager": "pnpm@9.4.0", 35 | "scripts": { 36 | "codegen": "build-utils prepare-v2", 37 | "build": "pnpm build-esm && pnpm build-annotate && pnpm build-cjs && build-utils pack-v2", 38 | "build-esm": "tsc -b tsconfig.build.json", 39 | "build-cjs": "babel build/esm --plugins @babel/transform-export-namespace-from --plugins @babel/transform-modules-commonjs --out-dir build/cjs --source-maps", 40 | "build-annotate": "babel build/esm --plugins annotate-pure-calls --out-dir build/esm --source-maps", 41 | "typecheck": "pnpm check", 42 | "typecheck:w": "pnpm check --watch", 43 | "dtslint": "dtslint dtslint", 44 | "check": "tsc -b tsconfig.json", 45 | "test": "vitest", 46 | "coverage": "vitest --coverage" 47 | }, 48 | "peerDependencies": { 49 | "effect": "^3.10.14", 50 | "mongodb": "^6.9.0" 51 | }, 52 | "devDependencies": { 53 | "effect": "^3.10.14", 54 | "mongodb": "^6.9.0", 55 | "@types/node": "^22.5.4" 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /packages/effect-mongodb/src/AggregationCursor.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 0.0.1 3 | */ 4 | import * as Data from "effect/Data" 5 | import * as Effect from "effect/Effect" 6 | import * as F from "effect/Function" 7 | import type * as ParseResult from "effect/ParseResult" 8 | import type { Pipeable } from "effect/Pipeable" 9 | import { pipeArguments } from "effect/Pipeable" 10 | import * as Schema from "effect/Schema" 11 | import * as Stream from "effect/Stream" 12 | import type { AggregationCursor as MongoAggregationCursor } from "mongodb" 13 | import { mongoErrorOrDie } from "./internal/mongo-error.js" 14 | import * as MongoError from "./MongoError.js" 15 | 16 | interface AggregationCursorFields { 17 | cursor: MongoAggregationCursor 18 | schema: Schema.Schema 19 | } 20 | 21 | export interface AggregationCursor extends AggregationCursorFields, Pipeable { 22 | _tag: "AggregationCursor" 23 | } 24 | 25 | /** @internal */ 26 | export class AggregationCursorImpl 27 | extends Data.TaggedClass("AggregationCursor")> 28 | implements AggregationCursor 29 | { 30 | pipe() { 31 | return pipeArguments(this, arguments) 32 | } 33 | } 34 | 35 | export const toArray = ( 36 | cursor: AggregationCursor 37 | ): Effect.Effect, MongoError.MongoError | ParseResult.ParseError, R> => { 38 | const decode = Schema.decodeUnknown(cursor.schema) 39 | return Effect.promise(() => cursor.cursor.toArray()).pipe( 40 | Effect.catchAllDefect(mongoErrorOrDie(errorSource(cursor, "toArray"))), 41 | Effect.flatMap(Effect.forEach((x) => decode(x))) 42 | ) 43 | } 44 | 45 | export const toStream = ( 46 | cursor: AggregationCursor 47 | ): Stream.Stream => { 48 | const decode = Schema.decodeUnknown(cursor.schema) 49 | return F.pipe( 50 | Stream.fromAsyncIterable(cursor.cursor, F.identity), 51 | Stream.catchAll(mongoErrorOrDie(errorSource(cursor, "toStream"))), 52 | Stream.mapEffect((x) => decode(x)) 53 | ) 54 | } 55 | 56 | const errorSource = (cursor: AggregationCursor, functionName: string) => 57 | new MongoError.CollectionErrorSource({ 58 | module: AggregationCursorImpl.name, 59 | functionName, 60 | db: cursor.cursor.namespace.db, 61 | collection: cursor.cursor.namespace.collection ?? "NO_COLLECTION_NAME" 62 | }) 63 | -------------------------------------------------------------------------------- /packages/effect-mongodb/src/Db.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 0.0.1 3 | */ 4 | import * as Data from "effect/Data" 5 | import * as Effect from "effect/Effect" 6 | import * as F from "effect/Function" 7 | import type * as Schema from "effect/Schema" 8 | import type { Db as Db_, Document, DropCollectionOptions, ListCollectionsOptions } from "mongodb" 9 | import type * as Collection from "./Collection.js" 10 | import * as DocumentCollection from "./DocumentCollection.js" 11 | import { mongoErrorOrDie } from "./internal/mongo-error.js" 12 | import * as ListCollectionsCursor from "./ListCollectionsCursor.js" 13 | import * as MongoError from "./MongoError.js" 14 | 15 | export class Db extends Data.TaggedClass("Db")<{ db: Db_ }> {} 16 | 17 | export const documentCollection: { 18 | (name: string): (db: Db) => DocumentCollection.DocumentCollection 19 | (db: Db, name: string): DocumentCollection.DocumentCollection 20 | } = F.dual( 21 | (args) => isDb(args[0]), 22 | (db: Db, name: string): DocumentCollection.DocumentCollection => 23 | new DocumentCollection.DocumentCollectionImpl({ 24 | collection: db.db.collection(name) 25 | }) 26 | ) 27 | 28 | export const collection: { 29 | ( 30 | name: string, 31 | schema: Schema.Schema 32 | ): (db: Db) => Collection.Collection 33 | ( 34 | db: Db, 35 | name: string, 36 | schema: Schema.Schema 37 | ): Collection.Collection 38 | } = F.dual( 39 | (args) => isDb(args[0]), 40 | ( 41 | db: Db, 42 | name: string, 43 | schema: Schema.Schema 44 | ): Collection.Collection => DocumentCollection.typed(documentCollection(db, name), schema) 45 | ) 46 | 47 | export const listCollections: { 48 | (db: Db): ListCollectionsCursor.ListCollectionsCursor 49 | (filter: Document): (db: Db) => ListCollectionsCursor.ListCollectionsCursor 50 | ( 51 | filter: Document, 52 | options: Exclude & { nameOnly: true } 53 | ): (db: Db) => ListCollectionsCursor.NameOnlyListCollectionsCursor 54 | ( 55 | filter: Document, 56 | options: Exclude & { nameOnly: false } 57 | ): (db: Db) => ListCollectionsCursor.FullListCollectionsCursor 58 | (db: Db, filter: Document): ListCollectionsCursor.ListCollectionsCursor 59 | ( 60 | db: Db, 61 | filter: Document, 62 | options: Exclude & { nameOnly: true } 63 | ): ListCollectionsCursor.NameOnlyListCollectionsCursor 64 | ( 65 | db: Db, 66 | filter: Document, 67 | options: Exclude & { nameOnly: false } 68 | ): ListCollectionsCursor.FullListCollectionsCursor 69 | } = F.dual( 70 | (args) => isDb(args[0]), 71 | (db: Db, filter?: Document, options?: ListCollectionsOptions): ListCollectionsCursor.ListCollectionsCursor => 72 | new ListCollectionsCursor.ListCollectionsCursorImpl({ cursor: db.db.listCollections(filter, options) }) 73 | ) 74 | 75 | export const dropCollection: { 76 | (name: string): (db: Db) => Effect.Effect 77 | (name: string, options: DropCollectionOptions): (db: Db) => Effect.Effect 78 | (db: Db, name: string): Effect.Effect 79 | (db: Db, name: string, options: DropCollectionOptions): Effect.Effect 80 | } = F.dual( 81 | (args) => isDb(args[0]), 82 | (db: Db, name: string, options?: DropCollectionOptions): Effect.Effect => 83 | F.pipe( 84 | Effect.promise(() => db.db.dropCollection(name, options)), 85 | Effect.catchAllDefect(mongoErrorOrDie(errorSource(db, "dropCollection"))) 86 | ) 87 | ) 88 | 89 | const isDb = (x: unknown) => x instanceof Db 90 | 91 | const errorSource = (db: Db, functionName: string) => 92 | new MongoError.DbErrorSource({ module: "Db", functionName, db: db.db.databaseName }) 93 | -------------------------------------------------------------------------------- /packages/effect-mongodb/src/DocumentAggregationCursor.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 0.0.1 3 | */ 4 | import * as Data from "effect/Data" 5 | import * as Effect from "effect/Effect" 6 | import * as F from "effect/Function" 7 | import type { Pipeable } from "effect/Pipeable" 8 | import { pipeArguments } from "effect/Pipeable" 9 | import * as Stream from "effect/Stream" 10 | import type { AggregationCursor as MongoAggregationCursor, Document } from "mongodb" 11 | import { mongoErrorOrDie } from "./internal/mongo-error.js" 12 | import * as MongoError from "./MongoError.js" 13 | 14 | type DocumentAggregationCursorFields = { cursor: MongoAggregationCursor } 15 | 16 | export interface DocumentAggregationCursor extends DocumentAggregationCursorFields, Pipeable { 17 | _tag: "DocumentAggregationCursor" 18 | } 19 | 20 | /** @internal */ 21 | export class DocumentAggregationCursorImpl 22 | extends Data.TaggedClass("DocumentAggregationCursor") 23 | implements DocumentAggregationCursor 24 | { 25 | pipe() { 26 | return pipeArguments(this, arguments) 27 | } 28 | } 29 | 30 | export const toArray = ( 31 | cursor: DocumentAggregationCursor 32 | ): Effect.Effect, MongoError.MongoError> => 33 | F.pipe( 34 | Effect.promise(() => cursor.cursor.toArray()), 35 | Effect.catchAllDefect(mongoErrorOrDie(errorSource(cursor, "toArray"))) 36 | ) 37 | 38 | export const toStream = ( 39 | cursor: DocumentAggregationCursor 40 | ): Stream.Stream => 41 | F.pipe( 42 | Stream.fromAsyncIterable(cursor.cursor, F.identity), 43 | Stream.catchAll(mongoErrorOrDie(errorSource(cursor, "toStream"))) 44 | ) 45 | 46 | const errorSource = (cursor: DocumentAggregationCursor, functionName: string) => 47 | new MongoError.CollectionErrorSource({ 48 | module: DocumentAggregationCursorImpl.name, 49 | functionName, 50 | db: cursor.cursor.namespace.db, 51 | collection: cursor.cursor.namespace.collection ?? "NO_COLLECTION_NAME" 52 | }) 53 | -------------------------------------------------------------------------------- /packages/effect-mongodb/src/DocumentCollection.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 0.0.1 3 | */ 4 | import * as Array from "effect/Array" 5 | import * as Data from "effect/Data" 6 | import * as Effect from "effect/Effect" 7 | import * as F from "effect/Function" 8 | import * as O from "effect/Option" 9 | import type { Pipeable } from "effect/Pipeable" 10 | import { pipeArguments } from "effect/Pipeable" 11 | import type * as Schema from "effect/Schema" 12 | import type { 13 | AggregateOptions, 14 | BulkWriteOptions, 15 | Collection as MongoCollection, 16 | CountDocumentsOptions, 17 | CreateIndexesOptions, 18 | DeleteOptions, 19 | DeleteResult, 20 | Document, 21 | DropCollectionOptions, 22 | DropIndexesOptions, 23 | EstimatedDocumentCountOptions, 24 | Filter, 25 | FindOneAndReplaceOptions, 26 | FindOptions, 27 | IndexDescription, 28 | IndexSpecification, 29 | InsertManyResult, 30 | InsertOneOptions, 31 | InsertOneResult, 32 | ModifyResult as MongoModifyResult, 33 | OptionalUnlessRequiredId, 34 | RenameOptions, 35 | ReplaceOptions, 36 | UpdateFilter, 37 | UpdateOptions, 38 | UpdateResult, 39 | WithoutId 40 | } from "mongodb" 41 | import * as Collection from "./Collection.js" 42 | import * as DocumentAggregationCursor from "./DocumentAggregationCursor.js" 43 | import * as DocumentFindCursor from "./DocumentFindCursor.js" 44 | import type { ModifyResult } from "./internal/modify-result.js" 45 | import { mongoErrorOrDie } from "./internal/mongo-error.js" 46 | import * as MongoError from "./MongoError.js" 47 | 48 | type DocumentCollectionFields = { 49 | collection: MongoCollection 50 | } 51 | 52 | export interface DocumentCollection extends DocumentCollectionFields, Pipeable { 53 | _tag: "DocumentCollection" 54 | } 55 | 56 | /** @internal */ 57 | export class DocumentCollectionImpl extends Data.TaggedClass("DocumentCollection") 58 | implements DocumentCollection 59 | { 60 | pipe() { 61 | return pipeArguments(this, arguments) 62 | } 63 | } 64 | 65 | export const find: { 66 | ( 67 | filter?: Filter, 68 | options?: FindOptions 69 | ): (collection: DocumentCollection) => DocumentFindCursor.DocumentFindCursor 70 | ( 71 | collection: DocumentCollection, 72 | filter?: Filter, 73 | options?: FindOptions 74 | ): DocumentFindCursor.DocumentFindCursor 75 | } = F.dual( 76 | (args) => isDocumentCollection(args[0]), 77 | (collection: DocumentCollection, filter?: Filter, options?: FindOptions) => 78 | new DocumentFindCursor.DocumentFindCursorImpl( 79 | { 80 | cursor: collection.collection.find(filter ?? {}, options) 81 | } 82 | ) 83 | ) 84 | 85 | export const findOne: { 86 | (filter: Filter, options?: FindOptions): ( 87 | collection: DocumentCollection 88 | ) => Effect.Effect, MongoError.MongoError> 89 | ( 90 | collection: DocumentCollection, 91 | filter: Filter, 92 | options?: FindOptions 93 | ): Effect.Effect, MongoError.MongoError> 94 | } = F.dual( 95 | (args) => isDocumentCollection(args[0]), 96 | ( 97 | collection: DocumentCollection, 98 | filter: Filter, 99 | options?: FindOptions 100 | ): Effect.Effect, MongoError.MongoError> => 101 | F.pipe( 102 | Effect.promise(() => collection.collection.findOne(filter, options)), 103 | Effect.map((value) => O.fromNullable(value)), 104 | Effect.catchAllDefect(mongoErrorOrDie(errorSource(collection, "findOne"))) 105 | ) 106 | ) 107 | 108 | export const insertOne: { 109 | ( 110 | collection: DocumentCollection, 111 | doc: OptionalUnlessRequiredId 112 | ): Effect.Effect 113 | (doc: OptionalUnlessRequiredId, options?: InsertOneOptions): ( 114 | collection: DocumentCollection 115 | ) => Effect.Effect 116 | ( 117 | collection: DocumentCollection, 118 | doc: OptionalUnlessRequiredId, 119 | options?: InsertOneOptions 120 | ): Effect.Effect 121 | } = F.dual((args) => isDocumentCollection(args[0]), ( 122 | collection: DocumentCollection, 123 | doc: OptionalUnlessRequiredId, 124 | options?: InsertOneOptions 125 | ): Effect.Effect => 126 | F.pipe( 127 | Effect.promise(() => collection.collection.insertOne(doc, options)), 128 | Effect.catchAllDefect(mongoErrorOrDie(errorSource(collection, "insertOne"))) 129 | )) 130 | 131 | export const insertMany: { 132 | ( 133 | docs: ReadonlyArray>, 134 | options?: BulkWriteOptions 135 | ): ( 136 | collection: DocumentCollection 137 | ) => Effect.Effect 138 | ( 139 | collection: DocumentCollection, 140 | docs: ReadonlyArray>, 141 | options?: BulkWriteOptions 142 | ): Effect.Effect 143 | } = F.dual((args) => isDocumentCollection(args[0]), ( 144 | collection: DocumentCollection, 145 | docs: ReadonlyArray>, 146 | options?: BulkWriteOptions 147 | ): Effect.Effect => 148 | F.pipe( 149 | Effect.promise(() => collection.collection.insertMany(docs, options)), 150 | Effect.catchAllDefect(mongoErrorOrDie(errorSource(collection, "insertMany"))) 151 | )) 152 | 153 | export const deleteOne: { 154 | (filter: Filter, options?: DeleteOptions): ( 155 | collection: DocumentCollection 156 | ) => Effect.Effect 157 | ( 158 | collection: DocumentCollection, 159 | filter: Filter, 160 | options?: DeleteOptions 161 | ): Effect.Effect 162 | } = F.dual( 163 | (args) => isDocumentCollection(args[0]), 164 | ( 165 | collection: DocumentCollection, 166 | filter: Filter, 167 | options?: DeleteOptions 168 | ): Effect.Effect => 169 | F.pipe( 170 | Effect.promise(() => collection.collection.deleteOne(filter, options)), 171 | Effect.catchAllDefect(mongoErrorOrDie(errorSource(collection, "deleteOne"))) 172 | ) 173 | ) 174 | 175 | export const deleteMany: { 176 | (filter: Filter, options?: DeleteOptions): ( 177 | collection: DocumentCollection 178 | ) => Effect.Effect 179 | ( 180 | collection: DocumentCollection, 181 | filter: Filter, 182 | options?: DeleteOptions 183 | ): Effect.Effect 184 | } = F.dual( 185 | (args) => isDocumentCollection(args[0]), 186 | ( 187 | collection: DocumentCollection, 188 | filter: Filter, 189 | options?: DeleteOptions 190 | ): Effect.Effect => 191 | F.pipe( 192 | Effect.promise(() => collection.collection.deleteMany(filter, options)), 193 | Effect.catchAllDefect(mongoErrorOrDie(errorSource(collection, "deleteMany"))) 194 | ) 195 | ) 196 | 197 | export const updateMany: { 198 | ( 199 | filter: Filter, 200 | update: UpdateFilter | ReadonlyArray, 201 | options?: UpdateOptions 202 | ): ( 203 | collection: DocumentCollection 204 | ) => Effect.Effect 205 | ( 206 | collection: DocumentCollection, 207 | filter: Filter, 208 | update: UpdateFilter | ReadonlyArray, 209 | options?: UpdateOptions 210 | ): Effect.Effect 211 | } = F.dual( 212 | (args) => isDocumentCollection(args[0]), 213 | ( 214 | collection: DocumentCollection, 215 | filter: Filter, 216 | update: UpdateFilter | ReadonlyArray, 217 | options?: UpdateOptions 218 | ): Effect.Effect => 219 | F.pipe( 220 | Effect.promise(() => 221 | collection.collection.updateMany(filter, Array.isArray(update) ? [...update] : update, options) 222 | ), 223 | Effect.catchAllDefect(mongoErrorOrDie(errorSource(collection, "updateMany"))) 224 | ) 225 | ) 226 | 227 | export const replaceOne: { 228 | ( 229 | filter: Filter, 230 | replacement: WithoutId, 231 | options?: ReplaceOptions 232 | ): ( 233 | collection: DocumentCollection 234 | ) => Effect.Effect 235 | ( 236 | collection: DocumentCollection, 237 | filter: Filter, 238 | replacement: WithoutId, 239 | options?: ReplaceOptions 240 | ): Effect.Effect 241 | } = F.dual( 242 | (args) => isDocumentCollection(args[0]), 243 | ( 244 | collection: DocumentCollection, 245 | filter: Filter, 246 | replacement: WithoutId, 247 | options?: ReplaceOptions 248 | ): Effect.Effect => 249 | F.pipe( 250 | Effect.promise(() => collection.collection.replaceOne(filter, replacement, options)), 251 | Effect.catchAllDefect(mongoErrorOrDie(errorSource(collection, "replaceOne"))) 252 | ) 253 | ) 254 | 255 | export const findOneAndReplace: { 256 | ( 257 | filter: Filter, 258 | replacement: WithoutId, 259 | options: FindOneAndReplaceOptions & { includeResultMetadata: true } 260 | ): ( 261 | collection: DocumentCollection 262 | ) => Effect.Effect, MongoError.MongoError> 263 | ( 264 | filter: Filter, 265 | replacement: WithoutId, 266 | options: FindOneAndReplaceOptions & { includeResultMetadata: false } 267 | ): ( 268 | collection: DocumentCollection 269 | ) => Effect.Effect, MongoError.MongoError> 270 | ( 271 | filter: Filter, 272 | replacement: WithoutId, 273 | options: FindOneAndReplaceOptions 274 | ): ( 275 | collection: DocumentCollection 276 | ) => Effect.Effect, MongoError.MongoError> 277 | (filter: Filter, replacement: WithoutId): ( 278 | collection: DocumentCollection 279 | ) => Effect.Effect, MongoError.MongoError> 280 | ( 281 | collection: DocumentCollection, 282 | filter: Filter, 283 | replacement: WithoutId, 284 | options: FindOneAndReplaceOptions & { includeResultMetadata: true } 285 | ): Effect.Effect, MongoError.MongoError> 286 | ( 287 | collection: DocumentCollection, 288 | filter: Filter, 289 | replacement: WithoutId, 290 | options: FindOneAndReplaceOptions & { includeResultMetadata: false } 291 | ): Effect.Effect, MongoError.MongoError> 292 | ( 293 | collection: DocumentCollection, 294 | filter: Filter, 295 | replacement: WithoutId, 296 | options: FindOneAndReplaceOptions 297 | ): Effect.Effect, MongoError.MongoError> 298 | ( 299 | collection: DocumentCollection, 300 | filter: Filter, 301 | replacement: WithoutId 302 | ): Effect.Effect, MongoError.MongoError> 303 | } = F.dual( 304 | (args) => isDocumentCollection(args[0]), 305 | ( 306 | collection: DocumentCollection, 307 | filter: Filter, 308 | replacement: WithoutId, 309 | options?: FindOneAndReplaceOptions 310 | ): Effect.Effect | ModifyResult, MongoError.MongoError> => 311 | F.pipe( 312 | Effect.promise(() => collection.collection.findOneAndReplace(filter, replacement, options ?? {})), 313 | Effect.map((value) => { 314 | if (options?.includeResultMetadata && !!value) { 315 | const result = value as unknown as MongoModifyResult 316 | return { ...result, value: O.fromNullable(result.value) } 317 | } 318 | return O.fromNullable(value) 319 | }), 320 | Effect.catchAllDefect( 321 | mongoErrorOrDie(errorSource(collection, "findOneAndReplace")) 322 | ) 323 | ) 324 | ) 325 | 326 | export const rename: { 327 | (newName: string, options?: RenameOptions): ( 328 | collection: DocumentCollection 329 | ) => Effect.Effect 330 | ( 331 | collection: DocumentCollection, 332 | newName: string, 333 | options?: RenameOptions 334 | ): Effect.Effect 335 | } = F.dual( 336 | (args) => isDocumentCollection(args[0]), 337 | ( 338 | collection: DocumentCollection, 339 | newName: string, 340 | options?: RenameOptions 341 | ): Effect.Effect => 342 | F.pipe( 343 | Effect.promise(() => collection.collection.rename(newName, options)), 344 | Effect.map((collection) => new DocumentCollectionImpl({ collection })), 345 | Effect.catchAllDefect(mongoErrorOrDie(errorSource(collection, "rename"))) 346 | ) 347 | ) 348 | 349 | export const drop: { 350 | (options?: DropCollectionOptions): (collection: DocumentCollection) => Effect.Effect 351 | (collection: DocumentCollection, options?: DropCollectionOptions): Effect.Effect 352 | } = F.dual( 353 | (args) => isDocumentCollection(args[0]), 354 | ( 355 | collection: DocumentCollection, 356 | options?: DropCollectionOptions 357 | ): Effect.Effect => 358 | F.pipe( 359 | Effect.promise(() => collection.collection.drop(options)), 360 | Effect.catchAllDefect(mongoErrorOrDie(errorSource(collection, "drop"))) 361 | ) 362 | ) 363 | 364 | export const createIndexes: { 365 | (indexSpecs: ReadonlyArray, options?: CreateIndexesOptions): ( 366 | collection: DocumentCollection 367 | ) => Effect.Effect, MongoError.MongoError> 368 | ( 369 | collection: DocumentCollection, 370 | indexSpecs: ReadonlyArray, 371 | options?: CreateIndexesOptions 372 | ): Effect.Effect, MongoError.MongoError> 373 | } = F.dual( 374 | (args) => isDocumentCollection(args[0]), 375 | ( 376 | collection: DocumentCollection, 377 | indexSpecs: ReadonlyArray, 378 | options?: CreateIndexesOptions 379 | ): Effect.Effect, MongoError.MongoError> => 380 | F.pipe( 381 | Effect.promise(() => collection.collection.createIndexes([...indexSpecs], options)), 382 | Effect.catchAllDefect(mongoErrorOrDie(errorSource(collection, "createIndexes"))) 383 | ) 384 | ) 385 | 386 | export const createIndex: { 387 | ( 388 | indexSpec: IndexSpecification, 389 | options?: CreateIndexesOptions 390 | ): (collection: DocumentCollection) => Effect.Effect 391 | ( 392 | collection: DocumentCollection, 393 | indexSpec: IndexSpecification, 394 | options?: CreateIndexesOptions 395 | ): Effect.Effect 396 | } = F.dual( 397 | (args) => isDocumentCollection(args[0]), 398 | ( 399 | collection: DocumentCollection, 400 | indexSpec: IndexSpecification, 401 | options?: CreateIndexesOptions 402 | ): Effect.Effect => 403 | F.pipe( 404 | Effect.promise(() => collection.collection.createIndex(indexSpec, options)), 405 | Effect.catchAllDefect(mongoErrorOrDie(errorSource(collection, "createIndex"))) 406 | ) 407 | ) 408 | 409 | export const dropIndex: { 410 | ( 411 | indexName: string, 412 | options?: DropIndexesOptions 413 | ): (collection: DocumentCollection) => Effect.Effect 414 | ( 415 | collection: DocumentCollection, 416 | indexName: string, 417 | options?: DropIndexesOptions 418 | ): Effect.Effect 419 | } = F.dual( 420 | (args) => isDocumentCollection(args[0]), 421 | ( 422 | collection: DocumentCollection, 423 | indexName: string, 424 | options?: DropIndexesOptions 425 | ): Effect.Effect => 426 | F.pipe( 427 | Effect.promise(() => collection.collection.dropIndex(indexName, options)), 428 | Effect.catchAllDefect(mongoErrorOrDie(errorSource(collection, "dropIndex"))) 429 | ) 430 | ) 431 | 432 | export const aggregate: { 433 | (pipeline?: ReadonlyArray, options?: AggregateOptions): ( 434 | collection: DocumentCollection 435 | ) => DocumentAggregationCursor.DocumentAggregationCursor 436 | ( 437 | collection: DocumentCollection, 438 | pipeline?: ReadonlyArray, 439 | options?: AggregateOptions 440 | ): DocumentAggregationCursor.DocumentAggregationCursor 441 | } = F.dual( 442 | (args) => isDocumentCollection(args[0]), 443 | ( 444 | collection: DocumentCollection, 445 | pipeline?: ReadonlyArray, 446 | options?: AggregateOptions 447 | ): DocumentAggregationCursor.DocumentAggregationCursor => 448 | new DocumentAggregationCursor.DocumentAggregationCursorImpl({ 449 | cursor: collection.collection.aggregate(pipeline ? [...pipeline] : undefined, options) 450 | }) 451 | ) 452 | 453 | export const estimatedDocumentCount: { 454 | (options?: EstimatedDocumentCountOptions): ( 455 | collection: DocumentCollection 456 | ) => Effect.Effect 457 | ( 458 | collection: DocumentCollection, 459 | options?: EstimatedDocumentCountOptions 460 | ): Effect.Effect 461 | } = F.dual( 462 | (args) => isDocumentCollection(args[0]), 463 | ( 464 | collection: DocumentCollection, 465 | options?: EstimatedDocumentCountOptions 466 | ): Effect.Effect => 467 | F.pipe( 468 | Effect.promise(() => collection.collection.estimatedDocumentCount(options)), 469 | Effect.catchAllDefect(mongoErrorOrDie(errorSource(collection, "estimatedDocumentCount"))) 470 | ) 471 | ) 472 | 473 | export const countDocuments: { 474 | (filter?: Filter, options?: CountDocumentsOptions): ( 475 | collection: DocumentCollection 476 | ) => Effect.Effect 477 | ( 478 | collection: DocumentCollection, 479 | filter?: Filter, 480 | options?: CountDocumentsOptions 481 | ): Effect.Effect 482 | } = F.dual( 483 | (args) => isDocumentCollection(args[0]), 484 | ( 485 | collection: DocumentCollection, 486 | filter?: Filter, 487 | options?: CountDocumentsOptions 488 | ): Effect.Effect => 489 | F.pipe( 490 | Effect.promise(() => collection.collection.countDocuments(filter, options)), 491 | Effect.catchAllDefect(mongoErrorOrDie(errorSource(collection, "countDocuments"))) 492 | ) 493 | ) 494 | 495 | export const typed: { 496 | ( 497 | schema: Schema.Schema 498 | ): (collection: DocumentCollection) => Collection.Collection 499 | ( 500 | collection: DocumentCollection, 501 | schema: Schema.Schema 502 | ): Collection.Collection 503 | } = F.dual((args) => isDocumentCollection(args[0]), ( 504 | collection: DocumentCollection, 505 | schema: Schema.Schema 506 | ): Collection.Collection => 507 | new Collection.CollectionImpl({ collection: collection.collection, schema })) 508 | 509 | const isDocumentCollection = (x: unknown) => x instanceof DocumentCollectionImpl 510 | 511 | const errorSource = ( 512 | collection: DocumentCollection, 513 | functionName: string 514 | ) => 515 | new MongoError.CollectionErrorSource({ 516 | module: DocumentCollectionImpl.name, 517 | functionName, 518 | db: collection.collection.dbName, 519 | collection: collection.collection.collectionName 520 | }) 521 | -------------------------------------------------------------------------------- /packages/effect-mongodb/src/DocumentFindCursor.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 0.0.1 3 | */ 4 | import * as Data from "effect/Data" 5 | import * as Effect from "effect/Effect" 6 | import * as F from "effect/Function" 7 | import type { Pipeable } from "effect/Pipeable" 8 | import { pipeArguments } from "effect/Pipeable" 9 | import type * as Schema from "effect/Schema" 10 | import * as Stream from "effect/Stream" 11 | import type { Document, Filter, FindCursor as MongoFindCursor, Sort, SortDirection } from "mongodb" 12 | import * as FindCursor from "./FindCursor.js" 13 | import { mongoErrorOrDie } from "./internal/mongo-error.js" 14 | import * as MongoError from "./MongoError.js" 15 | 16 | type DocumentFindCursorFields = { 17 | cursor: MongoFindCursor 18 | } 19 | 20 | export interface DocumentFindCursor extends DocumentFindCursorFields, Pipeable { 21 | _tag: "DocumentFindCursor" 22 | } 23 | 24 | /** @internal */ 25 | export class DocumentFindCursorImpl extends Data.TaggedClass("DocumentFindCursor") 26 | implements DocumentFindCursor 27 | { 28 | pipe() { 29 | return pipeArguments(this, arguments) 30 | } 31 | } 32 | 33 | export const filter: { 34 | (filter: Filter): (cursor: DocumentFindCursor) => DocumentFindCursor 35 | (cursor: DocumentFindCursor, filter: Filter): DocumentFindCursor 36 | } = F.dual( 37 | (args) => isDocumentFindCursor(args[0]), 38 | (cursor: DocumentFindCursor, filter: Filter): DocumentFindCursor => 39 | new DocumentFindCursorImpl({ cursor: cursor.cursor.filter(filter) }) 40 | ) 41 | 42 | export const project: { 43 | (value: Document): (cursor: DocumentFindCursor) => DocumentFindCursor 44 | (cursor: DocumentFindCursor, value: Document): DocumentFindCursor 45 | } = F.dual( 46 | (args) => isDocumentFindCursor(args[0]), 47 | (cursor: DocumentFindCursor, value: Document): DocumentFindCursor => 48 | new DocumentFindCursorImpl({ cursor: cursor.cursor.project(value) }) 49 | ) 50 | 51 | export const sort: { 52 | (sort: Sort | string, direction?: SortDirection): (cursor: DocumentFindCursor) => DocumentFindCursor 53 | (cursor: DocumentFindCursor, sort: Sort | string, direction?: SortDirection): DocumentFindCursor 54 | } = F.dual( 55 | (args) => isDocumentFindCursor(args[0]), 56 | (cursor: DocumentFindCursor, sort: Sort | string, direction?: SortDirection): DocumentFindCursor => 57 | new DocumentFindCursorImpl({ cursor: cursor.cursor.sort(sort, direction) }) 58 | ) 59 | 60 | export const limit: { 61 | (value: number): (cursor: DocumentFindCursor) => DocumentFindCursor 62 | (cursor: DocumentFindCursor, value: number): DocumentFindCursor 63 | } = F.dual( 64 | (args) => isDocumentFindCursor(args[0]), 65 | (cursor: DocumentFindCursor, value: number): DocumentFindCursor => 66 | new DocumentFindCursorImpl({ cursor: cursor.cursor.limit(value) }) 67 | ) 68 | 69 | export const toArray = (cursor: DocumentFindCursor): Effect.Effect, MongoError.MongoError> => 70 | F.pipe( 71 | Effect.promise(() => cursor.cursor.toArray()), 72 | Effect.catchAllDefect(mongoErrorOrDie(errorSource(cursor, "toArray"))) 73 | ) 74 | 75 | export const toStream = ( 76 | cursor: DocumentFindCursor 77 | ): Stream.Stream => 78 | F.pipe( 79 | Stream.fromAsyncIterable(cursor.cursor, F.identity), 80 | Stream.catchAll(mongoErrorOrDie(errorSource(cursor, "toStream"))) 81 | ) 82 | 83 | export const typed: { 84 | (schema: Schema.Schema): (cursor: DocumentFindCursor) => FindCursor.FindCursor 85 | (cursor: DocumentFindCursor, schema: Schema.Schema): FindCursor.FindCursor 86 | } = F.dual((args) => isDocumentFindCursor(args[0]), ( 87 | cursor: DocumentFindCursor, 88 | schema: Schema.Schema 89 | ): FindCursor.FindCursor => new FindCursor.FindCursorImpl({ cursor: cursor.cursor, schema })) 90 | 91 | const isDocumentFindCursor = (x: unknown) => x instanceof DocumentFindCursorImpl 92 | 93 | const errorSource = (cursor: DocumentFindCursor, functionName: string) => 94 | new MongoError.CollectionErrorSource({ 95 | module: DocumentFindCursorImpl.name, 96 | functionName, 97 | db: cursor.cursor.namespace.db, 98 | collection: cursor.cursor.namespace.collection ?? "NO_COLLECTION_NAME" 99 | }) 100 | -------------------------------------------------------------------------------- /packages/effect-mongodb/src/FindCursor.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 0.0.1 3 | */ 4 | import * as Data from "effect/Data" 5 | import * as Effect from "effect/Effect" 6 | import type * as E from "effect/Either" 7 | import * as F from "effect/Function" 8 | import type * as ParseResult from "effect/ParseResult" 9 | import type { Pipeable } from "effect/Pipeable" 10 | import { pipeArguments } from "effect/Pipeable" 11 | import * as Schema from "effect/Schema" 12 | import * as Stream from "effect/Stream" 13 | import * as Tuple from "effect/Tuple" 14 | import type { Document, FindCursor as FindCursor_, Sort, SortDirection } from "mongodb" 15 | import type { Filter as Filter_ } from "./internal/filter.js" 16 | import { mongoErrorOrDie } from "./internal/mongo-error.js" 17 | import * as MongoError from "./MongoError.js" 18 | 19 | type FindCursorFields = { 20 | cursor: FindCursor_ 21 | schema: Schema.Schema 22 | } 23 | 24 | export interface FindCursor extends FindCursorFields, Pipeable { 25 | _tag: "FindCursor" 26 | } 27 | 28 | /** @internal */ 29 | export class FindCursorImpl extends Data.TaggedClass("FindCursor")> 30 | implements FindCursor 31 | { 32 | pipe() { 33 | return pipeArguments(this, arguments) 34 | } 35 | } 36 | 37 | export type Filter = Filter_ 38 | 39 | export const filter: { 40 | (filter: Filter): (cursor: FindCursor) => FindCursor 41 | (cursor: FindCursor, filter: Filter): FindCursor 42 | } = F.dual( 43 | (args) => isFindCursor(args[0]), 44 | ( 45 | cursor: FindCursor, 46 | filter: Filter 47 | ): FindCursor => new FindCursorImpl({ cursor: cursor.cursor.filter(filter), schema: cursor.schema }) 48 | ) 49 | 50 | export const project: { 51 | ( 52 | newSchema: Schema.Schema, 53 | value: T 54 | ): (cursor: FindCursor) => FindCursor 55 | ( 56 | cursor: FindCursor, 57 | newSchema: Schema.Schema, 58 | value: T 59 | ): FindCursor 60 | } = F.dual( 61 | (args) => isFindCursor(args[0]), 62 | ( 63 | cursor: FindCursor, 64 | newSchema: Schema.Schema, 65 | value: T 66 | ): FindCursor => new FindCursorImpl({ cursor: cursor.cursor.project(value), schema: newSchema }) 67 | ) 68 | 69 | export const sort: { 70 | (sort: Sort | string, direction?: SortDirection): (cursor: FindCursor) => FindCursor 71 | ( 72 | cursor: FindCursor, 73 | sort: Sort | string, 74 | direction?: SortDirection 75 | ): FindCursor 76 | } = F.dual( 77 | (args) => isFindCursor(args[0]), 78 | ( 79 | cursor: FindCursor, 80 | sort: Sort | string, 81 | direction?: SortDirection 82 | ): FindCursor => new FindCursorImpl({ cursor: cursor.cursor.sort(sort, direction), schema: cursor.schema }) 83 | ) 84 | 85 | export const limit: { 86 | (value: number): (cursor: FindCursor) => FindCursor 87 | (cursor: FindCursor, value: number): FindCursor 88 | } = F.dual( 89 | (args) => isFindCursor(args[0]), 90 | (cursor: FindCursor, value: number): FindCursor => 91 | new FindCursorImpl({ cursor: cursor.cursor.limit(value), schema: cursor.schema }) 92 | ) 93 | 94 | export const toArray = ( 95 | cursor: FindCursor 96 | ): Effect.Effect, MongoError.MongoError | ParseResult.ParseError, R> => { 97 | const decode = Schema.decodeUnknown(cursor.schema) 98 | return Effect.promise(() => cursor.cursor.toArray()).pipe( 99 | Effect.catchAllDefect(mongoErrorOrDie(errorSource(cursor, "toArray"))), 100 | Effect.flatMap(Effect.forEach((x) => decode(x))) 101 | ) 102 | } 103 | 104 | export const toArrayEither = ( 105 | cursor: FindCursor 106 | ): Effect.Effect< 107 | Array>, 108 | MongoError.MongoError, 109 | R 110 | > => { 111 | const decode = Schema.decodeUnknown(cursor.schema) 112 | return Effect.promise(() => cursor.cursor.toArray()).pipe( 113 | Effect.catchAllDefect(mongoErrorOrDie(errorSource(cursor, "toArrayEither"))), 114 | Effect.flatMap(Effect.forEach((x) => 115 | F.pipe( 116 | decode(x), 117 | Effect.mapError((error) => Tuple.make(x, error)), 118 | Effect.either 119 | ) 120 | )) 121 | ) 122 | } 123 | 124 | export const toStream = ( 125 | cursor: FindCursor 126 | ): Stream.Stream => { 127 | const decode = Schema.decodeUnknown(cursor.schema) 128 | return F.pipe( 129 | Stream.fromAsyncIterable(cursor.cursor, F.identity), 130 | Stream.catchAll(mongoErrorOrDie(errorSource(cursor, "toStream"))), 131 | Stream.mapEffect((x) => decode(x)) 132 | ) 133 | } 134 | 135 | export const toStreamEither = ( 136 | cursor: FindCursor 137 | ): Stream.Stream, MongoError.MongoError, R> => { 138 | const decode = Schema.decodeUnknown(cursor.schema) 139 | return F.pipe( 140 | Stream.fromAsyncIterable(cursor.cursor, F.identity), 141 | Stream.catchAll(mongoErrorOrDie(errorSource(cursor, "toStreamEither"))), 142 | Stream.mapEffect((x) => 143 | F.pipe( 144 | // keep new line 145 | decode(x), 146 | Effect.mapError((error) => Tuple.make(x, error)), 147 | Effect.either 148 | ) 149 | ) 150 | ) 151 | } 152 | 153 | const isFindCursor = (x: unknown): x is FindCursor => x instanceof FindCursorImpl 154 | 155 | const errorSource = (cursor: FindCursor, functionName: string) => 156 | new MongoError.CollectionErrorSource({ 157 | module: FindCursorImpl.name, 158 | functionName, 159 | db: cursor.cursor.namespace.db, 160 | collection: cursor.cursor.namespace.collection ?? "NO_COLLECTION_NAME" 161 | }) 162 | -------------------------------------------------------------------------------- /packages/effect-mongodb/src/ListCollectionsCursor.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 0.0.1 3 | */ 4 | import * as Data from "effect/Data" 5 | import * as Effect from "effect/Effect" 6 | import * as F from "effect/Function" 7 | import type { Pipeable } from "effect/Pipeable" 8 | import { pipeArguments } from "effect/Pipeable" 9 | import * as Stream from "effect/Stream" 10 | import type { 11 | CollectionInfo as MongoCollectionInfo, 12 | ListCollectionsCursor as MongoListCollectionsCursor 13 | } from "mongodb" 14 | import { mongoErrorOrDie } from "./internal/mongo-error.js" 15 | import * as MongoError from "./MongoError.js" 16 | 17 | export type NameOnlyCollectionInfo = Pick 18 | export type FullCollectionInfo = MongoCollectionInfo 19 | 20 | type DefaultCollectionInfo = NameOnlyCollectionInfo | FullCollectionInfo 21 | 22 | type ListCollectionsCursorFields = { 23 | cursor: MongoListCollectionsCursor 24 | } 25 | 26 | export interface ListCollectionsCursor 27 | extends ListCollectionsCursorFields, Pipeable 28 | { 29 | _tag: "ListCollectionsCursor" 30 | } 31 | 32 | /** @internal */ 33 | export class ListCollectionsCursorImpl< 34 | T extends DefaultCollectionInfo = DefaultCollectionInfo 35 | > extends Data.TaggedClass("ListCollectionsCursor")> implements Pipeable { 36 | pipe() { 37 | return pipeArguments(this, arguments) 38 | } 39 | } 40 | 41 | export type NameOnlyListCollectionsCursor = ListCollectionsCursor< 42 | Pick 43 | > 44 | 45 | export type FullListCollectionsCursor = ListCollectionsCursor 46 | 47 | export const toArray = ( 48 | cursor: ListCollectionsCursor 49 | ): Effect.Effect, MongoError.MongoError> => 50 | F.pipe( 51 | Effect.promise(() => cursor.cursor.toArray()), 52 | Effect.catchAllDefect(mongoErrorOrDie(errorSource(cursor, "toArray"))) 53 | ) 54 | 55 | export const toStream = ( 56 | cursor: ListCollectionsCursor 57 | ): Stream.Stream => 58 | F.pipe( 59 | Stream.fromAsyncIterable(cursor.cursor, F.identity), 60 | Stream.catchAll(mongoErrorOrDie(errorSource(cursor, "toStream"))) 61 | ) 62 | 63 | const errorSource = (cursor: ListCollectionsCursor, functionName: string) => 64 | new MongoError.CollectionErrorSource({ 65 | module: ListCollectionsCursorImpl.name, 66 | functionName, 67 | db: cursor.cursor.namespace.db, 68 | collection: cursor.cursor.namespace.collection ?? "NO_COLLECTION_NAME" 69 | }) 70 | -------------------------------------------------------------------------------- /packages/effect-mongodb/src/MongoClient.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 0.0.1 3 | */ 4 | import * as Data from "effect/Data" 5 | import * as Effect from "effect/Effect" 6 | import * as F from "effect/Function" 7 | import type * as Scope from "effect/Scope" 8 | import type { DbOptions, MongoClientOptions } from "mongodb" 9 | import { MongoClient as MongoClient_ } from "mongodb" 10 | import * as Db from "./Db.js" 11 | import { mongoErrorOrDie } from "./internal/mongo-error.js" 12 | import * as MongoError from "./MongoError.js" 13 | 14 | export class MongoClient extends Data.TaggedClass("MongoClient")<{ client: MongoClient_ }> {} 15 | 16 | export const connect = ( 17 | url: string, 18 | options?: MongoClientOptions 19 | ): Effect.Effect => 20 | Effect.promise(() => MongoClient_.connect(url, options)).pipe( 21 | Effect.map((client) => new MongoClient({ client })), 22 | Effect.catchAllDefect(mongoErrorOrDie(errorSource([new URL(url).host], "connect"))) 23 | ) 24 | 25 | export const close: { 26 | (force?: boolean): (client: MongoClient) => Effect.Effect 27 | (client: MongoClient, force?: boolean): Effect.Effect 28 | } = F.dual( 29 | (args) => isMongoClient(args[0]), 30 | ({ client }: MongoClient, force?: boolean): Effect.Effect => 31 | Effect.promise(() => client.close(force)).pipe( 32 | Effect.catchAllDefect(mongoErrorOrDie(errorSource(client.options.hosts.map((x) => x.host ?? "NO_HOST"), "close"))) 33 | ) 34 | ) 35 | 36 | export type MongoClientScopedOptions = MongoClientOptions & { forceClose?: boolean } 37 | 38 | export const connectScoped = ( 39 | url: string, 40 | options?: MongoClientScopedOptions 41 | ): Effect.Effect => 42 | Effect.acquireRelease( 43 | connect(url, options), 44 | (client) => close(client, options?.forceClose).pipe(Effect.orDie) 45 | ) 46 | 47 | export const db: { 48 | (dbName?: string, options?: DbOptions): (client: MongoClient) => Db.Db 49 | (client: MongoClient, dbName?: string, options?: DbOptions): Db.Db 50 | } = F.dual( 51 | (args) => isMongoClient(args[0]), 52 | ({ client }: MongoClient, dbName?: string, options?: DbOptions): Db.Db => 53 | new Db.Db({ db: client.db(dbName, options) }) 54 | ) 55 | 56 | const isMongoClient = (x: unknown) => x instanceof MongoClient 57 | 58 | const errorSource = (hosts: Array, functionName: string) => 59 | new MongoError.ClientErrorSource({ module: "MongoClient", functionName, hosts }) 60 | -------------------------------------------------------------------------------- /packages/effect-mongodb/src/MongoError.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 0.0.1 3 | */ 4 | import * as Data from "effect/Data" 5 | import type { MongoError as MongoError_ } from "mongodb" 6 | 7 | export class ClientErrorSource extends Data.TaggedClass("ClientErrorSource")<{ 8 | module: string 9 | functionName: string 10 | hosts: Array 11 | }> {} 12 | 13 | export class DbErrorSource extends Data.TaggedClass("DbErrorSource")<{ 14 | module: string 15 | functionName: string 16 | db: string 17 | }> {} 18 | 19 | export class CollectionErrorSource extends Data.TaggedClass("CollectionErrorSource")<{ 20 | module: string 21 | functionName: string 22 | db: string 23 | collection: string 24 | }> {} 25 | 26 | export type ErrorSource = ClientErrorSource | DbErrorSource | CollectionErrorSource 27 | 28 | export class MongoError extends Data.TaggedError("MongoError")<{ 29 | message: string 30 | cause: MongoError_ 31 | source?: ErrorSource 32 | }> {} 33 | -------------------------------------------------------------------------------- /packages/effect-mongodb/src/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 0.0.1 3 | */ 4 | export * as AggregationCursor from "./AggregationCursor.js" 5 | 6 | /** 7 | * @since 0.0.1 8 | */ 9 | export * as Collection from "./Collection.js" 10 | 11 | /** 12 | * @since 0.0.1 13 | */ 14 | export * as Db from "./Db.js" 15 | 16 | /** 17 | * @since 0.0.1 18 | */ 19 | export * as DocumentAggregationCursor from "./DocumentAggregationCursor.js" 20 | 21 | /** 22 | * @since 0.0.1 23 | */ 24 | export * as DocumentCollection from "./DocumentCollection.js" 25 | 26 | /** 27 | * @since 0.0.1 28 | */ 29 | export * as DocumentFindCursor from "./DocumentFindCursor.js" 30 | 31 | /** 32 | * @since 0.0.1 33 | */ 34 | export * as FindCursor from "./FindCursor.js" 35 | 36 | /** 37 | * @since 0.0.1 38 | */ 39 | export * as ListCollectionsCursor from "./ListCollectionsCursor.js" 40 | 41 | /** 42 | * @since 0.0.1 43 | */ 44 | export * as MongoClient from "./MongoClient.js" 45 | 46 | /** 47 | * @since 0.0.1 48 | */ 49 | export * as MongoError from "./MongoError.js" 50 | -------------------------------------------------------------------------------- /packages/effect-mongodb/src/internal/filter.ts: -------------------------------------------------------------------------------- 1 | import type { Condition, RootFilterOperators as MongoRootFilterOperators, WithId } from "mongodb" 2 | 3 | export type RootFilterOperators = Omit, "$where"> & { $where?: string } 4 | 5 | export type Filter = 6 | & { 7 | [P in keyof WithId]?: Condition[P]> 8 | } 9 | & RootFilterOperators> 10 | -------------------------------------------------------------------------------- /packages/effect-mongodb/src/internal/modify-result.ts: -------------------------------------------------------------------------------- 1 | import type * as O from "effect/Option" 2 | import type { ModifyResult as MongoModifyResult } from "mongodb" 3 | 4 | export type ModifyResult = Omit, "value"> & { 5 | value: O.Option 6 | } 7 | -------------------------------------------------------------------------------- /packages/effect-mongodb/src/internal/mongo-error.ts: -------------------------------------------------------------------------------- 1 | import * as Effect from "effect/Effect" 2 | import * as F from "effect/Function" 3 | import * as Match from "effect/Match" 4 | import { MongoError as MongoError_ } from "mongodb" 5 | import type { ErrorSource } from "../MongoError.js" 6 | import { MongoError } from "../MongoError.js" 7 | 8 | export const mongoErrorOrDie = 9 | (source: ErrorSource, message?: string) => (error: unknown): Effect.Effect => 10 | F.pipe( 11 | Match.value(error), 12 | Match.when(Match.instanceOf(MongoError_), F.flow(makeMongoError(source, message), Effect.fail)), 13 | Match.orElse(F.flow(makeError(source, message), Effect.die)) 14 | ) 15 | 16 | const makeMongoError = (source: ErrorSource, message: string | undefined) => (cause: MongoError_) => 17 | new MongoError({ message: messageFrom(source, message), cause, source }) 18 | 19 | const makeError = (source: ErrorSource, message: string | undefined) => (cause: unknown) => 20 | new Error(messageFrom(source, message), { cause }) 21 | 22 | const messageFrom = (source: ErrorSource, message?: string) => 23 | message ? `${messageFromSource(source)} - ${message}` : messageFromSource(source) 24 | 25 | const messageFromSource = (source: ErrorSource) => 26 | F.pipe( 27 | Match.value(source), 28 | Match.tag("ClientErrorSource", (s) => `${baseMessage(s)} - hosts ${s.hosts.join(", ")}`), 29 | Match.tag("DbErrorSource", (s) => `${baseMessage(s)} - ${s.db}`), 30 | Match.tag("CollectionErrorSource", (s) => `${baseMessage(s)} - ${s.db}.${s.collection}`), 31 | Match.exhaustive 32 | ) 33 | 34 | const baseMessage = (s: { module: string; functionName: string }) => `Error in ${s.module}.${s.functionName}` 35 | -------------------------------------------------------------------------------- /packages/effect-mongodb/src/internal/schema.ts: -------------------------------------------------------------------------------- 1 | import * as Effect from "effect/Effect" 2 | import * as O from "effect/Option" 3 | import * as Schema from "effect/Schema" 4 | import type { Document } from "mongodb" 5 | 6 | // TODO: there is probably a Schema to do this decode or using typeclass package for traverse 7 | export const decodeNullableDocument = (schema: Schema.Schema, value: Document | null) => 8 | Effect.gen(function*() { 9 | if (value === null) return O.none() 10 | const decoded = yield* Schema.decodeUnknown(schema)(value) 11 | return O.some(decoded) 12 | }) 13 | -------------------------------------------------------------------------------- /packages/effect-mongodb/src/internal/version.ts: -------------------------------------------------------------------------------- 1 | let moduleVersion = "0.3.1" 2 | 3 | export const getCurrentVersion = () => moduleVersion 4 | 5 | export const setCurrentVersion = (version: string) => { 6 | moduleVersion = version 7 | } 8 | -------------------------------------------------------------------------------- /packages/effect-mongodb/test/Collection.test.ts: -------------------------------------------------------------------------------- 1 | import * as AggregationCursor from "effect-mongodb/AggregationCursor" 2 | import * as Collection from "effect-mongodb/Collection" 3 | import * as Db from "effect-mongodb/Db" 4 | import * as Effect from "effect/Effect" 5 | import * as O from "effect/Option" 6 | import * as Schema from "effect/Schema" 7 | import { expect, test } from "vitest" 8 | import { describeMongo } from "./support/describe-mongo.js" 9 | 10 | describeMongo("Collection", (ctx) => { 11 | test("find one", async () => { 12 | const user = User.make({ name: "john", birthday: new Date(1977, 11, 27) }) 13 | 14 | const program = Effect.gen(function*() { 15 | const db = yield* ctx.database 16 | const collection = Db.collection(db, "find-one", User) 17 | 18 | yield* Collection.insertOne(collection, user) 19 | 20 | return yield* Collection.findOne(collection, { name: "john" }) 21 | }) 22 | 23 | const result = await Effect.runPromise(program) 24 | 25 | expect(result).toEqual(O.some(user)) 26 | }) 27 | 28 | test("find one - no result", async () => { 29 | const program = Effect.gen(function*() { 30 | const db = yield* ctx.database 31 | const collection = Db.collection(db, "find-one-no-result", User) 32 | 33 | const users = [ 34 | User.make({ name: "any1", birthday: new Date(1994, 1, 1) }), 35 | User.make({ name: "any2", birthday: new Date(1977, 11, 27) }), 36 | User.make({ name: "any3", birthday: new Date(1989, 5, 11) }) 37 | ] 38 | yield* Collection.insertMany(collection, users) 39 | 40 | return yield* Collection.findOne(collection, { name: "john" }) 41 | }) 42 | 43 | const result = await Effect.runPromise(program) 44 | 45 | expect(result).toEqual(O.none()) 46 | }) 47 | 48 | test("find one and replace", async () => { 49 | const program = Effect.gen(function*() { 50 | const db = yield* ctx.database 51 | const collection = Db.collection(db, "find-one-and-replace", UserWithVersion) 52 | 53 | yield* Collection.insertOne(collection, { name: "john", version: "v1" }) 54 | 55 | return yield* Collection.findOneAndReplace( 56 | collection, 57 | { name: "john", version: "v1" }, 58 | { name: "john", version: "v2" }, 59 | { returnDocument: "after" } 60 | ) 61 | }) 62 | 63 | const result = await Effect.runPromise(program) 64 | 65 | expect(result).toEqual(O.some({ name: "john", version: "v2" })) 66 | }) 67 | 68 | test("find one and replace - include result metadata", async () => { 69 | const program = Effect.gen(function*() { 70 | const db = yield* ctx.database 71 | const collection = Db.collection(db, "find-one-and-replace-include-result-metadata", UserWithVersion) 72 | 73 | yield* Collection.insertOne(collection, { name: "john", version: "v1" }) 74 | 75 | return yield* Collection.findOneAndReplace( 76 | collection, 77 | { name: "john", version: "v1" }, 78 | { name: "john", version: "v2" }, 79 | { returnDocument: "after", includeResultMetadata: true } 80 | ) 81 | }) 82 | 83 | const result = await Effect.runPromise(program) 84 | 85 | expect(result).toMatchObject({ 86 | ok: 1, 87 | value: O.some({ name: "john", version: "v2" }) 88 | }) 89 | }) 90 | 91 | test("aggregate", async () => { 92 | const user1 = User.make({ name: "user1", birthday: new Date(1977, 11, 27) }) 93 | const user2 = User.make({ name: "user2", birthday: new Date(1977, 11, 27) }) 94 | const user3 = User.make({ name: "user3", birthday: new Date(1985, 6, 16) }) 95 | const user4 = User.make({ name: "user4", birthday: new Date(1989, 11, 28) }) 96 | const user5 = User.make({ name: "user5", birthday: new Date(1995, 3, 21) }) 97 | const user6 = User.make({ name: "user6", birthday: new Date(2000, 5, 30) }) 98 | 99 | const program = Effect.gen(function*() { 100 | const db = yield* ctx.database 101 | const collection = Db.collection(db, "aggregate", User) 102 | 103 | yield* Collection.insertMany(collection, [user1, user2, user3, user4, user5, user6]) 104 | 105 | const _1990 = "1990-01-01T00:00:00.000Z" 106 | return yield* Collection.aggregate(collection, UserAggregation, [ 107 | { 108 | $match: { 109 | birthday: { $lt: _1990 } 110 | } 111 | }, 112 | { 113 | $group: { 114 | _id: "$birthday", 115 | names: { $addToSet: "$name" } 116 | } 117 | }, 118 | { 119 | $sort: { _id: 1 } 120 | } 121 | ]).pipe(AggregationCursor.toArray) 122 | }) 123 | 124 | const result = await Effect.runPromise(program) 125 | 126 | expect(result).toEqual([ 127 | { 128 | _id: new Date(1977, 11, 27), 129 | names: expect.arrayContaining(["user1", "user2"]) 130 | }, 131 | { 132 | _id: new Date(1985, 6, 16), 133 | names: ["user3"] 134 | }, 135 | { 136 | _id: new Date(1989, 11, 28), 137 | names: ["user4"] 138 | } 139 | ]) 140 | }) 141 | }) 142 | 143 | const User = Schema.Struct({ 144 | name: Schema.String, 145 | birthday: Schema.Date 146 | }) 147 | 148 | const UserWithVersion = Schema.Struct({ 149 | name: Schema.String, 150 | version: Schema.String 151 | }) 152 | 153 | const UserAggregation = Schema.Struct({ 154 | _id: Schema.Date, 155 | names: Schema.Array(Schema.String) 156 | }) 157 | -------------------------------------------------------------------------------- /packages/effect-mongodb/test/DocumentCollection.test.ts: -------------------------------------------------------------------------------- 1 | import * as Db from "effect-mongodb/Db" 2 | import * as DocumentCollection from "effect-mongodb/DocumentCollection" 3 | import * as DocumentFindCursor from "effect-mongodb/DocumentFindCursor" 4 | import * as Cause from "effect/Cause" 5 | import * as Chunk from "effect/Chunk" 6 | import * as Effect from "effect/Effect" 7 | import * as Exit from "effect/Exit" 8 | import * as O from "effect/Option" 9 | import { ObjectId } from "mongodb" 10 | import { expect, test } from "vitest" 11 | import { describeMongo } from "./support/describe-mongo.js" 12 | 13 | describeMongo("DocumentCollection", (ctx) => { 14 | test("insert an array as record", async () => { 15 | const program = Effect.gen(function*() { 16 | const db = yield* ctx.database 17 | const collection = Db.documentCollection(db, "insert-an-array-as-record") 18 | 19 | yield* DocumentCollection.insertOne(collection, [{ name: "John" }]) 20 | }) 21 | 22 | const result = await Effect.runPromiseExit(program) 23 | 24 | expect(Exit.isFailure(result)).toBeTruthy() 25 | if (Exit.isFailure(result)) { 26 | const error = Chunk.unsafeHead(Cause.failures(result.cause)) 27 | expect(error.cause.errmsg).toEqual( 28 | "BSON field 'insert.documents.0' is the wrong type 'array', expected type 'object'" 29 | ) 30 | } 31 | }) 32 | 33 | test("insert a class instance as record", async () => { 34 | class MyClass { 35 | name: string 36 | 37 | constructor(name: string) { 38 | this.name = name 39 | } 40 | } 41 | 42 | const program = Effect.gen(function*() { 43 | const db = yield* ctx.database 44 | const collection = Db.documentCollection(db, "insert-a-class-instance-as-record") 45 | 46 | yield* DocumentCollection.insertOne(collection, new MyClass("John")) 47 | 48 | return yield* DocumentCollection.find(collection).pipe(DocumentFindCursor.toArray) 49 | }) 50 | 51 | const result = await Effect.runPromise(program) 52 | 53 | expect(result).toHaveLength(1) 54 | expect(result[0]).toEqual({ _id: expect.any(ObjectId), name: "John" }) 55 | expect(result[0]).not.toBeInstanceOf(MyClass) 56 | }) 57 | 58 | test("find one", async () => { 59 | const program = Effect.gen(function*() { 60 | const db = yield* ctx.database 61 | const collection = Db.documentCollection(db, "find-one") 62 | 63 | yield* DocumentCollection.insertMany( 64 | collection, 65 | [{ name: "ANY_NAME_1" }, { name: "john" }, { name: "ANY_NAME_2" }] 66 | ) 67 | 68 | return yield* DocumentCollection.findOne(collection, { name: "john" }) 69 | }) 70 | 71 | const result = await Effect.runPromise(program) 72 | 73 | expect(result).toEqual(O.some({ _id: expect.any(ObjectId), name: "john" })) 74 | }) 75 | 76 | test("find one - no result", async () => { 77 | const program = Effect.gen(function*() { 78 | const db = yield* ctx.database 79 | const collection = Db.documentCollection(db, "find-one-no-result") 80 | 81 | yield* DocumentCollection.insertMany( 82 | collection, 83 | [{ name: "ANY_NAME_1" }, { name: "ANY_NAME_2" }, { name: "ANY_NAME_3" }] 84 | ) 85 | 86 | return yield* DocumentCollection.findOne(collection, { name: "john" }) 87 | }) 88 | 89 | const result = await Effect.runPromise(program) 90 | 91 | expect(result).toEqual(O.none()) 92 | }) 93 | 94 | test("find one and replace", async () => { 95 | const program = Effect.gen(function*() { 96 | const db = yield* ctx.database 97 | const collection = Db.documentCollection(db, "find-one-and-replace") 98 | 99 | yield* DocumentCollection.insertOne(collection, { name: "john", version: "v1" }) 100 | 101 | return yield* DocumentCollection.findOneAndReplace( 102 | collection, 103 | { name: "john", version: "v1" }, 104 | { name: "john", version: "v2" }, 105 | { returnDocument: "after" } 106 | ) 107 | }) 108 | 109 | const result = await Effect.runPromise(program) 110 | 111 | expect(result).toEqual(O.some({ _id: expect.any(ObjectId), name: "john", version: "v2" })) 112 | }) 113 | 114 | test("find one and replace - include result metadata", async () => { 115 | const program = Effect.gen(function*() { 116 | const db = yield* ctx.database 117 | const collection = Db.documentCollection(db, "find-one-and-replace-include-result-metadata") 118 | 119 | yield* DocumentCollection.insertOne(collection, { name: "john", version: "v1" }) 120 | 121 | return yield* DocumentCollection.findOneAndReplace( 122 | collection, 123 | { name: "john", version: "v1" }, 124 | { name: "john", version: "v2" }, 125 | { returnDocument: "after", includeResultMetadata: true } 126 | ) 127 | }) 128 | 129 | const result = await Effect.runPromise(program) 130 | 131 | expect(result).toMatchObject({ 132 | ok: 1, 133 | value: O.some({ _id: expect.any(ObjectId), name: "john", version: "v2" }) 134 | }) 135 | }) 136 | }) 137 | -------------------------------------------------------------------------------- /packages/effect-mongodb/test/DocumentFindCursor.test.ts: -------------------------------------------------------------------------------- 1 | import * as Db from "effect-mongodb/Db" 2 | import * as DocumentCollection from "effect-mongodb/DocumentCollection" 3 | import * as DocumentFindCursor from "effect-mongodb/DocumentFindCursor" 4 | import * as Chunk from "effect/Chunk" 5 | import * as Effect from "effect/Effect" 6 | import * as Stream from "effect/Stream" 7 | import { expect, test } from "vitest" 8 | import { describeMongo } from "./support/describe-mongo.js" 9 | 10 | describeMongo("DocumentFindCursor", (ctx) => { 11 | test("acceptance test", async () => { 12 | const beforeJuly = "2024-07-01T00:00:00.000Z" 13 | 14 | const program = Effect.gen(function*() { 15 | const db = yield* ctx.database 16 | const collection = Db.documentCollection(db, "acceptance-test") 17 | 18 | yield* DocumentCollection.insertMany(collection, [ 19 | { id: 2, type: "Admin", createdOn: "2024-06-26T21:15:00.000Z" }, 20 | { id: 3, type: "User", createdOn: "2024-07-26T09:30:00.000Z" }, 21 | { id: 1, type: "User", createdOn: "2024-04-19T17:45:00.000Z" }, 22 | { id: 5, type: "User", createdOn: "2024-05-26T09:30:00.000Z" }, 23 | { id: 4, type: "User", createdOn: "2024-07-05T15:00:00.000Z" } 24 | ]) 25 | 26 | return yield* DocumentCollection.find(collection).pipe( 27 | DocumentFindCursor.filter({ createdOn: { $lt: beforeJuly } }), 28 | DocumentFindCursor.sort("createdOn", "ascending"), 29 | DocumentFindCursor.project({ _id: 0, id: 1 }), 30 | DocumentFindCursor.limit(2), 31 | DocumentFindCursor.toArray 32 | ) 33 | }) 34 | 35 | const result = await Effect.runPromise(program) 36 | 37 | expect(result).toEqual([{ id: 1 }, { id: 5 }]) 38 | }) 39 | 40 | test("stream", async () => { 41 | const users = [ 42 | { id: 1, type: "User" }, 43 | { id: 2, type: "Admin" }, 44 | { id: 3, type: "User" }, 45 | { id: 4, type: "Admin" } 46 | ] 47 | const program = Effect.gen(function*() { 48 | const db = yield* ctx.database 49 | 50 | const collection = Db.documentCollection(db, "stream") 51 | yield* DocumentCollection.insertMany(collection, users) 52 | 53 | return yield* DocumentCollection.find(collection).pipe( 54 | DocumentFindCursor.toStream, 55 | Stream.runCollect, 56 | Effect.map(Chunk.toReadonlyArray) 57 | ) 58 | }) 59 | 60 | const result = await Effect.runPromise(program) 61 | 62 | expect(result).toEqual(users) 63 | }) 64 | }) 65 | -------------------------------------------------------------------------------- /packages/effect-mongodb/test/FindCursor.test.ts: -------------------------------------------------------------------------------- 1 | import * as Collection from "effect-mongodb/Collection" 2 | import * as Db from "effect-mongodb/Db" 3 | import * as DocumentCollection from "effect-mongodb/DocumentCollection" 4 | import * as FindCursor from "effect-mongodb/FindCursor" 5 | import * as Arbitrary from "effect/Arbitrary" 6 | import * as Array from "effect/Array" 7 | import * as Chunk from "effect/Chunk" 8 | import * as Effect from "effect/Effect" 9 | import * as FastCheck from "effect/FastCheck" 10 | import * as ParseResult from "effect/ParseResult" 11 | import * as Schema from "effect/Schema" 12 | import * as Stream from "effect/Stream" 13 | import { expect, test } from "vitest" 14 | import { describeMongo } from "./support/describe-mongo.js" 15 | 16 | describeMongo("FindCursor", (ctx) => { 17 | test("acceptance test", async () => { 18 | const beforeJuly = "2024-07-01T00:00:00.000Z" 19 | 20 | const User = Schema.Struct({ 21 | id: Schema.Number, 22 | createdOn: Schema.Date 23 | }) 24 | const UserProjection = Schema.Struct({ 25 | id: User.fields.id 26 | }) 27 | 28 | const program = Effect.gen(function*() { 29 | const db = yield* ctx.database 30 | const collection = Db.collection(db, "acceptance-test", User) 31 | 32 | yield* Collection.insertMany(collection, [ 33 | { id: 2, createdOn: new Date("2024-06-26T21:15:00.000Z") }, 34 | { id: 3, createdOn: new Date("2024-07-26T09:30:00.000Z") }, 35 | { id: 1, createdOn: new Date("2024-04-19T17:45:00.000Z") }, 36 | { id: 5, createdOn: new Date("2024-05-26T09:30:00.000Z") }, 37 | { id: 4, createdOn: new Date("2024-07-05T15:00:00.000Z") } 38 | ]) 39 | 40 | return yield* Collection.find(collection).pipe( 41 | FindCursor.filter({ createdOn: { $lt: beforeJuly } }), 42 | FindCursor.sort("createdOn", "ascending"), 43 | FindCursor.project(UserProjection, { _id: 0, id: 1 }), 44 | FindCursor.limit(2), 45 | FindCursor.toArray 46 | ) 47 | }) 48 | 49 | const result = await Effect.runPromise(program) 50 | 51 | expect(result).toEqual([{ id: 1 }, { id: 5 }]) 52 | }) 53 | 54 | test("array with partitioned errors", async () => { 55 | const program = Effect.gen(function*() { 56 | const db = yield* ctx.database 57 | const documentCollection = Db.documentCollection(db, "array-with-partitioned-errors") 58 | const collection = DocumentCollection.typed(documentCollection, User) 59 | 60 | yield* Collection.insertMany(collection, FastCheck.sample(UserArbitrary, 6)) 61 | yield* DocumentCollection.insertOne(documentCollection, { id: 999, surname: "foo" }) 62 | yield* Collection.insertMany(collection, FastCheck.sample(UserArbitrary, 3)) 63 | 64 | return yield* Collection.find(collection).pipe(FindCursor.toArrayEither) 65 | }) 66 | 67 | const result = await Effect.runPromise(program) 68 | 69 | expect(Array.getRights(result)).toHaveLength(9) 70 | expect(Array.getLefts(result)).toEqual( 71 | [ 72 | [expect.objectContaining({ id: 999, surname: "foo" }), expect.any(ParseResult.ParseError)] as const 73 | ] 74 | ) 75 | }) 76 | 77 | test("stream", async () => { 78 | const anyUsers = FastCheck.sample(UserArbitrary, 6) 79 | 80 | const program = Effect.gen(function*() { 81 | const db = yield* ctx.database 82 | const collection = Db.collection(db, "stream", User) 83 | 84 | yield* Collection.insertMany(collection, anyUsers) 85 | 86 | return yield* Collection.find(collection).pipe( 87 | FindCursor.toStream, 88 | Stream.runCollect, 89 | Effect.map(Chunk.toReadonlyArray) 90 | ) 91 | }) 92 | 93 | const result = await Effect.runPromise(program) 94 | 95 | expect(result).toEqual(anyUsers) 96 | }) 97 | 98 | test("stream with partitioned errors", async () => { 99 | const program = Effect.gen(function*() { 100 | const db = yield* ctx.database 101 | const documentCollection = Db.documentCollection(db, "stream-with-partitioned-errors") 102 | const collection = DocumentCollection.typed(documentCollection, User) 103 | 104 | yield* Collection.insertMany(collection, FastCheck.sample(UserArbitrary, 6)) 105 | yield* DocumentCollection.insertOne(documentCollection, { id: 999, surname: "foo" }) 106 | yield* Collection.insertMany(collection, FastCheck.sample(UserArbitrary, 3)) 107 | 108 | return yield* Collection.find(collection).pipe( 109 | FindCursor.toStreamEither, 110 | Stream.runCollect, 111 | Effect.map(Chunk.toReadonlyArray) 112 | ) 113 | }) 114 | 115 | const result = await Effect.runPromise(program) 116 | 117 | expect(Array.getRights(result)).toHaveLength(9) 118 | expect(Array.getLefts(result)).toEqual( 119 | [ 120 | [expect.objectContaining({ id: 999, surname: "foo" }), expect.any(ParseResult.ParseError)] as const 121 | ] 122 | ) 123 | }) 124 | }) 125 | 126 | const User = Schema.Struct({ 127 | id: Schema.Number, 128 | name: Schema.String 129 | }) 130 | const UserArbitrary = Arbitrary.make(User) 131 | -------------------------------------------------------------------------------- /packages/effect-mongodb/test/MongoClient.test.ts: -------------------------------------------------------------------------------- 1 | import * as MongoClient from "effect-mongodb/MongoClient" 2 | import * as MongoError from "effect-mongodb/MongoError" 3 | import * as Effect from "effect/Effect" 4 | import * as F from "effect/Function" 5 | import { describe, expect, inject, test } from "vitest" 6 | 7 | describe("MongoClient", () => { 8 | test("connect and close", async () => { 9 | await F.pipe( 10 | MongoClient.connect(inject("mongoConnectionString"), { directConnection: true }), 11 | Effect.tap((client) => Effect.sync(() => expect(client).toBeDefined())), 12 | Effect.flatMap(MongoClient.close), 13 | Effect.runPromise 14 | ) 15 | }) 16 | 17 | test("connect error", async () => { 18 | const result = await F.pipe( 19 | MongoClient.connect("mongodb://user:pwd@wrongurlforsure.local:27017", { 20 | directConnection: true, 21 | serverSelectionTimeoutMS: 200 22 | }), 23 | Effect.catchAll(Effect.succeed), 24 | Effect.runPromise 25 | ) 26 | 27 | expect(result).toBeInstanceOf(MongoError.MongoError) 28 | if (result instanceof MongoError.MongoError) { 29 | expect(result.message).not.toContain("user") 30 | expect(result.message).not.toContain("pwd") 31 | } 32 | }) 33 | }) 34 | -------------------------------------------------------------------------------- /packages/effect-mongodb/test/globalSetup/mongodb.ts: -------------------------------------------------------------------------------- 1 | import { MongoDBContainer } from "@testcontainers/mongodb" 2 | import type { StartedMongoDBContainer } from "@testcontainers/mongodb" 3 | import type { GlobalSetupContext } from "vitest/node" 4 | 5 | let container: StartedMongoDBContainer | undefined 6 | 7 | export async function setup({ provide }: GlobalSetupContext) { 8 | container = await new MongoDBContainer("mongo:8.0.4").start() 9 | 10 | provide("mongoConnectionString", container.getConnectionString()) 11 | if (process.env.EFFECT_MONGODB_DEBUG === "true") { 12 | console.log( 13 | `[EFFECT_MONGODB_DEBUG] MongoDB connection string with direct connection: '${container.getConnectionString()}'` 14 | ) 15 | } 16 | } 17 | 18 | export async function teardown() { 19 | await container?.stop() 20 | } 21 | 22 | declare module "vitest" { 23 | export interface ProvidedContext { 24 | mongoConnectionString: string 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /packages/effect-mongodb/test/support/describe-mongo.ts: -------------------------------------------------------------------------------- 1 | import * as Db from "effect-mongodb/Db" 2 | import * as Effect from "effect/Effect" 3 | import type { Db as Db_ } from "mongodb" 4 | import { MongoClient } from "mongodb" 5 | import { afterAll, beforeAll, describe, inject } from "vitest" 6 | 7 | type MongoContext = { 8 | client: Effect.Effect 9 | database: Effect.Effect 10 | _client: () => MongoClient 11 | _database: () => Db_ 12 | } 13 | 14 | export const describeMongo = ( 15 | suiteName: string, 16 | tests: (ctx: MongoContext) => void 17 | ) => { 18 | describe(suiteName, () => { 19 | let client: MongoClient 20 | let database: Db_ 21 | let databaseName: string 22 | 23 | beforeAll(async () => { 24 | client = new MongoClient(inject("mongoConnectionString"), { 25 | directConnection: true 26 | }) 27 | await client.connect() 28 | 29 | // https://www.mongodb.com/docs/v5.2/reference/limits/#mongodb-limit-Database-Name-Case-Sensitivity 30 | databaseName = suiteName.replace(/[/\\. "$*<>:|?]/g, "-") 31 | database = client.db(databaseName) 32 | 33 | const collections = await database.collections() 34 | await Promise.all(collections.map((x) => x.deleteMany({}))) 35 | }) 36 | 37 | afterAll(async () => { 38 | await client.close() 39 | }) 40 | 41 | tests({ 42 | _client: () => client, 43 | _database: () => database, 44 | client: Effect.sync(() => client), 45 | database: Effect.sync(() => new Db.Db({ db: database })) 46 | }) 47 | }) 48 | } 49 | -------------------------------------------------------------------------------- /packages/effect-mongodb/test/testcontainer.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, test } from "vitest" 2 | import { describeMongo } from "./support/describe-mongo.js" 3 | 4 | describeMongo("test containers", (ctx) => { 5 | test("insert and find", async () => { 6 | const db = ctx._database() 7 | const users = db.collection("users") 8 | 9 | await users.insertMany([ 10 | { id: 1, name: "ANY_NAME_1" }, 11 | { id: 2, name: "ANY_NAME_2" }, 12 | { id: 3, name: "ANY_NAME_3" } 13 | ]) 14 | 15 | const result = await users.find({}).toArray() 16 | 17 | expect(result).toEqual([ 18 | expect.objectContaining({ id: 1, name: "ANY_NAME_1" }), 19 | expect.objectContaining({ id: 2, name: "ANY_NAME_2" }), 20 | expect.objectContaining({ id: 3, name: "ANY_NAME_3" }) 21 | ]) 22 | }) 23 | }) 24 | -------------------------------------------------------------------------------- /packages/effect-mongodb/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.src.json", 3 | "compilerOptions": { 4 | "tsBuildInfoFile": ".tsbuildinfo/build.tsbuildinfo", 5 | "outDir": "build/esm", 6 | "declarationDir": "build/dts", 7 | "stripInternal": true 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/effect-mongodb/tsconfig.examples.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "include": ["examples"], 4 | "references": [{ "path": "tsconfig.src.json" }], 5 | "compilerOptions": { 6 | "tsBuildInfoFile": ".tsbuildinfo/examples.tsbuildinfo", 7 | "rootDir": "examples", 8 | "noEmit": true, 9 | "noUnusedLocals": false 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /packages/effect-mongodb/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "include": [], 4 | "references": [ 5 | { "path": "tsconfig.src.json" }, 6 | { "path": "tsconfig.test.json" }, 7 | { "path": "tsconfig.examples.json" } 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /packages/effect-mongodb/tsconfig.src.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "include": ["src"], 4 | "compilerOptions": { 5 | "tsBuildInfoFile": ".tsbuildinfo/src.tsbuildinfo", 6 | "rootDir": "src", 7 | "outDir": "build/src" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/effect-mongodb/tsconfig.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "include": ["test"], 4 | "references": [{ "path": "tsconfig.src.json" }], 5 | "compilerOptions": { 6 | "tsBuildInfoFile": ".tsbuildinfo/test.tsbuildinfo", 7 | "rootDir": "test", 8 | "noEmit": true, 9 | "exactOptionalPropertyTypes": false, 10 | "noUnusedLocals": false 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /packages/effect-mongodb/vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { mergeConfig, type UserConfigExport } from "vitest/config" 2 | import shared from "../../vitest.shared.js" 3 | 4 | const config: UserConfigExport = { 5 | test: { 6 | globalSetup: ["./test/globalSetup/mongodb.ts"] 7 | } 8 | } 9 | 10 | export default mergeConfig(shared, config) 11 | -------------------------------------------------------------------------------- /packages/services/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @effect-mongodb/services 2 | 3 | ## 0.2.1 4 | 5 | ### Patch Changes 6 | 7 | - [`191c6c6`](https://github.com/doubleloop-io/effect-mongodb/commit/191c6c695c52b2e62f09ef8ba25a208223e258b1) Thanks [@VenomAV](https://github.com/VenomAV)! - Bump patch to work around changeset issue in the previous release 8 | 9 | - Updated dependencies [[`191c6c6`](https://github.com/doubleloop-io/effect-mongodb/commit/191c6c695c52b2e62f09ef8ba25a208223e258b1)]: 10 | - effect-mongodb@0.3.1 11 | 12 | ## 0.2.0 13 | 14 | ### Minor Changes 15 | 16 | - [`988e46e`](https://github.com/doubleloop-io/effect-mongodb/commit/988e46e6604fee6ebc0c99d5246d0596ce19be66) Thanks [@VenomAV](https://github.com/VenomAV)! - Add helper type to extract service type from tag 17 | 18 | ### Patch Changes 19 | 20 | - Updated dependencies [[`4c9d190`](https://github.com/doubleloop-io/effect-mongodb/commit/4c9d190b1fedcb31c15bbceecbabfa019f234354)]: 21 | - effect-mongodb@0.3.0 22 | 23 | ## 0.1.1 24 | 25 | ### Patch Changes 26 | 27 | - [`be876d2`](https://github.com/doubleloop-io/effect-mongodb/commit/be876d2f90a93afbc19b99138f7f0aec3abcda82) Thanks [@devmatteini](https://github.com/devmatteini)! - First public release of `@effect-mongodb/services`. 28 | 29 | ## 0.1.0 30 | 31 | ### Minor Changes 32 | 33 | - [`7f6168f`](https://github.com/doubleloop-io/effect-mongodb/commit/7f6168fc7fa83815e51f670294bd3aeb30c235ce) Thanks [@devmatteini](https://github.com/devmatteini)! - First release of `@effect-mongodb/services`. 34 | Check out the README documentation for more information. 35 | -------------------------------------------------------------------------------- /packages/services/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024-present doubleloop.io 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 | -------------------------------------------------------------------------------- /packages/services/README.md: -------------------------------------------------------------------------------- 1 | # @effect-mongodb/services 2 | 3 | ![NPM Version](https://img.shields.io/npm/v/@effect-mongodb/services?link=https%3A%2F%2Fwww.npmjs.com%2Fpackage%2F@effect-mongodb/services) 4 | ![minzipped size](https://badgen.net/bundlephobia/minzip/@effect-mongodb/services) 5 | ![dependency count](https://badgen.net/bundlephobia/dependency-count/@effect-mongodb/services) 6 | ![tree shaking support](https://badgen.net/bundlephobia/tree-shaking/@effect-mongodb/services) 7 | 8 | [Effect](https://github.com/Effect-TS/effect/) services for [effect-mongodb](../effect-mongodb/README.md). 9 | 10 | ## Install 11 | 12 | ```shell 13 | pnpm install @effect-mongodb/services effect-mongodb effect mongodb 14 | ``` 15 | 16 | Note that `effect-mongodb`, `effect`, and `mongodb` are requested as peer dependencies. 17 | 18 | ## Usage 19 | 20 | Here is a simple example of how to use this package: 21 | 22 | ```typescript 23 | import { DbInstance, DbService } from "@effect-mongodb/services" 24 | import { Effect, Schema } from "effect" 25 | import { Collection, Db, FindCursor } from "effect-mongodb" 26 | 27 | const Person = Schema.Struct({ name: Schema.String, age: Schema.Number, birthday: Schema.Date }) 28 | 29 | // 1. Create your database service tag 30 | const Database = DbService.Tag("Database") 31 | 32 | const program = Effect.gen(function* () { 33 | // 2. Use your database service 34 | const db = yield* Database 35 | const sourceCollection = Db.collection(db, "source", Person) 36 | const destinationCollection = Db.collection(db, "destination", Person) 37 | 38 | const items = yield* Collection.find(sourceCollection).pipe(FindCursor.toArray) 39 | yield* Collection.insertMany(destinationCollection, items) 40 | }) 41 | 42 | // 3. Create a layer for your service, using DbInstance higher-level API 43 | const DatabaseLive = DbInstance.layer( 44 | Database, 45 | { database: { name: "mydb" }, client: { url: "mongodb://localhost:27017" } } 46 | ) 47 | 48 | await program.pipe( 49 | // 4. Provide the layer to your program 50 | Effect.provide(DatabaseLive), 51 | Effect.runPromise 52 | ) 53 | ``` 54 | 55 | Find more examples in the [examples](./examples) folder. 56 | -------------------------------------------------------------------------------- /packages/services/dtslint/DbInstance.ts: -------------------------------------------------------------------------------- 1 | import * as DbInstance from "@effect-mongodb/services/DbInstance" 2 | import type * as DbService from "@effect-mongodb/services/DbService" 3 | import * as Config from "effect/Config" 4 | import type * as Context from "effect/Context" 5 | import * as Effect from "effect/Effect" 6 | import * as F from "effect/Function" 7 | import type { MongoClient as NativeMongoClient } from "mongodb" 8 | 9 | declare const db: DbService.Tag<"MyDb"> 10 | 11 | type SomeService = { url: string; name: string } 12 | declare const SomeService: Context.Tag 13 | 14 | // ------------------------------------------------------------------------------------- 15 | // layerEffect 16 | // ------------------------------------------------------------------------------------- 17 | 18 | // $ExpectType Layer, MongoError, never> 19 | DbInstance.layerEffect(db, Effect.succeed({ database: { name: "mydb" }, client: { url: "mongodb://localhost:27017" } })) 20 | 21 | // $ExpectType Layer, MongoError | ConfigError, never> 22 | DbInstance.layerEffect( 23 | db, 24 | Effect.gen(function*() { 25 | const databaseName = yield* Config.string("MONGO_DATABASE_NAME") 26 | const clientUrl = yield* Config.string("MONGO_CONNECTION_STRING") 27 | return { database: { name: databaseName }, client: { url: clientUrl } } 28 | }) 29 | ) 30 | 31 | const withRequirements = Effect.gen(function*() { 32 | const { url } = yield* SomeService 33 | const name = yield* Config.string("MONGO_DATABASE_NAME") 34 | return { database: { name }, client: { url } } 35 | }) 36 | // $ExpectType Layer, MongoError | ConfigError, SomeService> 37 | DbInstance.layerEffect(db, withRequirements) 38 | 39 | // ------------------------------------------------------------------------------------- 40 | // layer 41 | // ------------------------------------------------------------------------------------- 42 | 43 | // $ExpectType Layer, MongoError, never> 44 | DbInstance.layer(db, { database: { name: "mydb" }, client: { url: "mongodb://localhost:27017" } }) 45 | 46 | // ------------------------------------------------------------------------------------- 47 | // fromMongoClient 48 | // ------------------------------------------------------------------------------------- 49 | 50 | declare const legacyClient: NativeMongoClient 51 | 52 | // $ExpectType Layer, never, never> 53 | DbInstance.fromMongoClient(db, Effect.succeed({ database: { name: "mydb" }, client: legacyClient })) 54 | 55 | // $ExpectType Layer, ConfigError, never> 56 | DbInstance.fromMongoClient( 57 | db, 58 | F.pipe( 59 | Config.string("MONGO_DATABASE_NAME"), 60 | Effect.map((name) => ({ database: { name }, client: legacyClient })) 61 | ) 62 | ) 63 | 64 | // $ExpectType Layer, ConfigError, SomeService> 65 | DbInstance.fromMongoClient( 66 | db, 67 | Effect.gen(function*() { 68 | const { name: dbName } = yield* SomeService 69 | const name = yield* Config.string(dbName) 70 | return { database: { name }, client: legacyClient } 71 | }) 72 | ) 73 | -------------------------------------------------------------------------------- /packages/services/dtslint/DbService.ts: -------------------------------------------------------------------------------- 1 | import * as DbService from "@effect-mongodb/services/DbService" 2 | import type * as MongoClientService from "@effect-mongodb/services/MongoClientService" 3 | import * as Config from "effect/Config" 4 | import type * as Context from "effect/Context" 5 | import * as Effect from "effect/Effect" 6 | 7 | declare const mongoClient: MongoClientService.Tag<"MyMongoClient"> 8 | declare const usersDb: DbService.Tag<"MyDb"> 9 | 10 | type SomeService = { name: string } 11 | declare const SomeService: Context.Tag 12 | 13 | // ------------------------------------------------------------------------------------- 14 | // Tag 15 | // ------------------------------------------------------------------------------------- 16 | 17 | // $ExpectType Tag<"MyDb"> 18 | DbService.Tag("MyDb") 19 | 20 | // ------------------------------------------------------------------------------------- 21 | // layerEffect 22 | // ------------------------------------------------------------------------------------- 23 | 24 | // $ExpectType Layer, never, MongoClientService<"MyMongoClient">> 25 | DbService.layerEffect(usersDb, mongoClient, Effect.succeed("mydb")) 26 | 27 | // $ExpectType Layer, ConfigError, MongoClientService<"MyMongoClient">> 28 | DbService.layerEffect(usersDb, mongoClient, Config.string("DATABASE_NAME")) 29 | 30 | const withRequirements = SomeService.pipe(Effect.flatMap(({ name }) => Config.string(name))) 31 | // $ExpectType Layer, ConfigError, SomeService | MongoClientService<"MyMongoClient">> 32 | DbService.layerEffect(usersDb, mongoClient, withRequirements) 33 | 34 | // ------------------------------------------------------------------------------------- 35 | // layer 36 | // ------------------------------------------------------------------------------------- 37 | 38 | // $ExpectType Layer, never, MongoClientService<"MyMongoClient">> 39 | DbService.layer(usersDb, mongoClient, "mydb") 40 | -------------------------------------------------------------------------------- /packages/services/dtslint/MongoClientService.ts: -------------------------------------------------------------------------------- 1 | import * as MongoClientService from "@effect-mongodb/services/MongoClientService" 2 | import type * as MongoClient from "effect-mongodb/MongoClient" 3 | import * as Config from "effect/Config" 4 | import type * as Context from "effect/Context" 5 | import * as Effect from "effect/Effect" 6 | 7 | declare const mongoClient: MongoClientService.Tag<"MyMongoClient"> 8 | 9 | type SomeService = { url: string } 10 | declare const SomeService: Context.Tag 11 | 12 | // ------------------------------------------------------------------------------------- 13 | // Tag 14 | // ------------------------------------------------------------------------------------- 15 | 16 | // $ExpectType Tag<"MyMongoClient"> 17 | MongoClientService.Tag("MyMongoClient") 18 | 19 | // ------------------------------------------------------------------------------------- 20 | // layerEffect 21 | // ------------------------------------------------------------------------------------- 22 | 23 | // $ExpectType Layer, MongoError, never> 24 | MongoClientService.layerEffect(mongoClient, Effect.succeed("mongodb://localhost:27017")) 25 | 26 | // $ExpectType Layer, MongoError | ConfigError, never> 27 | MongoClientService.layerEffect(mongoClient, Config.string("DATABASE_NAME")) 28 | 29 | const withRequirements = SomeService.pipe(Effect.flatMap(({ url }) => Config.string(url))) 30 | // $ExpectType Layer, MongoError | ConfigError, SomeService> 31 | MongoClientService.layerEffect(mongoClient, withRequirements) 32 | 33 | // ------------------------------------------------------------------------------------- 34 | // layer 35 | // ------------------------------------------------------------------------------------- 36 | 37 | // $ExpectType Layer, MongoError, never> 38 | MongoClientService.layer(mongoClient, "mongodb://localhost:27017") 39 | 40 | // ------------------------------------------------------------------------------------- 41 | // fromMongoClient 42 | // ------------------------------------------------------------------------------------- 43 | 44 | declare const legacyClient: Effect.Effect 45 | 46 | // $ExpectType Layer, never, never> 47 | MongoClientService.fromMongoClient(mongoClient, legacyClient) 48 | 49 | declare const legacyClientWithError: Effect.Effect 50 | 51 | // $ExpectType Layer, Error, never> 52 | MongoClientService.fromMongoClient(mongoClient, legacyClientWithError) 53 | 54 | declare const legacyClientWithRequirements: Effect.Effect 55 | 56 | // $ExpectType Layer, Error, SomeService> 57 | MongoClientService.fromMongoClient(mongoClient, legacyClientWithRequirements) 58 | -------------------------------------------------------------------------------- /packages/services/dtslint/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../../tsconfig.base.json", 3 | "include": ["."], 4 | "compilerOptions": { 5 | "incremental": false, 6 | "composite": false, 7 | "noUnusedLocals": false 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /packages/services/examples/db-instance-legacy-mongo-client.ts: -------------------------------------------------------------------------------- 1 | import * as DbInstance from "@effect-mongodb/services/DbInstance" 2 | import * as DbService from "@effect-mongodb/services/DbService" 3 | import * as Collection from "effect-mongodb/Collection" 4 | import * as Db from "effect-mongodb/Db" 5 | import * as FindCursor from "effect-mongodb/FindCursor" 6 | import * as Effect from "effect/Effect" 7 | import * as Schema from "effect/Schema" 8 | import { MongoClient } from "mongodb" 9 | 10 | const Todo = Schema.Struct({ 11 | userId: Schema.Number, 12 | id: Schema.Number, 13 | title: Schema.String, 14 | completed: Schema.Boolean 15 | }) 16 | 17 | const MyDb = DbService.Tag("MyDb") 18 | 19 | const program = Effect.gen(function*() { 20 | const db = yield* MyDb 21 | const sourceCollection = Db.collection(db, "source", Todo) 22 | const destinationCollection = Db.collection(db, "destination", Todo) 23 | 24 | const items = yield* Collection.find(sourceCollection).pipe(FindCursor.toArray) 25 | 26 | yield* Collection.insertMany(destinationCollection, items) 27 | }) 28 | 29 | /*** main.ts ***/ 30 | 31 | class LegacyConnectionPool { 32 | private static instance: MongoClient | null = null 33 | 34 | static async mongoClient(url: string) { 35 | if (!this.instance) { 36 | this.instance = new MongoClient(url) 37 | await this.instance.connect() 38 | } 39 | return this.instance 40 | } 41 | 42 | static async close() { 43 | if (!this.instance) return 44 | await this.instance.close() 45 | } 46 | } 47 | 48 | const MyDbLive = DbInstance.fromMongoClient( 49 | MyDb, 50 | Effect.gen(function*() { 51 | const client = yield* Effect.promise(() => LegacyConnectionPool.mongoClient("mongodb://localhost:27017")) 52 | return { database: { name: "mydb" }, client } 53 | }) 54 | ) 55 | 56 | await program.pipe( 57 | Effect.provide(MyDbLive), 58 | Effect.runPromise 59 | ).then(() => LegacyConnectionPool.close()) 60 | -------------------------------------------------------------------------------- /packages/services/examples/db-instance-multiple.ts: -------------------------------------------------------------------------------- 1 | import * as DbInstance from "@effect-mongodb/services/DbInstance" 2 | import * as DbService from "@effect-mongodb/services/DbService" 3 | import * as Collection from "effect-mongodb/Collection" 4 | import * as Db from "effect-mongodb/Db" 5 | import * as FindCursor from "effect-mongodb/FindCursor" 6 | import * as Effect from "effect/Effect" 7 | import * as Layer from "effect/Layer" 8 | import * as Schema from "effect/Schema" 9 | 10 | const Todo = Schema.Struct({ 11 | userId: Schema.Number, 12 | id: Schema.Number, 13 | title: Schema.String, 14 | completed: Schema.Boolean 15 | }) 16 | 17 | const MainDb = DbService.Tag("MainDb") 18 | const ReplicaDb = DbService.Tag("ReplicaDb") 19 | 20 | const program = Effect.gen(function*() { 21 | const mainDb = yield* MainDb 22 | const newDb = yield* ReplicaDb 23 | 24 | const collectionName = "source" 25 | const mainCollection = Db.collection(mainDb, collectionName, Todo) 26 | const replicaCollection = Db.collection(newDb, collectionName, Todo) 27 | 28 | const items = yield* Collection.find(mainCollection).pipe(FindCursor.toArray) 29 | 30 | yield* Collection.insertMany(replicaCollection, items) 31 | }) 32 | 33 | /*** main.ts ***/ 34 | 35 | const MainDbLive = DbInstance.layer(MainDb, { 36 | database: { name: "mydb" }, 37 | client: { url: "mongodb://localhost:27017" } 38 | }) 39 | 40 | const ReplicaDbLive = DbInstance.layer(ReplicaDb, { 41 | database: { name: "mydb" }, 42 | client: { url: "mongodb://localhost:37017" } 43 | }) 44 | 45 | const MainLive = Layer.mergeAll(MainDbLive, ReplicaDbLive) 46 | 47 | await program.pipe( 48 | Effect.provide(MainLive), 49 | Effect.runPromise 50 | ) 51 | -------------------------------------------------------------------------------- /packages/services/examples/db-instance.ts: -------------------------------------------------------------------------------- 1 | import * as DbInstance from "@effect-mongodb/services/DbInstance" 2 | import * as DbService from "@effect-mongodb/services/DbService" 3 | import * as Collection from "effect-mongodb/Collection" 4 | import * as Db from "effect-mongodb/Db" 5 | import * as FindCursor from "effect-mongodb/FindCursor" 6 | import * as Config from "effect/Config" 7 | import * as Effect from "effect/Effect" 8 | import * as Schema from "effect/Schema" 9 | 10 | const Todo = Schema.Struct({ 11 | userId: Schema.Number, 12 | id: Schema.Number, 13 | title: Schema.String, 14 | completed: Schema.Boolean 15 | }) 16 | 17 | const MyDb = DbService.Tag("MyDb") 18 | 19 | const program = Effect.gen(function*() { 20 | const db = yield* MyDb 21 | const sourceCollection = Db.collection(db, "source", Todo) 22 | const destinationCollection = Db.collection(db, "destination", Todo) 23 | 24 | const items = yield* Collection.find(sourceCollection).pipe(FindCursor.toArray) 25 | 26 | yield* Collection.insertMany(destinationCollection, items) 27 | }) 28 | 29 | /*** main.ts ***/ 30 | 31 | const MyDbLive = DbInstance.layerEffect( 32 | MyDb, 33 | Effect.gen(function*() { 34 | const databaseName = yield* Config.string("MONGO_DATABASE_NAME") 35 | const clientUrl = yield* Config.string("MONGO_CONNECTION_STRING") 36 | return { 37 | database: { name: databaseName }, 38 | client: { url: clientUrl, timeoutMS: 5000 } 39 | } 40 | }) 41 | ) 42 | 43 | await program.pipe( 44 | Effect.provide(MyDbLive), 45 | Effect.runPromise 46 | ) 47 | -------------------------------------------------------------------------------- /packages/services/examples/layers.ts: -------------------------------------------------------------------------------- 1 | import * as DbService from "@effect-mongodb/services/DbService" 2 | import * as MongoClientService from "@effect-mongodb/services/MongoClientService" 3 | import * as Collection from "effect-mongodb/Collection" 4 | import * as Db from "effect-mongodb/Db" 5 | import * as FindCursor from "effect-mongodb/FindCursor" 6 | import * as Effect from "effect/Effect" 7 | import * as Schema from "effect/Schema" 8 | 9 | import * as Layer from "effect/Layer" 10 | 11 | const Todo = Schema.Struct({ 12 | userId: Schema.Number, 13 | id: Schema.Number, 14 | title: Schema.String, 15 | completed: Schema.Boolean 16 | }) 17 | 18 | const MyDb = DbService.Tag("MyDb") 19 | 20 | const program = Effect.gen(function*() { 21 | const db = yield* MyDb 22 | const sourceCollection = Db.collection(db, "source", Todo) 23 | const destinationCollection = Db.collection(db, "destination", Todo) 24 | 25 | const items = yield* Collection.find(sourceCollection).pipe(FindCursor.toArray) 26 | 27 | yield* Collection.insertMany(destinationCollection, items) 28 | }) 29 | 30 | /*** main.ts ***/ 31 | 32 | const MyMongoClient = MongoClientService.Tag("MyMongoClient") 33 | const MyDbLive = DbService.layer(MyDb, MyMongoClient, "mydb") 34 | const MyMongoClientLive = MongoClientService.layer(MyMongoClient, "mongodb://localhost:27017") 35 | 36 | const MainLive = Layer.provide(MyDbLive, MyMongoClientLive) 37 | 38 | await program.pipe( 39 | Effect.provide(MainLive), 40 | Effect.runPromise 41 | ) 42 | -------------------------------------------------------------------------------- /packages/services/examples/multiple-mongodb-instances.ts: -------------------------------------------------------------------------------- 1 | import * as DbService from "@effect-mongodb/services/DbService" 2 | import * as MongoClientService from "@effect-mongodb/services/MongoClientService" 3 | import * as Collection from "effect-mongodb/Collection" 4 | import * as Db from "effect-mongodb/Db" 5 | import * as FindCursor from "effect-mongodb/FindCursor" 6 | import * as Effect from "effect/Effect" 7 | import * as Schema from "effect/Schema" 8 | 9 | import * as Layer from "effect/Layer" 10 | 11 | const Todo = Schema.Struct({ 12 | userId: Schema.Number, 13 | id: Schema.Number, 14 | title: Schema.String, 15 | completed: Schema.Boolean 16 | }) 17 | 18 | const MainDb = DbService.Tag("MainDb") 19 | const ReplicaDb = DbService.Tag("ReplicaDb") 20 | 21 | const program = Effect.gen(function*() { 22 | const mainDb = yield* MainDb 23 | const newDb = yield* ReplicaDb 24 | 25 | const collectionName = "source" 26 | const mainCollection = Db.collection(mainDb, collectionName, Todo) 27 | const replicaCollection = Db.collection(newDb, collectionName, Todo) 28 | 29 | const items = yield* Collection.find(mainCollection).pipe(FindCursor.toArray) 30 | 31 | yield* Collection.insertMany(replicaCollection, items) 32 | }) 33 | 34 | /*** main.ts ***/ 35 | 36 | const MainMongoClient = MongoClientService.Tag("MainMongoClient") 37 | const MainDbLive = DbService.layer(MainDb, MainMongoClient, "mydb") 38 | const MainMongoClientLive = MongoClientService.layer(MainMongoClient, "mongodb://localhost:27017") 39 | 40 | const ReplicaMongoClient = MongoClientService.Tag("ReplicaMongoClient") 41 | const ReplicaDbLive = DbService.layer(ReplicaDb, ReplicaMongoClient, "mydb") 42 | const ReplicaMongoClientLive = MongoClientService.layer(ReplicaMongoClient, "mongodb://localhost:37017") 43 | 44 | const MainLive = Layer.mergeAll( 45 | Layer.provide(MainDbLive, MainMongoClientLive), 46 | Layer.provide(ReplicaDbLive, ReplicaMongoClientLive) 47 | ) 48 | 49 | await program.pipe( 50 | Effect.provide(MainLive), 51 | Effect.runPromise 52 | ) 53 | -------------------------------------------------------------------------------- /packages/services/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@effect-mongodb/services", 3 | "author": "doubleloop.io", 4 | "version": "0.2.1", 5 | "type": "module", 6 | "license": "MIT", 7 | "description": "Effect services for effect-mongodb", 8 | "homepage": "https://github.com/doubleloop-io/effect-mongodb", 9 | "repository": { 10 | "type": "git", 11 | "url": "https://github.com/doubleloop-io/effect-mongodb", 12 | "directory": "packages/services" 13 | }, 14 | "bugs": { 15 | "url": "https://github.com/doubleloop-io/effect-mongodb/issues" 16 | }, 17 | "tags": [ 18 | "typescript", 19 | "mongodb", 20 | "effect", 21 | "functional-programming", 22 | "effect-mongodb" 23 | ], 24 | "keywords": [ 25 | "typescript", 26 | "mongodb", 27 | "effect", 28 | "functional-programming", 29 | "effect-mongodb" 30 | ], 31 | "publishConfig": { 32 | "access": "public", 33 | "directory": "dist", 34 | "provenance": true 35 | }, 36 | "packageManager": "pnpm@9.4.0", 37 | "scripts": { 38 | "codegen": "build-utils prepare-v2", 39 | "build": "pnpm build-esm && pnpm build-annotate && pnpm build-cjs && build-utils pack-v2", 40 | "build-esm": "tsc -b tsconfig.build.json", 41 | "build-cjs": "babel build/esm --plugins @babel/transform-export-namespace-from --plugins @babel/transform-modules-commonjs --out-dir build/cjs --source-maps", 42 | "build-annotate": "babel build/esm --plugins annotate-pure-calls --out-dir build/esm --source-maps", 43 | "typecheck": "pnpm check", 44 | "typecheck:w": "pnpm check --watch", 45 | "dtslint": "dtslint dtslint", 46 | "check": "tsc -b tsconfig.json", 47 | "test": "vitest", 48 | "coverage": "vitest --coverage" 49 | }, 50 | "peerDependencies": { 51 | "effect": "^3.10.14", 52 | "mongodb": "^6.9.0", 53 | "effect-mongodb": "workspace:^" 54 | }, 55 | "devDependencies": { 56 | "effect": "^3.10.14", 57 | "mongodb": "^6.9.0", 58 | "effect-mongodb": "workspace:^", 59 | "@types/node": "^22.5.4" 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /packages/services/src/DbInstance.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 0.0.1 3 | */ 4 | import * as MongoClient from "effect-mongodb/MongoClient" 5 | import type * as MongoError from "effect-mongodb/MongoError" 6 | import * as Effect from "effect/Effect" 7 | import * as F from "effect/Function" 8 | import * as Layer from "effect/Layer" 9 | import type { DbOptions, MongoClient as MongoClient_ } from "mongodb" 10 | import * as DbService from "./DbService.js" 11 | import * as MongoClientService from "./MongoClientService.js" 12 | 13 | type DbInstanceOptions = { 14 | database: DbOptions & { name: string } 15 | client: MongoClient.MongoClientScopedOptions & { url: string } 16 | } 17 | 18 | export const layerEffect = ( 19 | dbTag: DbService.Tag, 20 | options: Effect.Effect 21 | ): Layer.Layer, MongoError.MongoError | E, R> => 22 | F.pipe( 23 | options, 24 | Effect.map((options) => layer(dbTag, options)), 25 | Layer.unwrapEffect 26 | ) 27 | 28 | export const layer = ( 29 | dbTag: DbService.Tag, 30 | options: DbInstanceOptions 31 | ): Layer.Layer, MongoError.MongoError> => { 32 | const { name: databaseName, ...databaseOptions } = options.database 33 | const { url: clientUrl, ...clientOptions } = options.client 34 | 35 | const dbLayer = DbService.layer(dbTag, DefaultMongoClient, databaseName, databaseOptions) 36 | const defaultClientLayer = MongoClientService.layer(DefaultMongoClient, clientUrl, clientOptions) 37 | return dbLayer.pipe(Layer.provide(defaultClientLayer)) 38 | } 39 | 40 | type DbInstanceOptionsWithClient = Pick & { client: MongoClient_ } 41 | 42 | export const fromMongoClient = ( 43 | dbTag: DbService.Tag, 44 | options: Effect.Effect 45 | ): Layer.Layer, E, R> => 46 | Layer.effect( 47 | dbTag, 48 | Effect.gen(function*() { 49 | const { client: client_, database: { name, ...dbOptions } } = yield* options 50 | const client = new MongoClient.MongoClient({ client: client_ }) 51 | const db = MongoClient.db(client, name, dbOptions) 52 | return dbTag.of(db as DbService.DbService) 53 | }) 54 | ) 55 | 56 | const DefaultMongoClient = MongoClientService.Tag("@effect-mongodb/services/DefaultMongoClient") 57 | -------------------------------------------------------------------------------- /packages/services/src/DbService.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 0.0.1 3 | */ 4 | import type * as Db from "effect-mongodb/Db" 5 | import * as MongoClient from "effect-mongodb/MongoClient" 6 | import type * as Brand from "effect/Brand" 7 | import * as Context from "effect/Context" 8 | import * as Effect from "effect/Effect" 9 | import * as Layer from "effect/Layer" 10 | import type { DbOptions } from "mongodb" 11 | import type * as MongoClientService from "./MongoClientService.js" 12 | 13 | export type DbService = Db.Db & Brand.Brand 14 | 15 | export type Tag = Context.Tag, DbService> 16 | export const Tag = (key: K): Tag => Context.GenericTag>(key) 17 | export type Service> = Context.Tag.Service 18 | 19 | export const layerEffect = ( 20 | dbTag: Tag, 21 | clientTag: MongoClientService.Tag, 22 | dbName: Effect.Effect, 23 | options?: DbOptions 24 | ): Layer.Layer, E, MongoClientService.MongoClientService | R> => 25 | Effect.gen(function*() { 26 | const dbname_ = yield* dbName 27 | return layer(dbTag, clientTag, dbname_, options) 28 | }).pipe(Layer.unwrapEffect) 29 | 30 | export const layer = ( 31 | dbTag: Tag, 32 | clientTag: MongoClientService.Tag, 33 | dbName: string, 34 | options?: DbOptions 35 | ): Layer.Layer, never, MongoClientService.MongoClientService> => 36 | Layer.effect( 37 | dbTag, 38 | Effect.gen(function*() { 39 | const client = yield* clientTag 40 | const db = MongoClient.db(client, dbName, options) 41 | return dbTag.of(db as DbService) // TODO fix cast using branded ctor 42 | }) 43 | ) 44 | -------------------------------------------------------------------------------- /packages/services/src/MongoClientService.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 0.0.1 3 | */ 4 | import * as MongoClient from "effect-mongodb/MongoClient" 5 | import type * as MongoError from "effect-mongodb/MongoError" 6 | import type * as Brand from "effect/Brand" 7 | import * as Context from "effect/Context" 8 | import * as Effect from "effect/Effect" 9 | import * as Layer from "effect/Layer" 10 | 11 | export type MongoClientService = MongoClient.MongoClient & Brand.Brand 12 | 13 | export type Tag = Context.Tag, MongoClientService> 14 | export const Tag = (key: K): Tag => Context.GenericTag>(key) 15 | export type Service> = Context.Tag.Service 16 | 17 | export const layerEffect = ( 18 | clientTag: Tag, 19 | url: Effect.Effect, 20 | options?: MongoClient.MongoClientScopedOptions 21 | ): Layer.Layer, MongoError.MongoError | E, R> => 22 | Effect.gen(function*() { 23 | const url_ = yield* url 24 | return layer(clientTag, url_, options) 25 | }).pipe(Layer.unwrapEffect) 26 | 27 | export const layer = ( 28 | clientTag: Tag, 29 | url: string, 30 | options?: MongoClient.MongoClientScopedOptions 31 | ): Layer.Layer, MongoError.MongoError> => 32 | Layer.scopedContext(Effect.gen(function*() { 33 | const client = yield* MongoClient.connectScoped(url, options) 34 | return Context.make(clientTag, client as MongoClientService) 35 | })) 36 | 37 | export const fromMongoClient = ( 38 | clientTag: Tag, 39 | mongoClient: Effect.Effect 40 | ): Layer.Layer, E, R> => 41 | Layer.effect( 42 | clientTag, 43 | Effect.map(mongoClient, (client) => client as MongoClientService) 44 | ) 45 | -------------------------------------------------------------------------------- /packages/services/src/index.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @since 0.0.1 3 | */ 4 | export * as DbInstance from "./DbInstance.js" 5 | 6 | /** 7 | * @since 0.0.1 8 | */ 9 | export * as DbService from "./DbService.js" 10 | 11 | /** 12 | * @since 0.0.1 13 | */ 14 | export * as MongoClientService from "./MongoClientService.js" 15 | -------------------------------------------------------------------------------- /packages/services/test/globalSetup/mongodb.ts: -------------------------------------------------------------------------------- 1 | import { MongoDBContainer } from "@testcontainers/mongodb" 2 | import type { StartedMongoDBContainer } from "@testcontainers/mongodb" 3 | import type { GlobalSetupContext } from "vitest/node" 4 | 5 | let container: StartedMongoDBContainer | undefined 6 | 7 | export async function setup({ provide }: GlobalSetupContext) { 8 | container = await new MongoDBContainer("mongo:6.0.16").start() 9 | 10 | provide("mongoConnectionString", container.getConnectionString()) 11 | if (process.env.EFFECT_MONGODB_DEBUG === "true") { 12 | console.log( 13 | `[EFFECT_MONGODB_DEBUG] MongoDB connection string with direct connection: '${container.getConnectionString()}'` 14 | ) 15 | } 16 | } 17 | 18 | export async function teardown() { 19 | await container?.stop() 20 | } 21 | 22 | declare module "vitest" { 23 | export interface ProvidedContext { 24 | mongoConnectionString: string 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /packages/services/test/support/describe-mongo.ts: -------------------------------------------------------------------------------- 1 | import * as Db from "effect-mongodb/Db" 2 | import * as Effect from "effect/Effect" 3 | import type { Db as Db_ } from "mongodb" 4 | import { MongoClient } from "mongodb" 5 | import { afterAll, beforeAll, describe, inject } from "vitest" 6 | 7 | type MongoContext = { 8 | client: Effect.Effect 9 | database: Effect.Effect 10 | _client: () => MongoClient 11 | _database: () => Db_ 12 | } 13 | 14 | export const describeMongo = ( 15 | suiteName: string, 16 | tests: (ctx: MongoContext) => void 17 | ) => { 18 | describe(suiteName, () => { 19 | let client: MongoClient 20 | let database: Db_ 21 | let databaseName: string 22 | 23 | beforeAll(async () => { 24 | client = new MongoClient(inject("mongoConnectionString"), { 25 | directConnection: true 26 | }) 27 | await client.connect() 28 | 29 | // https://www.mongodb.com/docs/v5.2/reference/limits/#mongodb-limit-Database-Name-Case-Sensitivity 30 | databaseName = suiteName.replace(/[/\\. "$*<>:|?]/g, "-") 31 | database = client.db(databaseName) 32 | 33 | const collections = await database.collections() 34 | await Promise.all(collections.map((x) => x.deleteMany({}))) 35 | }) 36 | 37 | afterAll(async () => { 38 | await client.close() 39 | }) 40 | 41 | tests({ 42 | _client: () => client, 43 | _database: () => database, 44 | client: Effect.sync(() => client), 45 | database: Effect.sync(() => new Db.Db({ db: database })) 46 | }) 47 | }) 48 | } 49 | -------------------------------------------------------------------------------- /packages/services/test/testcontainer.test.ts: -------------------------------------------------------------------------------- 1 | import { expect, test } from "vitest" 2 | import { describeMongo } from "./support/describe-mongo.js" 3 | 4 | describeMongo("test containers", (ctx) => { 5 | test("insert and find", async () => { 6 | const db = ctx._database() 7 | const users = db.collection("users") 8 | 9 | await users.insertMany([ 10 | { id: 1, name: "ANY_NAME_1" }, 11 | { id: 2, name: "ANY_NAME_2" }, 12 | { id: 3, name: "ANY_NAME_3" } 13 | ]) 14 | 15 | const result = await users.find({}).toArray() 16 | 17 | expect(result).toEqual([ 18 | expect.objectContaining({ id: 1, name: "ANY_NAME_1" }), 19 | expect.objectContaining({ id: 2, name: "ANY_NAME_2" }), 20 | expect.objectContaining({ id: 3, name: "ANY_NAME_3" }) 21 | ]) 22 | }) 23 | }) 24 | -------------------------------------------------------------------------------- /packages/services/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.src.json", 3 | "references": [{ "path": "../effect-mongodb/tsconfig.build.json" }], 4 | "compilerOptions": { 5 | "tsBuildInfoFile": ".tsbuildinfo/build.tsbuildinfo", 6 | "outDir": "build/esm", 7 | "declarationDir": "build/dts", 8 | "stripInternal": true 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/services/tsconfig.examples.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "include": ["examples"], 4 | "references": [{ "path": "tsconfig.src.json" }], 5 | "compilerOptions": { 6 | "tsBuildInfoFile": ".tsbuildinfo/examples.tsbuildinfo", 7 | "rootDir": "examples", 8 | "noEmit": true, 9 | "noUnusedLocals": false 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /packages/services/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "include": [], 4 | "references": [ 5 | { "path": "tsconfig.src.json" }, 6 | { "path": "tsconfig.test.json" }, 7 | { "path": "tsconfig.examples.json" } 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /packages/services/tsconfig.src.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "include": ["src"], 4 | "references": [{ "path": "../effect-mongodb" }], 5 | "compilerOptions": { 6 | "tsBuildInfoFile": ".tsbuildinfo/src.tsbuildinfo", 7 | "rootDir": "src", 8 | "outDir": "build/src" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /packages/services/tsconfig.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.base.json", 3 | "include": ["test"], 4 | "references": [ 5 | { "path": "tsconfig.src.json" }, 6 | { "path": "../effect-mongodb" } 7 | ], 8 | "compilerOptions": { 9 | "tsBuildInfoFile": ".tsbuildinfo/test.tsbuildinfo", 10 | "rootDir": "test", 11 | "noEmit": true, 12 | "exactOptionalPropertyTypes": false, 13 | "noUnusedLocals": false 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /packages/services/vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { mergeConfig, type UserConfigExport } from "vitest/config" 2 | import shared from "../../vitest.shared.js" 3 | 4 | const config: UserConfigExport = { 5 | test: { 6 | globalSetup: ["./test/globalSetup/mongodb.ts"] 7 | } 8 | } 9 | 10 | export default mergeConfig(shared, config) 11 | -------------------------------------------------------------------------------- /patches/babel-plugin-annotate-pure-calls@0.4.0.patch: -------------------------------------------------------------------------------- 1 | diff --git a/lib/index.js b/lib/index.js 2 | index 2182884e21874ebb37261e2375eec08ad956fc9a..ef5630199121c2830756e00c7cc48cf1078c8207 100644 3 | --- a/lib/index.js 4 | +++ b/lib/index.js 5 | @@ -78,7 +78,7 @@ const isInAssignmentContext = path => { 6 | 7 | parentPath = _ref.parentPath; 8 | 9 | - if (parentPath.isVariableDeclaration() || parentPath.isAssignmentExpression()) { 10 | + if (parentPath.isVariableDeclaration() || parentPath.isAssignmentExpression() || parentPath.isClassDeclaration()) { 11 | return true; 12 | } 13 | } while (parentPath !== statement); 14 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - packages/* 3 | -------------------------------------------------------------------------------- /scripts/circular.mjs: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-undef */ 2 | import * as glob from "glob" 3 | import madge from "madge" 4 | 5 | madge( 6 | glob.globSync(["packages/*/src/**/*.ts"]), 7 | { 8 | detectiveOptions: { 9 | ts: { 10 | skipTypeImports: true 11 | } 12 | } 13 | } 14 | ).then((res) => { 15 | const circular = res.circular() 16 | if (circular.length) { 17 | console.error("Circular dependencies found") 18 | console.error(circular) 19 | process.exit(1) 20 | } 21 | }) 22 | -------------------------------------------------------------------------------- /scripts/clean.mjs: -------------------------------------------------------------------------------- 1 | import * as Glob from "glob" 2 | import * as Fs from "node:fs" 3 | 4 | const dirs = [".", ...Glob.sync("packages/*/")] 5 | dirs.forEach((pkg) => { 6 | const files = [".tsbuildinfo", "docs", "build", "dist", "coverage"] 7 | 8 | files.forEach((file) => { 9 | if (pkg === "." && file === "docs") { 10 | return 11 | } 12 | 13 | Fs.rmSync(`${pkg}/${file}`, { recursive: true, force: true }, () => {}) 14 | }) 15 | }) 16 | 17 | Glob.sync("docs/*/").forEach((dir) => { 18 | Fs.rmSync(dir, { recursive: true, force: true }, () => {}) 19 | }) 20 | -------------------------------------------------------------------------------- /scripts/docs.mjs: -------------------------------------------------------------------------------- 1 | import * as Fs from "node:fs" 2 | import * as Path from "node:path" 3 | 4 | function packages() { 5 | return Fs.readdirSync("packages") 6 | .filter((_) => Fs.existsSync(Path.join("packages", _, "docs/modules"))) 7 | } 8 | 9 | function pkgName(pkg) { 10 | const packageJson = Fs.readFileSync( 11 | Path.join("packages", pkg, "package.json") 12 | ) 13 | return JSON.parse(packageJson).name 14 | } 15 | 16 | function copyFiles(pkg) { 17 | const name = pkgName(pkg) 18 | const docs = Path.join("packages", pkg, "docs/modules") 19 | const dest = Path.join("docs", pkg) 20 | const files = Fs.readdirSync(docs, { withFileTypes: true }) 21 | 22 | function handleFiles(root, files) { 23 | for (const file of files) { 24 | const path = Path.join(docs, root, file.name) 25 | const destPath = Path.join(dest, root, file.name) 26 | 27 | if (file.isDirectory()) { 28 | Fs.mkdirSync(destPath, { recursive: true }) 29 | handleFiles(Path.join(root, file.name), Fs.readdirSync(path, { withFileTypes: true })) 30 | continue 31 | } 32 | 33 | const content = Fs.readFileSync(path, "utf8").replace( 34 | /^parent: Modules$/m, 35 | `parent: "${name}"` 36 | ) 37 | Fs.writeFileSync(destPath, content) 38 | } 39 | } 40 | 41 | Fs.rmSync(dest, { recursive: true, force: true }) 42 | Fs.mkdirSync(dest, { recursive: true }) 43 | handleFiles("", files) 44 | } 45 | 46 | function generateIndex(pkg, order) { 47 | const name = pkgName(pkg) 48 | const content = `--- 49 | title: "${name}" 50 | has_children: true 51 | permalink: /docs/${pkg} 52 | nav_order: ${order} 53 | --- 54 | ` 55 | 56 | Fs.writeFileSync(Path.join("docs", pkg, "index.md"), content) 57 | } 58 | 59 | packages().forEach((pkg, i) => { 60 | Fs.rmSync(Path.join("docs", pkg), { recursive: true, force: true }) 61 | Fs.mkdirSync(Path.join("docs", pkg), { recursive: true }) 62 | copyFiles(pkg) 63 | generateIndex(pkg, i + 2) 64 | }) 65 | -------------------------------------------------------------------------------- /scripts/version.mjs: -------------------------------------------------------------------------------- 1 | import * as Fs from "node:fs" 2 | import Package from "../packages/effect-mongodb/package.json" assert { type: "json" } 3 | 4 | const tpl = Fs.readFileSync("./scripts/version.template.txt").toString("utf8") 5 | 6 | Fs.writeFileSync( 7 | "packages/effect-mongodb/src/internal/version.ts", 8 | tpl.replace("VERSION", Package.version) 9 | ) 10 | -------------------------------------------------------------------------------- /scripts/version.template.txt: -------------------------------------------------------------------------------- 1 | let moduleVersion = "VERSION" 2 | 3 | export const getCurrentVersion = () => moduleVersion 4 | 5 | export const setCurrentVersion = (version: string) => { 6 | moduleVersion = version 7 | } 8 | -------------------------------------------------------------------------------- /tsconfig.base.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "strict": true, 4 | "exactOptionalPropertyTypes": true, 5 | "moduleDetection": "force", 6 | "composite": true, 7 | "downlevelIteration": true, 8 | "resolveJsonModule": true, 9 | "esModuleInterop": false, 10 | "declaration": true, 11 | "skipLibCheck": true, 12 | "emitDecoratorMetadata": true, 13 | "experimentalDecorators": true, 14 | "moduleResolution": "NodeNext", 15 | "lib": ["ES2022"], 16 | "types": ["node"], 17 | "isolatedModules": true, 18 | "sourceMap": true, 19 | "declarationMap": true, 20 | "noImplicitReturns": false, 21 | "noUnusedLocals": true, 22 | "noUnusedParameters": false, 23 | "noFallthroughCasesInSwitch": true, 24 | "noEmitOnError": false, 25 | "noErrorTruncation": false, 26 | "allowJs": false, 27 | "checkJs": false, 28 | "forceConsistentCasingInFileNames": true, 29 | "noImplicitAny": true, 30 | "noImplicitThis": true, 31 | "noUncheckedIndexedAccess": false, 32 | "strictNullChecks": true, 33 | "baseUrl": ".", 34 | "target": "ES2022", 35 | "module": "NodeNext", 36 | "incremental": true, 37 | "removeComments": false, 38 | "plugins": [{ "name": "@effect/language-service" }], 39 | "paths": { 40 | "effect-mongodb": ["./packages/effect-mongodb/src/index.js"], 41 | "effect-mongodb/*": ["./packages/effect-mongodb/src/*.js"], 42 | "effect-mongodb/test/*": ["./packages/effect-mongodb/test/*.js"], 43 | "@effect-mongodb/services": ["./packages/services/src/index.js"], 44 | "@effect-mongodb/services/*": ["./packages/services/src/*.js"], 45 | "@effect-mongodb/services/test/*": ["./packages/services/test/*.js"] 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.base.json", 3 | "include": [], 4 | "references": [ 5 | { "path": "packages/effect-mongodb/tsconfig.build.json" }, 6 | { "path": "packages/services/tsconfig.build.json" }, 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.base.json", 3 | "include": [], 4 | "references": [ 5 | { "path": "packages/effect-mongodb" }, 6 | { "path": "packages/services" }, 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /vitest.shared.ts: -------------------------------------------------------------------------------- 1 | import * as path from "node:path" 2 | import type { UserConfig } from "vitest/config" 3 | 4 | const alias = (pkg: string, dir = pkg) => { 5 | const name = pkg === "effect-mongodb" ? "effect-mongodb" : `@effect-mongodb/${pkg}` 6 | const target = process.env.TEST_DIST !== undefined ? "dist/dist/esm" : "src" 7 | return ({ 8 | [`${name}/test`]: path.join(__dirname, "packages", dir, "test"), 9 | [`${name}`]: path.join(__dirname, "packages", dir, target) 10 | }) 11 | } 12 | 13 | const config: UserConfig = { 14 | esbuild: { 15 | target: "es2020" 16 | }, 17 | test: { 18 | include: ["test/**/*.test.ts"], 19 | fakeTimers: { 20 | toFake: undefined 21 | }, 22 | sequence: { 23 | concurrent: true 24 | }, 25 | alias: { 26 | ...alias("effect-mongodb"), 27 | ...alias("services") 28 | } 29 | } 30 | } 31 | export default config 32 | -------------------------------------------------------------------------------- /vitest.workspace.ts: -------------------------------------------------------------------------------- 1 | import { defineWorkspace } from "vitest/config" 2 | 3 | export default defineWorkspace([ 4 | "packages/*" 5 | ]) 6 | --------------------------------------------------------------------------------