{"_id":"@cerbos/langchain-chromadb","name":"@cerbos/langchain-chromadb","dist-tags":{"latest":"0.0.1"},"versions":{"0.0.1":{"name":"@cerbos/langchain-chromadb","version":"0.0.1","homepage":"https://cerbos.dev","repository":{"type":"git","url":"git+https://github.com/cerbos/query-plan-adapters.git","directory":"packages/langchain-chromadb"},"bugs":{"url":"https://github.com/cerbos/query-plan-adapters/issues"},"author":{"name":"Cerbos","email":"help@cerbos.dev","url":"https://cerbos.dev"},"license":"Apache-2.0","main":"./lib/index.js","types":"./lib/index.d.ts","exports":{".":{"types":"./lib/index.d.ts","default":"./lib/index.js"}},"publishConfig":{"access":"public","provenance":true},"scripts":{"build":"tsc --build","prepare":"npm run build","preversion":"npm run lint","version":"git add -A src","postversion":"git push && git push --tags","test":"cross-env CHROMA_URL=http://127.0.0.1:8234 start-server-and-test chroma http://127.0.0.1:8234/api/v2/heartbeat test:cerbos","test:cerbos":"cerbos run --log-level=error --set=storage.disk.directory=../policies --set=telemetry.disabled=true -- jest src/**.test.ts","cerbos":"cerbos server --config=./cerbos-config.yaml","chroma":"docker run --rm -p 8234:8000 chromadb/chroma:latest"},"engines":{"node":">=20.0.0"},"devDependencies":{"@jest/globals":"^30.2.0","@tsconfig/node22":"^22.0.5","@tsconfig/strictest":"^2.0.8","@types/jest":"^30.0.0","@types/node":"^22.0.0","cross-env":"^7.0.3","start-server-and-test":"^2.0.3","ts-jest":"^29.4.6","ts-node":"^10.4.0","typescript":"^5.0.0"},"dependencies":{"@cerbos/core":"^0.27.0","@cerbos/grpc":"^0.24.0","chromadb":"^3.2.2"},"gitHead":"25a35bb069e77e9158d164be2fbe75b8546d15b7","_id":"@cerbos/langchain-chromadb@0.0.1","description":"An adapter library that takes a [Cerbos](https://cerbos.dev) Query Plan ([PlanResources API](https://docs.cerbos.dev/cerbos/latest/api/index.html#resources-query-plan)) response and converts it into a [ChromaDB](https://www.trychroma.com/) filter object t","_nodeVersion":"24.11.1","_npmVersion":"11.6.2","dist":{"integrity":"sha512-ufXINkBXhiPgdvQnak1QCly8+zbj2GAiF7w35qjHOf7+F9E2guSwT5p53ojXYfysqEEUNK0d1wIu1NAxODeJtQ==","shasum":"be0f89f248bc7f4013e0501b9ce2dd2c3056d24a","tarball":"https://registry.npmjs.org/@cerbos/langchain-chromadb/-/langchain-chromadb-0.0.1.tgz","fileCount":7,"unpackedSize":34930,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCICoroNuK1DSwclaEd9PT5ZLy6VvrlNxijTnuApaIAwDoAiBUCHalI/272kSz/+pqBGfS8xyglNtoki8R3rRgrDrDMQ=="}]},"_npmUser":{"name":"alexolivier","email":"alex@alexolivier.me"},"directories":{},"maintainers":[{"name":"alexolivier","email":"alex@alexolivier.me"},{"name":"ahaines","email":"andrew@haines.org.nz"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/langchain-chromadb_0.0.1_1770053816069_0.5854885373627632"},"_hasShrinkwrap":false}},"time":{"created":"2026-02-02T17:36:55.935Z","0.0.1":"2026-02-02T17:36:56.234Z","modified":"2026-02-02T17:36:56.485Z"},"maintainers":[{"name":"alexolivier","email":"alex@alexolivier.me"},{"name":"ahaines","email":"andrew@haines.org.nz"}],"description":"An adapter library that takes a [Cerbos](https://cerbos.dev) Query Plan ([PlanResources API](https://docs.cerbos.dev/cerbos/latest/api/index.html#resources-query-plan)) response and converts it into a [ChromaDB](https://www.trychroma.com/) filter object t","homepage":"https://cerbos.dev","repository":{"type":"git","url":"git+https://github.com/cerbos/query-plan-adapters.git","directory":"packages/langchain-chromadb"},"author":{"name":"Cerbos","email":"help@cerbos.dev","url":"https://cerbos.dev"},"bugs":{"url":"https://github.com/cerbos/query-plan-adapters/issues"},"license":"Apache-2.0","readme":"# Cerbos + LangChain.js ChromaDB Adapter\n\nAn adapter library that takes a [Cerbos](https://cerbos.dev) Query Plan ([PlanResources API](https://docs.cerbos.dev/cerbos/latest/api/index.html#resources-query-plan)) response and converts it into a [ChromaDB](https://www.trychroma.com/) filter object that can be passed to the LangChain.js Chroma vector store. This is designed to work alongside a project using the [Cerbos Javascript SDK](https://github.com/cerbos/cerbos-sdk-javascript).\n\n## How it works\n\n1. Use a Cerbos client (`@cerbos/http` or `@cerbos/grpc`) to call `planResources` and obtain a `PlanResourcesResponse`.\n2. Provide `queryPlanToChromaDB` with that plan and a `fieldNameMapper` that describes how Cerbos attribute paths relate to your ChromaDB metadata fields.\n3. The adapter walks the Cerbos expression tree, translates supported operators to ChromaDB `Where` filter syntax, and returns `{ kind, filters? }`.\n4. Inspect `result.kind`:\n   - `ALWAYS_ALLOWED`: the caller can query without any additional filters.\n   - `ALWAYS_DENIED`: short-circuit and return an empty result set.\n   - `CONDITIONAL`: execute the query with `result.filters`.\n\n## Supported operators\n\n| Category | Cerbos operators | ChromaDB output |\n| --- | --- | --- |\n| Logical | `and`, `or` | `$and`, `$or` |\n| Negation | `not` | Operator inversion and De Morgan's law (see below) |\n| Comparisons | `eq`, `ne`, `lt`, `le`, `gt`, `ge` | `$eq`, `$ne`, `$lt`, `$lte`, `$gt`, `$gte` |\n| Membership | `in` | `$in` |\n\n### Negation handling\n\nChromaDB's `Where` filter does not support `$not` or `$nor`. The adapter handles `not` expressions by inverting the inner operator:\n\n- `not(eq)` → `$ne`, `not(ne)` → `$eq`\n- `not(lt)` → `$gte`, `not(gt)` → `$lte`, `not(le)` → `$gt`, `not(ge)` → `$lt`\n- `not(in)` → `$nin`\n- `not(and(A, B))` → `$or[not(A), not(B)]` (De Morgan's law)\n- `not(or(A, B))` → `$and[not(A), not(B)]` (De Morgan's law)\n- `not(not(X))` → `X` (double negation elimination)\n\n### Not supported\n\nChromaDB stores flat scalar metadata, so the following Cerbos operators cannot be mapped:\n\n- String helpers: `contains`, `startsWith`, `endsWith`\n- Existence: `isSet`\n- Array/collection: `hasIntersection`, `exists`, `exists_one`, `all`, `filter`, `map`, `lambda`, `size`\n\nAny unsupported operator in the plan causes `queryPlanToChromaDB` to throw an error.\n\n## Requirements\n\n- Cerbos > v0.16\n- `@cerbos/http` or `@cerbos/grpc` client\n- Node.js >= 20.0.0\n- ChromaDB 3.x\n\n## Installation\n\n```bash\nnpm install @cerbos/langchain-chromadb\n```\n\n## API\n\n```ts\nimport { queryPlanToChromaDB, PlanKind } from \"@cerbos/langchain-chromadb\";\n\nconst result = queryPlanToChromaDB({\n  queryPlan, // PlanResourcesResponse from Cerbos\n  fieldNameMapper, // map or function - see below\n});\n\nif (result.kind === PlanKind.CONDITIONAL) {\n  // use result.filters as the `where` property of a ChromaDB query\n}\n```\n\n`PlanKind` is re-exported from `@cerbos/core`:\n\n```ts\nexport enum PlanKind {\n  ALWAYS_ALLOWED = \"KIND_ALWAYS_ALLOWED\",\n  ALWAYS_DENIED = \"KIND_ALWAYS_DENIED\",\n  CONDITIONAL = \"KIND_CONDITIONAL\",\n}\n```\n\n### Field name mapper\n\nThe Cerbos query plan references fields using paths such as `request.resource.attr.title`. Use a mapper to translate those to the metadata field names in your ChromaDB collection.\n\nAs a map:\n\n```ts\nconst result = queryPlanToChromaDB({\n  queryPlan,\n  fieldNameMapper: {\n    \"request.resource.attr.aBool\": \"aBool\",\n    \"request.resource.attr.aString\": \"title\",\n  },\n});\n```\n\nAs a function:\n\n```ts\nconst result = queryPlanToChromaDB({\n  queryPlan,\n  fieldNameMapper: (fieldName: string): string => {\n    return fieldName.replace(\"request.resource.attr.\", \"\");\n  },\n});\n```\n\nIf a field is not found in the map, the original path is used as-is.\n\n## Usage example\n\n```ts\nimport { GRPC as Cerbos } from \"@cerbos/grpc\";\nimport { Chroma } from \"@langchain/community/vectorstores/chroma\";\nimport { OpenAIEmbeddings } from \"@langchain/openai\";\nimport { queryPlanToChromaDB, PlanKind } from \"@cerbos/langchain-chromadb\";\n\nconst cerbos = new Cerbos(\"localhost:3592\", { tls: false });\n\nconst queryPlan = await cerbos.planResources({\n  principal: { id: \"user1\", roles: [\"USER\"] },\n  resource: { kind: \"document\" },\n  action: \"view\",\n});\n\nconst result = queryPlanToChromaDB({\n  queryPlan,\n  fieldNameMapper: {\n    \"request.resource.attr.department\": \"department\",\n    \"request.resource.attr.public\": \"public\",\n  },\n});\n\nif (result.kind === PlanKind.ALWAYS_DENIED) {\n  return [];\n}\n\nconst chroma = await Chroma.fromExistingCollection(new OpenAIEmbeddings(), {\n  collectionName: \"my_collection\",\n});\n\nconst filters =\n  result.kind === PlanKind.CONDITIONAL ? result.filters : undefined;\n\nconst matches = await chroma.similaritySearch(\"query\", 10, filters);\n```\n\n## Error handling\n\n`queryPlanToChromaDB` throws descriptive errors when:\n\n- The plan kind is not a valid `PlanKind` value.\n- A conditional plan contains an operand that is not a `PlanExpression`.\n- An operator in the plan is not supported by ChromaDB's filter syntax.\n- A comparison operator is missing a variable or field name.\n- A `not` expression wraps an operator that cannot be negated.\n","readmeFilename":"README.md","_rev":"1-246f128b79a5d41a0672534efc542ad7"}