diff --git a/.changeset/pink-tigers-drum.md b/.changeset/pink-tigers-drum.md
new file mode 100644
index 0000000..af9ccca
--- /dev/null
+++ b/.changeset/pink-tigers-drum.md
@@ -0,0 +1,5 @@
+---
+"@codelytv/criteria-to-mongo": major
+---
+
+add criteria to mongo converter
diff --git a/README.md b/README.md
index cab1894..aa5ef04 100644
--- a/README.md
+++ b/README.md
@@ -33,6 +33,7 @@ Create a Criteria from:
Convert a Criteria to:
- [Elasticsearch (and esql)](./packages/criteria-to-elasticsearch)
- [MySql](./packages/criteria-to-mysql)
+- [MongoDB](./packages/criteria-to-mongo)
You can also create your custom transformer.
diff --git a/packages/criteria-to-mongo/README.md b/packages/criteria-to-mongo/README.md
new file mode 100644
index 0000000..98c9cb5
--- /dev/null
+++ b/packages/criteria-to-mongo/README.md
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+ 🎼 Criteria to MongoDB converter
+
+
+
+
+
+
+## 📥 Installation
+
+```sh
+npm i @codelytv/criteria-to-mongo
+```
diff --git a/packages/criteria-to-mongo/package.json b/packages/criteria-to-mongo/package.json
new file mode 100644
index 0000000..cd2db70
--- /dev/null
+++ b/packages/criteria-to-mongo/package.json
@@ -0,0 +1,20 @@
+{
+ "name": "@codelytv/criteria-to-mongo",
+ "version": "1.0.0",
+ "description": "",
+ "keywords": [],
+ "author": "Codely (https://codely.com)",
+ "license": "MIT",
+ "main": "dist/index.js",
+ "types": "dist/index.d.ts",
+ "scripts": {
+ "test": "node --import tsx --test test/*.test.ts",
+ "build": "tsc --build --verbose tsconfig.json"
+ },
+ "dependencies": {
+ "@codelytv/criteria": "workspace:^"
+ },
+ "devDependencies": {
+ "@codelytv/criteria-test-mother": "workspace:^"
+ }
+}
diff --git a/packages/criteria-to-mongo/src/CriteriaToMongoConverter.ts b/packages/criteria-to-mongo/src/CriteriaToMongoConverter.ts
new file mode 100644
index 0000000..8384a24
--- /dev/null
+++ b/packages/criteria-to-mongo/src/CriteriaToMongoConverter.ts
@@ -0,0 +1,108 @@
+import { Criteria, Filter, OrderTypes } from "@codelytv/criteria";
+
+type MongoFilterOperator = "$eq" | "$ne" | "$gt" | "$gte" | "$lt" | "$lte" | "$regex";
+type MongoFilterOperation = {
+ [operator in MongoFilterOperator]?: unknown;
+};
+
+type MongoFilter =
+ | {
+ [field: string]: MongoFilterOperation;
+ }
+ | {
+ [field: string]: { $not: MongoFilterOperation };
+ };
+
+type MongoSortDirection = 1 | -1;
+
+type MongoSort = {
+ [field: string]: MongoSortDirection;
+};
+
+export type MongoQuery = {
+ filter: MongoFilter;
+ sort?: MongoSort;
+ skip?: number;
+ limit?: number;
+};
+
+export class CriteriaToMongoConverter {
+ convert(criteria: Criteria): MongoQuery {
+ const query: MongoQuery = {
+ filter: {},
+ };
+
+ if (criteria.hasFilters()) {
+ query.filter = criteria.filters.value.reduce((acc, filter) => {
+ return { ...acc, ...this.generateMongoFilter(filter) };
+ }, {});
+ }
+
+ if (criteria.hasOrder()) {
+ query.sort = {
+ [criteria.order.orderBy.value]: criteria.order.orderType.value === OrderTypes.ASC ? 1 : -1,
+ };
+ }
+
+ if (criteria.pageSize !== null) {
+ query.limit = criteria.pageSize;
+ }
+
+ if (criteria.pageSize !== null && criteria.pageNumber !== null) {
+ query.skip = criteria.pageSize * (criteria.pageNumber - 1);
+ }
+
+ return query;
+ }
+
+ private generateMongoFilter(filter: Filter): MongoFilter {
+ const field = filter.field.value;
+ const value = filter.value.value;
+
+ if (filter.operator.isContains()) {
+ return {
+ [field]: { $regex: value },
+ };
+ }
+
+ if (filter.operator.isNotContains()) {
+ return {
+ [field]: { $not: { $regex: value } },
+ };
+ }
+
+ if (filter.operator.isGreaterThan()) {
+ return {
+ [field]: { $gt: value },
+ };
+ }
+
+ if (filter.operator.isGreaterThanOrEqual()) {
+ return {
+ [field]: { $gte: value },
+ };
+ }
+
+ if (filter.operator.isLowerThan()) {
+ return {
+ [field]: { $lt: value },
+ };
+ }
+
+ if (filter.operator.isLowerThanOrEqual()) {
+ return {
+ [field]: { $lte: value },
+ };
+ }
+
+ if (filter.operator.isNotEquals()) {
+ return {
+ [field]: { $ne: value },
+ };
+ }
+
+ return {
+ [field]: { $eq: value },
+ };
+ }
+}
diff --git a/packages/criteria-to-mongo/src/index.ts b/packages/criteria-to-mongo/src/index.ts
new file mode 100644
index 0000000..0fa5cef
--- /dev/null
+++ b/packages/criteria-to-mongo/src/index.ts
@@ -0,0 +1 @@
+export * from "./CriteriaToMongoConverter";
diff --git a/packages/criteria-to-mongo/test/CriteriaToMongoConverter.test.ts b/packages/criteria-to-mongo/test/CriteriaToMongoConverter.test.ts
new file mode 100644
index 0000000..95e3434
--- /dev/null
+++ b/packages/criteria-to-mongo/test/CriteriaToMongoConverter.test.ts
@@ -0,0 +1,183 @@
+import assert from "node:assert";
+import { describe, it } from "node:test";
+
+import { CriteriaMother } from "@codelytv/criteria-test-mother";
+
+import { CriteriaToMongoConverter } from "../src";
+
+describe("CriteriaToMongoConverter should", () => {
+ const converter = new CriteriaToMongoConverter();
+
+ it("Generate simple select with an empty criteria", () => {
+ const actualQuery = converter.convert(CriteriaMother.empty());
+
+ assert.deepEqual(actualQuery, { filter: {} });
+ });
+
+ it("Generate select with order", () => {
+ const actualQuery = converter.convert(CriteriaMother.emptySorted("_id", "DESC"));
+
+ assert.deepEqual(actualQuery, {
+ filter: {},
+ sort: { _id: -1 },
+ });
+ });
+
+ it("Generate select with one filter", () => {
+ const actualQuery = converter.convert(CriteriaMother.withOneFilter("name", "EQUAL", "Javier"));
+
+ assert.deepEqual(actualQuery, {
+ filter: { name: { $eq: "Javier" } },
+ });
+ });
+
+ it("Generate select with one greater than filter", () => {
+ const actualQuery = converter.convert(
+ CriteriaMother.withOneFilter("age", "GREATER_THAN", "25"),
+ );
+
+ assert.deepEqual(actualQuery, {
+ filter: { age: { $gt: 25 } },
+ });
+ });
+
+ it("Generate select with one greater than or equal filter", () => {
+ const actualQuery = converter.convert(
+ CriteriaMother.withOneFilter("age", "GREATER_THAN_OR_EQUAL", "25"),
+ );
+
+ assert.deepEqual(actualQuery, {
+ filter: { age: { $gte: 25 } },
+ });
+ });
+
+ it("Generate select with one lower than filter", () => {
+ const actualQuery = converter.convert(CriteriaMother.withOneFilter("age", "LOWER_THAN", "18"));
+
+ assert.deepEqual(actualQuery, {
+ filter: { age: { $lt: 18 } },
+ });
+ });
+
+ it("Generate select with one lower than or equal filter", () => {
+ const actualQuery = converter.convert(
+ CriteriaMother.withOneFilter("age", "LOWER_THAN_OR_EQUAL", "18"),
+ );
+
+ assert.deepEqual(actualQuery, {
+ filter: { age: { $lte: 18 } },
+ });
+ });
+
+ it("Generate select with one filter sorted", () => {
+ const actualQuery = converter.convert(
+ CriteriaMother.withOneFilterSorted("name", "EQUAL", "Javier", "_id", "DESC"),
+ );
+
+ assert.deepEqual(actualQuery, {
+ filter: { name: { $eq: "Javier" } },
+ sort: { _id: -1 },
+ });
+ });
+
+ it("Generate select with multiples filters", () => {
+ const actualQuery = converter.convert(
+ CriteriaMother.create({
+ filters: [
+ {
+ field: "name",
+ operator: "EQUAL",
+ value: "Javier",
+ },
+ {
+ field: "email",
+ operator: "EQUAL",
+ value: "javier@terra.es",
+ },
+ ],
+ orderBy: null,
+ orderType: null,
+ pageSize: null,
+ pageNumber: null,
+ }),
+ );
+
+ assert.deepEqual(actualQuery, {
+ filter: {
+ name: { $eq: "Javier" },
+ email: { $eq: "javier@terra.es" },
+ },
+ });
+ });
+
+ it("Generate select with multiples filters and sort", () => {
+ const actualQuery = converter.convert(
+ CriteriaMother.create({
+ filters: [
+ {
+ field: "name",
+ operator: "EQUAL",
+ value: "Javier",
+ },
+ {
+ field: "email",
+ operator: "EQUAL",
+ value: "javier@terra.es",
+ },
+ ],
+ orderBy: "_id",
+ orderType: "DESC",
+ pageSize: null,
+ pageNumber: null,
+ }),
+ );
+
+ assert.deepEqual(actualQuery, {
+ filter: {
+ name: { $eq: "Javier" },
+ email: { $eq: "javier@terra.es" },
+ },
+ sort: { _id: -1 },
+ });
+ });
+
+ it("Generate select with one contains filter", () => {
+ const actualQuery = converter.convert(
+ CriteriaMother.withOneFilter("name", "CONTAINS", "Javier"),
+ );
+
+ assert.deepEqual(actualQuery, {
+ filter: { name: { $regex: "Javier" } },
+ });
+ });
+
+ it("Generate select with one not contains filter", () => {
+ const actualQuery = converter.convert(
+ CriteriaMother.withOneFilter("name", "NOT_CONTAINS", "Javier"),
+ );
+
+ assert.deepEqual(actualQuery, {
+ filter: { name: { $not: { $regex: "Javier" } } },
+ });
+ });
+
+ it("Generate simple select paginated", () => {
+ const actualQuery = converter.convert(CriteriaMother.emptyPaginated(10, 3));
+
+ assert.deepEqual(actualQuery, {
+ filter: {},
+ limit: 10,
+ skip: 20,
+ });
+ });
+
+ it("Generate select with not equal filter", () => {
+ const actualQuery = converter.convert(
+ CriteriaMother.withOneFilter("name", "NOT_EQUAL", "Javier"),
+ );
+
+ assert.deepEqual(actualQuery, {
+ filter: { name: { $ne: "Javier" } },
+ });
+ });
+});
diff --git a/packages/criteria-to-mongo/tsconfig.json b/packages/criteria-to-mongo/tsconfig.json
new file mode 100644
index 0000000..e0d6637
--- /dev/null
+++ b/packages/criteria-to-mongo/tsconfig.json
@@ -0,0 +1,7 @@
+{
+ "extends": "../../tsconfig.json",
+ "compilerOptions": {
+ "outDir": "dist"
+ },
+ "include": ["src/**/*"]
+}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 07af765..2121ea6 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -75,6 +75,16 @@ importers:
specifier: workspace:^
version: link:../criteria-test-mother
+ packages/criteria-to-mongo:
+ dependencies:
+ '@codelytv/criteria':
+ specifier: workspace:^
+ version: link:../criteria
+ devDependencies:
+ '@codelytv/criteria-test-mother':
+ specifier: workspace:^
+ version: link:../criteria-test-mother
+
packages/criteria-to-mysql:
dependencies:
'@codelytv/criteria':