{"id":22138892,"url":"https://github.com/stoqey/sofa","last_synced_at":"2026-05-19T03:19:19.444Z","repository":{"id":54549420,"uuid":"332723025","full_name":"stoqey/sofa","owner":"stoqey","description":"Super Simple Couchbase ORM","archived":false,"fork":false,"pushed_at":"2021-09-15T21:27:17.000Z","size":2446,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-07-01T14:42:59.464Z","etag":null,"topics":["couchbase","couchbase-community","couchbase-orm-utility","couchbase-server","mongoose","orm-framework","pagination","sofa"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/stoqey.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null},"funding":{"github":"ceddybi","patreon":null,"open_collective":null,"ko_fi":null,"tidelift":null,"community_bridge":null,"liberapay":null,"issuehunt":null,"otechie":null,"custom":null}},"created_at":"2021-01-25T11:21:38.000Z","updated_at":"2021-09-15T21:27:18.000Z","dependencies_parsed_at":"2022-08-13T19:20:44.102Z","dependency_job_id":null,"html_url":"https://github.com/stoqey/sofa","commit_stats":null,"previous_names":[],"tags_count":26,"template":false,"template_full_name":null,"purl":"pkg:github/stoqey/sofa","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stoqey%2Fsofa","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stoqey%2Fsofa/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stoqey%2Fsofa/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stoqey%2Fsofa/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/stoqey","download_url":"https://codeload.github.com/stoqey/sofa/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stoqey%2Fsofa/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":265248543,"owners_count":23734255,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["couchbase","couchbase-community","couchbase-orm-utility","couchbase-server","mongoose","orm-framework","pagination","sofa"],"created_at":"2024-12-01T20:12:15.633Z","updated_at":"2026-05-19T03:19:14.422Z","avatar_url":"https://github.com/stoqey.png","language":"TypeScript","funding_links":["https://github.com/sponsors/ceddybi"],"categories":[],"sub_categories":[],"readme":"\n\u003cp align=\"center\"\u003e\n  \u003ch1 align=\"center\"\u003e Sofa - Simple Couchbase ORM\u003c/h1\u003e\n\u003c/p\u003e\n\n\n\u003cdiv align=\"center\"\u003e\n\n\u003cimg width=\"400px\" src=\"./docs/sofa.jpg\"\u003e\u003c/img\u003e\n\n\u003cdiv style=\"display: flex;justify-content:center;\"\u003e\n\n\u003cimg alt=\"NPM\" src=\"https://img.shields.io/npm/dt/@stoqey/sofa.svg\"\u003e\u003c/img\u003e\n \n\n\u003c/div\u003e\n\n\u003c/div\u003e\n\n\nSofa is a super simple Couchbase ORM\n\n\n## 1. Install\n```bash\nnpm i @stoqey/sofa --save\n```\n\n## 2. Start couchbase\n```ts\nimport { startSofa } from '@stoqey/sofa';\n\n const started = await startSofa({\n    connectionString: 'couchbase://localhost',\n    username: 'admin',\n    password: '123456',\n    bucketName: 'stq'\n })\n```\n\n## 3. Create a Model and start using it\n\n```ts\nimport { Model } from '@stoqey/sofa';\n\nconst userModel = new Model('User');\n\n\n// Create document\nconst created = await userModel.create({\n    username: 'ceddy',\n    password: 'love couchbase',\n});\n\n// Find document\nconst foundData = await userModel.findById(created.id);\n\n// update document\nconst updatedData = await userModel.updateById(created.id, { ...created, someValue: 'x' });\n\n// delete\nconst deletedData = await userModel.delete(created.id);\n\n```\n\n## 4. Pagination\n\nAll models come with a method for automatic pagination \n```ts\nconst paginationData = await model.pagination({\n    select: [\"id\", \"email\", \"phone\",\"fullname\"],\n    where: { \n        userId: { $eq: \"ceddy\" },\n        $or: [{ userId: { $eq: \"ceddy\" } }, { phone: 10 }],\n        },\n    limit: 100,\n    page: 0,\n});\n```\n\nwhich translates to this query \n\n```sql\nSELECT * FROM `stq` WHERE _type=\"User\" AND userId=\"ceddy\" AND (userId=\"ceddy\" OR phone=10) ORDER BY createdAt DESC LIMIT 100 OFFSET 0\n```\n\n\nPagination results\n\n```js\n[\n  {\n    id: '209d3143-09b7-4b3d-bf7d-f0ccd3f98922',\n    updatedAt: 2021-01-26T01:51:43.218Z,\n    createdAt: 2021-01-26T01:51:43.210Z,\n    _type: 'User',\n    userId: 'ceddy',\n    password: '...',\n    someValue: 'x'\n  },\n  {\n    id: '1392e4f6-ae1e-4e01-b7d5-103bdd0e843f',\n    updatedAt: 2021-01-26T01:51:29.591Z,\n    createdAt: 2021-01-26T01:51:29.583Z,\n    _type: 'User',\n    userId: 'ceddy',\n    password: '...',\n    someValue: 'x'\n  }\n]\n```\n\n\n## 5. Custom queries \u0026 Query builder\nQuery builder is inspired from node-ottoman, for more examples, please see https://ottomanjs.com/guides/query-builder.html#query-builder\n\n```ts\nimport { Query } from '@stoqey/sofa';\n\nconst params = {\n  select: [\n    {\n      $count: {\n        $field: {\n          name: 'type',\n        },\n        as: 'odm',\n      },\n    },\n  ],\n  let: [\n    { key: 'amount_val', value: 10 },\n    { key: 'size_val', value: 20 },\n  ],\n  where: {\n    $or: [{ price: { $gt: 'amount_val', $isNotNull: true } }, { auto: { $gt: 10 } }, { amount: 10 }],\n    $and: [\n      { price2: { $gt: 1.99, $isNotNull: true } },\n      { $or: [{ price3: { $gt: 1.99, $isNotNull: true } }, { id: '20' }] },\n    ],\n    $any: {\n      $expr: [{ $in: { search_expr: 'search', target_expr: 'address' } }],\n      $satisfied: { address: '10' },\n    },\n    $in: { search_expr: 'search', target_expr: ['address'] },\n  },\n  groupBy: [{ expr: 'type', as: 'sch' }],\n  letting: [\n    { key: 'amount_v2', value: 10 },\n    { key: 'size_v2', value: 20 },\n  ],\n  having: { type: { $like: '%hotel%' } },\n  orderBy: { type: 'DESC' },\n  limit: 10,\n  offset: 1,\n  use: ['airlineR_8093', 'airlineR_8094'],\n};\n\nconst query = new Query(params, 'travel-sample').build();\nconsole.log(query);\n\n```\n\nwhich translates to\n\n```sql\nSELECT COUNT(type) AS odm FROM travel-sample USE KEYS [\"airlineR_8093\",\"airlineR_8094\"] LET amount_val=10,size_val=20 WHERE ((price\u003eamount_val AND price IS NOT NULL) OR auto\u003e10 OR amount=10) AND ((price2\u003e1.99 AND price2 IS NOT NULL) AND ((price3\u003e1.99 AND price3 IS NOT NULL) OR id=\"20\")) AND ANY search IN address SATISFIES address=\"10\" END AND search IN [\"address\"] GROUP BY type AS sch LETTING amount_v2=10,size_v2=20 HAVING type LIKE \"%hotel%\" ORDER BY type DESC LIMIT 10 OFFSET 1\n```\n\n\n### Another example using functions\n\n```ts\nconst select = [\n  {\n    $count: {\n      $field: {\n        name: 'type',\n      },\n      as: 'odm',\n    },\n  },\n  {\n    $max: {\n      $field: 'amount',\n    },\n  },\n];\nconst letExpr = [\n  { key: 'amount_val', value: 10 },\n  { key: 'size_val', value: 20 },\n];\nconst where = {\n  $or: [{ price: { $gt: 'amount_val', $isNotNull: true } }, { auto: { $gt: 10 } }, { amount: 10 }],\n  $and: [\n    { price2: { $gt: 1.99, $isNotNull: true } },\n    { $or: [{ price3: { $gt: 1.99, $isNotNull: true } }, { id: '20' }] },\n  ],\n};\nconst groupBy = [{ expr: 'type', as: 'sch' }];\nconst having = {\n  type: { $like: '%hotel%' },\n};\nconst lettingExpr = [\n  { key: 'amount_v2', value: 10 },\n  { key: 'size_v2', value: 20 },\n];\nconst orderBy = { type: 'DESC' };\nconst limit = 10;\nconst offset = 1;\nconst useExpr = ['airlineR_8093', 'airlineR_8094'];\n\nconst query = new Query({}, 'collection-name')\n  .select(select)\n  .let(letExpr)\n  .where(where)\n  .groupBy(groupBy)\n  .letting(lettingExpr)\n  .having(having)\n  .orderBy(orderBy)\n  .limit(limit)\n  .offset(offset)\n  .useKeys(useExpr)\n  .build();\nconsole.log(query);\n\n```\n\nWhich translates to\n```sql\nSELECT COUNT(type) AS odm,MAX(amount) FROM `travel-sample` USE KEYS ['airlineR_8093','airlineR_8094'] LET amount_val = 10,size_val = 20 WHERE ((price \u003e amount_val AND price IS NOT NULL) OR auto \u003e 10 OR amount = 10) AND ((price2 \u003e 1.99 AND price2 IS NOT NULL) AND ((price3 \u003e 1.99 AND price3 IS NOT NULL) OR id = '20')) GROUP BY type AS sch LETTING amount_v2=10,size_v2=20 HAVING type LIKE \"%hotel%\" ORDER BY type = 'DESC' LIMIT 10 OFFSET 1\n```\n\n\n### Running custom query on cluster\n\n```ts\nimport { QueryCluster } from '@stoqey/sofa';\n\nconst queryresults = await QueryCluster(queryBuilder);\n// queryresults = { rows: object[], meta: any}\n\n```\n### Contributors needed \n- Create automatic pagination ✅\n- Create Schema and validation/population ✅\n- Create static methods for models like `save`, `update`, `findMany` e.t.c ✅\n- Automated indexes\n- Geospatial queries\n- FTS queries\n\n\n\n\u003cimg height=\"500px\" src=\"./docs/sleeping.png\"\u003e\u003c/img\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003ch1 align=\"center\"\u003e Stoqey Inc \u003c/h1\u003e\n\u003c/p\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fstoqey%2Fsofa","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fstoqey%2Fsofa","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fstoqey%2Fsofa/lists"}