{"id":25339887,"url":"https://github.com/cipherstash/protectjs","last_synced_at":"2025-10-29T10:30:58.357Z","repository":{"id":267532032,"uuid":"888687041","full_name":"cipherstash/protectjs","owner":"cipherstash","description":"Encrypt and protect data using industry standard algorithms, field level encryption, a unique data key per record, bulk encryption operations, and decryption level identity verification. Powered by CipherStash Encryption.","archived":false,"fork":false,"pushed_at":"2025-02-13T17:19:53.000Z","size":665,"stargazers_count":9,"open_issues_count":5,"forks_count":0,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-02-13T18:29:05.928Z","etag":null,"topics":["data","data-security","encryption","javascript","postgres","postgresql","security","typescript"],"latest_commit_sha":null,"homepage":"https://cipherstash.com","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/cipherstash.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-11-14T20:38:26.000Z","updated_at":"2025-02-13T00:59:07.000Z","dependencies_parsed_at":"2024-12-10T22:27:23.783Z","dependency_job_id":"9b4cab61-2007-4e5d-bce6-dd458e618f9e","html_url":"https://github.com/cipherstash/protectjs","commit_stats":null,"previous_names":["cipherstash/jseql","cipherstash/protectjs"],"tags_count":30,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cipherstash%2Fprotectjs","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cipherstash%2Fprotectjs/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cipherstash%2Fprotectjs/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cipherstash%2Fprotectjs/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/cipherstash","download_url":"https://codeload.github.com/cipherstash/protectjs/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":238805888,"owners_count":19533617,"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":["data","data-security","encryption","javascript","postgres","postgresql","security","typescript"],"created_at":"2025-02-14T07:43:28.508Z","updated_at":"2025-10-29T10:30:58.350Z","avatar_url":"https://github.com/cipherstash.png","language":"TypeScript","readme":"\u003ch1 align=\"center\"\u003e\n  \u003cimg alt=\"CipherStash Logo\" loading=\"lazy\" width=\"128\" height=\"128\" decoding=\"async\" data-nimg=\"1\"   style=\"color:transparent\" src=\"https://cipherstash.com/assets/cs-github.png\"\u003e\n  \u003c/br\u003e\n  Protect.js\n\u003c/h1\u003e\n\u003cp align=\"center\"\u003e\n  End-to-end field level encryption for JavaScript/TypeScript apps with zero‑knowledge key management. Search encrypted data without decrypting it.\n  \u003cbr/\u003e\n  \u003cdiv align=\"center\" style=\"display: flex; justify-content: center; gap: 1rem;\"\u003e\n    \u003ca href=\"https://cipherstash.com\"\u003e\n      \u003cimg\n        src=\"https://raw.githubusercontent.com/cipherstash/meta/refs/heads/main/csbadge.svg\"\n        alt=\"Built by CipherStash\"\n      /\u003e\n    \u003c/a\u003e\n    \u003ca href=\"https://www.npmjs.com/package/@cipherstash/protect\"\u003e\n      \u003cimg\n        alt=\"NPM version\"\n        src=\"https://img.shields.io/npm/v/@cipherstash/protect.svg?style=for-the-badge\u0026labelColor=000000\"\n      /\u003e\n    \u003c/a\u003e\n    \u003ca href=\"https://www.npmjs.com/package/@cipherstash/protect\"\u003e\n      \u003cimg\n        alt=\"npm downloads\"\n        src=\"https://img.shields.io/npm/dm/@cipherstash/protect.svg?style=for-the-badge\u0026labelColor=000000\"\n      /\u003e\n    \u003c/a\u003e\n    \u003ca href=\"https://github.com/cipherstash/protectjs/stargazers\"\u003e\n      \u003cimg\n        alt=\"GitHub stars\"\n        src=\"https://img.shields.io/github/stars/cipherstash/protectjs?style=for-the-badge\u0026labelColor=000000\"\n      /\u003e\n    \u003c/a\u003e\n    \u003ca href=\"https://github.com/cipherstash/protectjs/blob/main/LICENSE.md\"\u003e\n      \u003cimg\n        alt=\"License\"\n        src=\"https://img.shields.io/npm/l/@cipherstash/protect.svg?style=for-the-badge\u0026labelColor=000000\"\n      /\u003e\n    \u003c/a\u003e\n    \u003ca href=\"https://github.com/cipherstash/protectjs/tree/main/docs\"\u003e\n      \u003cimg\n        alt=\"Docs\"\n        src=\"https://img.shields.io/badge/docs-Read-blue?style=for-the-badge\u0026labelColor=000000\"\n      /\u003e\n    \u003c/a\u003e\n  \u003c/div\u003e\n\u003c/p\u003e\n\u003cdiv align=\"center\"\u003e⭐ Please star this repo if you find it useful!\u003c/div\u003e\n\u003cbr/\u003e\n\n\u003c!-- start --\u003e\n\nProtect.js lets you encrypt every value with its own key—without sacrificing performance or usability. Encryption happens in your app; ciphertext is stored in your database.\n\nPer‑value unique keys are powered by CipherStash [ZeroKMS](https://cipherstash.com/products/zerokms) bulk key operations, backed by a root key in [AWS KMS](https://docs.aws.amazon.com/kms/latest/developerguide/overview.html).\n\nEncrypted data is structured as an [EQL](https://github.com/cipherstash/encrypt-query-language) JSON payload and can be stored in any database that supports JSONB.\n\n\u003e [!IMPORTANT]\n\u003e Searching, sorting, and filtering on encrypted data is currently only supported when storing encrypted data in PostgreSQL.\n\u003e Read more about [searching encrypted data](./docs/concepts/searchable-encryption.md).\n\nLooking for DynamoDB support? Check out the [Protect.js for DynamoDB helper library](https://www.npmjs.com/package/@cipherstash/protect-dynamodb).\n\n## Quick start (60 seconds)\n\nCreate an account and workspace in the [CipherStash dashboard](https://cipherstash.com/signup), then follow the onboarding guide to generate your client credentials and store them in your `.env` file.\n\nInstall the package:\n\n```bash\nnpm install @cipherstash/protect\n```\n\nStart encrypting data:\n\n```ts\nimport { protect } from \"@cipherstash/protect\";\nimport { csTable, csColumn } from \"@cipherstash/protect\";\n\n// 1) Define a schema\nconst users = csTable(\"users\", { email: csColumn(\"email\") });\n\n// 2) Create a client (requires CS_* env vars)\nconst client = await protect({ schemas: [users] });\n\n// 3) Encrypt → store JSONB payload\nconst encrypted = await client.encrypt(\"alice@example.com\", {\n  table: users,\n  column: users.email,\n});\n\nif (encrypted.failure) {\n  // You decide how to handle the failure and the user experience\n}\n\n// 4) Decrypt later\nconst decrypted = await client.decrypt(encrypted.data);\n```\n\n## Architecture (high level)\n\n![Protect.js Architecture Diagram](https://github.com/cipherstash/protectjs/blob/main/docs/images/protectjs-architecture.png)\n\n## Table of contents\n\n- [Quick start (60 seconds)](#quick-start-60-seconds)\n- [Architecture (high level)](#architecture-high-level)\n- [Features](#features)\n- [Installing Protect.js](#installing-protectjs)\n- [Getting started](#getting-started)\n- [Identity-aware encryption](#identity-aware-encryption)\n- [Supported data types](#supported-data-types)\n- [Searchable encryption](#searchable-encryption)\n- [Logging](#logging)\n- [CipherStash Client](#cipherstash-client)\n- [Example applications](#example-applications)\n- [Builds and bundling](#builds-and-bundling)\n- [Contributing](#contributing)\n- [License](#license)\n\nFor more specific documentation, refer to the [docs](https://github.com/cipherstash/protectjs/tree/main/docs).\n\n## Features\n\nProtect.js protects data in using industry-standard AES encryption.\nProtect.js uses [ZeroKMS](https://cipherstash.com/products/zerokms) for bulk encryption and decryption operations.\nThis enables every encrypted value, in every column, in every row in your database to have a unique key — without sacrificing performance.\n\n**Features:**\n\n- **Bulk encryption and decryption**: Protect.js uses [ZeroKMS](https://cipherstash.com/products/zerokms) for encrypting and decrypting thousands of records at once, while using a unique key for every value.\n- **Single item encryption and decryption**: Just looking for a way to encrypt and decrypt single values? Protect.js has you covered.\n- **Really fast:** ZeroKMS's performance makes using millions of unique keys feasible and performant for real-world applications built with Protect.js.\n- **Identity-aware encryption**: Lock down access to sensitive data by requiring a valid JWT to perform a decryption.\n- **Audit trail**: Every decryption event will be logged in ZeroKMS to help you prove compliance.\n- **Searchable encryption**: Protect.js supports searching encrypted data in PostgreSQL.\n- **TypeScript support**: Strongly typed with TypeScript interfaces and types.\n\n**Use cases:**\n\n- **Trusted data access**: make sure only your end-users can access their sensitive data stored in your product.\n- **Meet compliance requirements faster:** meet and exceed the data encryption requirements of SOC2 and ISO27001.\n- **Reduce the blast radius of data breaches:** limit the impact of exploited vulnerabilities to only the data your end-users can decrypt.\n\n## Installing Protect.js\n\nInstall the [`@cipherstash/protect` package](https://www.npmjs.com/package/@cipherstash/protect) with your package manager of choice:\n\n```bash\nnpm install @cipherstash/protect\n# or\nyarn add @cipherstash/protect\n# or\npnpm add @cipherstash/protect\n```\n\n\u003e [!TIP]\n\u003e [Bun](https://bun.sh/) is not currently supported due to a lack of [Node-API compatibility](https://github.com/oven-sh/bun/issues/158). Under the hood, Protect.js uses [CipherStash Client](#cipherstash-client) which is written in Rust and embedded using [Neon](https://github.com/neon-bindings/neon).\n\n### Opt-out of bundling\n\n\u003e [!IMPORTANT]\n\u003e **You need to opt-out of bundling when using Protect.js.**\n\nProtect.js uses Node.js specific features and requires the use of the [native Node.js `require`](https://nodejs.org/api/modules.html#requireid).\n\nWhen using Protect.js, you need to opt-out of bundling for tools like [Webpack](https://webpack.js.org/configuration/externals/), [esbuild](https://webpack.js.org/configuration/externals/), or [Next.js](https://nextjs.org/docs/app/api-reference/config/next-config-js/serverExternalPackages).\n\nRead more about [building and bundling with Protect.js](#builds-and-bundling).\n\n## Getting started\n\n- 🆕 **Existing app?** Skip to [the next step](#configuration).\n- 🌱 **Clean slate?** Check out the [getting started tutorial](./docs/getting-started.md).\n\n### Configuration\n\nIf you haven't already, sign up for a [CipherStash account](https://cipherstash.com/signup).\nOnce you have an account, you will create a Workspace which is scoped to your application environment.\n\nFollow the onboarding steps to get your first set of credentials required to use Protect.js.\nBy the end of the onboarding, you will have the following environment variables:\n\n```bash\nCS_WORKSPACE_CRN= # The workspace identifier\nCS_CLIENT_ID= # The client identifier\nCS_CLIENT_KEY= # The client key which is used as key material in combination with ZeroKMS\nCS_CLIENT_ACCESS_KEY= # The API key used for authenticating with the CipherStash API\n```\n\nSave these environment variables to a `.env` file in your project.\n\n### Basic file structure\n\nThe following is the basic file structure of the project.\nIn the `src/protect/` directory, we have the table definition in `schema.ts` and the protect client in `index.ts`.\n\n```\n📦 \u003cproject root\u003e\n ├ 📂 src\n │   ├ 📂 protect\n │   │  ├ 📜 index.ts\n │   │  └ 📜 schema.ts\n │   └ 📜 index.ts\n ├ 📜 .env\n ├ 📜 cipherstash.toml\n ├ 📜 cipherstash.secret.toml\n ├ 📜 package.json\n └ 📜 tsconfig.json\n```\n\n### Define your schema\n\nProtect.js uses a schema to define the tables and columns that you want to encrypt and decrypt.\n\nDefine your tables and columns by adding this to `src/protect/schema.ts`:\n\n```ts\nimport { csTable, csColumn } from \"@cipherstash/protect\";\n\nexport const users = csTable(\"users\", {\n  email: csColumn(\"email\"),\n});\n\nexport const orders = csTable(\"orders\", {\n  address: csColumn(\"address\"),\n});\n```\n\n**Searchable encryption:**\n\nIf you want to search encrypted data in your PostgreSQL database, you must declare the indexes in schema in `src/protect/schema.ts`:\n\n```ts\nimport { csTable, csColumn } from \"@cipherstash/protect\";\n\nexport const users = csTable(\"users\", {\n  email: csColumn(\"email\").freeTextSearch().equality().orderAndRange(),\n});\n\nexport const orders = csTable(\"orders\", {\n  address: csColumn(\"address\"),\n});\n```\n\nRead more about [defining your schema](./docs/reference/schema.md).\n\n### Initialize the Protect client\n\nTo import the `protect` function and initialize a client with your defined schema, add the following to `src/protect/index.ts`:\n\n```ts\nimport { protect, type ProtectClientConfig } from \"@cipherstash/protect\";\nimport { users, orders } from \"./schema\";\n\nconst config: ProtectClientConfig = {\n  schemas: [users, orders],\n}\n\n// Pass all your tables to the protect function to initialize the client\nexport const protectClient = await protect(config);\n```\n\nThe `protect` function requires at least one `csTable` be provided in the `schemas` array.\n\n### Encrypt data\n\nProtect.js provides the `encrypt` function on `protectClient` to encrypt data.\n`encrypt` takes a plaintext string, and an object with the table and column as parameters.\n\nTo start encrypting data, add the following to `src/index.ts`:\n\n```typescript\nimport { users } from \"./protect/schema\";\nimport { protectClient } from \"./protect\";\n\nconst encryptResult = await protectClient.encrypt(\"secret@squirrel.example\", {\n  column: users.email,\n  table: users,\n});\n\nif (encryptResult.failure) {\n  // Handle the failure\n  console.log(\n    \"error when encrypting:\",\n    encryptResult.failure.type,\n    encryptResult.failure.message\n  );\n}\n\nconsole.log(\"EQL Payload containing ciphertexts:\", encryptResult.data);\n```\n\nThe `encrypt` function will return a `Result` object with either a `data` key, or a `failure` key.\nThe `encryptResult` will return one of the following:\n\n```typescript\n// Success\n{\n  data: EncryptedPayload\n}\n\n// Failure\n{\n  failure: {\n    type: 'EncryptionError',\n    message: 'A message about the error'\n  }\n}\n```\n\n### Decrypt data\n\nProtect.js provides the `decrypt` function on `protectClient` to decrypt data.\n`decrypt` takes an encrypted data object as a parameter.\n\nTo start decrypting data, add the following to `src/index.ts`:\n\n```typescript\nimport { protectClient } from \"./protect\";\n\n// encryptResult is the EQL payload from the previous step\nconst decryptResult = await protectClient.decrypt(encryptResult.data);\n\nif (decryptResult.failure) {\n  // Handle the failure\n  console.log(\n    \"error when decrypting:\",\n    decryptResult.failure.type,\n    decryptResult.failure.message\n  );\n}\n\nconst plaintext = decryptResult.data;\nconsole.log(\"plaintext:\", plaintext);\n```\n\nThe `decrypt` function returns a `Result` object with either a `data` key, or a `failure` key.\nThe `decryptResult` will return one of the following:\n\n```typescript\n// Success\n{\n  data: 'secret@squirrel.example'\n}\n\n// Failure\n{\n  failure: {\n    type: 'DecryptionError',\n    message: 'A message about the error'\n  }\n}\n```\n\n### Working with models and objects\n\nProtect.js provides model-level encryption methods that make it easy to encrypt and decrypt entire objects.\nThese methods automatically handle the encryption of fields defined in your schema.\n\nIf you are working with a large data set, the model operations are significantly faster than encrypting and decrypting individual objects as they are able to perform bulk operations.\n\n\u003e [!TIP]\n\u003e CipherStash [ZeroKMS](https://cipherstash.com/products/zerokms) is optimized for bulk operations.\n\u003e\n\u003e All the model operations are able to take advantage of this performance for real-world use cases by only making a single call to ZeroKMS regardless of the number of objects you are encrypting or decrypting while still using a unique key for each record.\n\n#### Encrypting a model\n\nUse the `encryptModel` method to encrypt a model's fields that are defined in your schema:\n\n```typescript\nimport { protectClient } from \"./protect\";\nimport { users } from \"./protect/schema\";\n\n// Your model with plaintext values\nconst user = {\n  id: \"1\",\n  email: \"user@example.com\",\n  address: \"123 Main St\",\n  createdAt: new Date(\"2024-01-01\"),\n};\n\nconst encryptedResult = await protectClient.encryptModel(user, users);\n\nif (encryptedResult.failure) {\n  // Handle the failure\n  console.log(\n    \"error when encrypting:\",\n    encryptedResult.failure.type,\n    encryptedResult.failure.message\n  );\n}\n\nconst encryptedUser = encryptedResult.data;\nconsole.log(\"encrypted user:\", encryptedUser);\n```\n\nThe `encryptModel` function will only encrypt fields that are defined in your schema.\nOther fields (like `id` and `createdAt` in the example above) will remain unchanged.\n\n#### Type safety with models\n\nProtect.js provides strong TypeScript support for model operations.\nYou can specify your model's type to ensure end-to-end type safety:\n\n```typescript\nimport { protectClient } from \"./protect\";\nimport { users } from \"./protect/schema\";\n\n// Define your model type\ntype User = {\n  id: string;\n  email: string | null;\n  address: string | null;\n  createdAt: Date;\n  updatedAt: Date;\n  metadata?: {\n    preferences?: {\n      notifications: boolean;\n      theme: string;\n    };\n  };\n};\n\n// The encryptModel method will ensure type safety\nconst encryptedResult = await protectClient.encryptModel\u003cUser\u003e(user, users);\n\nif (encryptedResult.failure) {\n  // Handle the failure\n}\n\nconst encryptedUser = encryptedResult.data;\n// TypeScript knows that encryptedUser matches the User type structure\n// but with encrypted fields for those defined in the schema\n\n// Decryption maintains type safety\nconst decryptedResult = await protectClient.decryptModel\u003cUser\u003e(encryptedUser);\n\nif (decryptedResult.failure) {\n  // Handle the failure\n}\n\nconst decryptedUser = decryptedResult.data;\n// decryptedUser is fully typed as User\n\n// Bulk operations also support type safety\nconst bulkEncryptedResult = await protectClient.bulkEncryptModels\u003cUser\u003e(\n  userModels,\n  users\n);\n\nconst bulkDecryptedResult = await protectClient.bulkDecryptModels\u003cUser\u003e(\n  bulkEncryptedResult.data\n);\n```\n\nThe type system ensures that:\n\n- Input models match your defined type structure\n- Only fields defined in your schema are encrypted\n- Encrypted and decrypted results maintain the correct type structure\n- Optional and nullable fields are properly handled\n- Nested object structures are preserved\n- Additional properties not defined in the schema remain unchanged\n\nThis type safety helps catch potential issues at compile time and provides better IDE support with autocompletion and type hints.\n\n\u003e [!TIP]\n\u003e When using TypeScript with an ORM, you can reuse your ORM's model types directly with Protect.js's model operations.\n\nExample with Drizzle infered types:\n\n```typescript\nimport { protectClient } from \"./protect\";\nimport { jsonb, pgTable, serial, InferSelectModel } from \"drizzle-orm/pg-core\";\nimport { csTable, csColumn } from \"@cipherstash/protect\";\n\nconst protectUsers = csTable(\"users\", {\n  email: csColumn(\"email\"),\n});\n\nconst users = pgTable(\"users\", {\n  id: serial(\"id\").primaryKey(),\n  email: jsonb(\"email\").notNull(),\n});\n\ntype User = InferSelectModel\u003ctypeof users\u003e;\n\nconst user = {\n  id: \"1\",\n  email: \"user@example.com\",\n};\n\n// Drizzle User type works directly with model operations\nconst encryptedResult = await protectClient.encryptModel\u003cUser\u003e(\n  user,\n  protectUsers\n);\n```\n\n#### Decrypting a model\n\nUse the `decryptModel` method to decrypt a model's encrypted fields:\n\n```typescript\nimport { protectClient } from \"./protect\";\n\nconst decryptedResult = await protectClient.decryptModel(encryptedUser);\n\nif (decryptedResult.failure) {\n  // Handle the failure\n  console.log(\n    \"error when decrypting:\",\n    decryptedResult.failure.type,\n    decryptedResult.failure.message\n  );\n}\n\nconst decryptedUser = decryptedResult.data;\nconsole.log(\"decrypted user:\", decryptedUser);\n```\n\n#### Bulk model operations\n\nFor better performance when working with multiple models, use the `bulkEncryptModels` and `bulkDecryptModels` methods:\n\n```typescript\nimport { protectClient } from \"./protect\";\nimport { users } from \"./protect/schema\";\n\n// Array of models with plaintext values\nconst userModels = [\n  {\n    id: \"1\",\n    email: \"user1@example.com\",\n    address: \"123 Main St\",\n  },\n  {\n    id: \"2\",\n    email: \"user2@example.com\",\n    address: \"456 Oak Ave\",\n  },\n];\n\n// Encrypt multiple models at once\nconst encryptedResult = await protectClient.bulkEncryptModels(\n  userModels,\n  users\n);\n\nif (encryptedResult.failure) {\n  // Handle the failure\n}\n\nconst encryptedUsers = encryptedResult.data;\n\n// Decrypt multiple models at once\nconst decryptedResult = await protectClient.bulkDecryptModels(encryptedUsers);\n\nif (decryptedResult.failure) {\n  // Handle the failure\n}\n\nconst decryptedUsers = decryptedResult.data;\n```\n\nThe model encryption methods provide a higher-level interface that's particularly useful when working with ORMs or when you need to encrypt multiple fields in an object.\nThey automatically handle the mapping between your model's structure and the encrypted fields defined in your schema.\n\n### Bulk operations\n\nProtect.js provides direct access to ZeroKMS bulk operations through the `bulkEncrypt` and `bulkDecrypt` methods. These methods are ideal when you need maximum performance and want to handle the correlation between encrypted/decrypted values and your application data manually.\n\n\u003e [!TIP]\n\u003e The bulk operations provide the most direct interface to ZeroKMS's blazing fast bulk encryption and decryption capabilities. Each value gets a unique key while maintaining optimal performance through a single call to ZeroKMS.\n\n#### Bulk encryption\n\nUse the `bulkEncrypt` method to encrypt multiple plaintext values at once:\n\n```typescript\nimport { protectClient } from \"./protect\";\nimport { users } from \"./protect/schema\";\n\n// Array of plaintext values with optional IDs for correlation\nconst plaintexts = [\n  { id: \"user1\", plaintext: \"alice@example.com\" },\n  { id: \"user2\", plaintext: \"bob@example.com\" },\n  { id: \"user3\", plaintext: \"charlie@example.com\" },\n];\n\nconst encryptedResult = await protectClient.bulkEncrypt(plaintexts, {\n  column: users.email,\n  table: users,\n});\n\nif (encryptedResult.failure) {\n  // Handle the failure\n  console.log(\n    \"error when bulk encrypting:\",\n    encryptedResult.failure.type,\n    encryptedResult.failure.message\n  );\n}\n\nconst encryptedData = encryptedResult.data;\nconsole.log(\"encrypted data:\", encryptedData);\n```\n\nThe `bulkEncrypt` method returns an array of objects with the following structure:\n\n```typescript\n[\n  { id: \"user1\", data: EncryptedPayload },\n  { id: \"user2\", data: EncryptedPayload },\n  { id: \"user3\", data: EncryptedPayload },\n]\n```\n\nYou can also encrypt without IDs if you don't need correlation:\n\n```typescript\nconst plaintexts = [\n  { plaintext: \"alice@example.com\" },\n  { plaintext: \"bob@example.com\" },\n  { plaintext: \"charlie@example.com\" },\n];\n\nconst encryptedResult = await protectClient.bulkEncrypt(plaintexts, {\n  column: users.email,\n  table: users,\n});\n```\n\n#### Bulk decryption\n\nUse the `bulkDecrypt` method to decrypt multiple encrypted values at once:\n\n```typescript\nimport { protectClient } from \"./protect\";\n\n// encryptedData is the result from bulkEncrypt\nconst decryptedResult = await protectClient.bulkDecrypt(encryptedData);\n\nif (decryptedResult.failure) {\n  // Handle the failure\n  console.log(\n    \"error when bulk decrypting:\",\n    decryptedResult.failure.type,\n    decryptedResult.failure.message\n  );\n}\n\nconst decryptedData = decryptedResult.data;\nconsole.log(\"decrypted data:\", decryptedData);\n```\n\nThe `bulkDecrypt` method returns an array of objects with the following structure:\n\n```typescript\n[\n  { id: \"user1\", data: \"alice@example.com\" },\n  { id: \"user2\", data: \"bob@example.com\" },\n  { id: \"user3\", data: \"charlie@example.com\" },\n]\n```\n\n#### Response structure\n\nThe `bulkDecrypt` method returns a `Result` object that represents the overall operation status. When successful from an HTTP and execution perspective, the `data` field contains an array where each item can have one of two outcomes:\n\n- **Success**: The item has a `data` field containing the decrypted plaintext\n- **Failure**: The item has an `error` field containing a specific error message explaining why that particular decryption failed\n\n```typescript\n// Example response structure\n{\n  data: [\n    { id: \"user1\", data: \"alice@example.com\" },           // Success\n    { id: \"user2\", error: \"Invalid ciphertext format\" },  // Failure\n    { id: \"user3\", data: \"charlie@example.com\" },         // Success\n  ]\n}\n```\n\n\u003e [!NOTE]\n\u003e The underlying ZeroKMS response uses HTTP status code 207 (Multi-Status) to indicate that the bulk operation completed, but individual items within the batch may have succeeded or failed. This allows you to handle partial failures gracefully while still processing the successful decryptions.\n\nYou can handle mixed results by checking each item:\n\n```typescript\nconst decryptedResult = await protectClient.bulkDecrypt(encryptedData);\n\nif (decryptedResult.failure) {\n  // Handle overall operation failure\n  console.log(\"Bulk decryption failed:\", decryptedResult.failure.message);\n  return;\n}\n\n// Process individual results\ndecryptedResult.data.forEach((item) =\u003e {\n  if ('data' in item) {\n    // Success - item.data contains the decrypted plaintext\n    console.log(`Decrypted ${item.id}:`, item.data);\n  } else if ('error' in item) {\n    // Failure - item.error contains the specific error message\n    console.log(`Failed to decrypt ${item.id}:`, item.error);\n  }\n});\n```\n\n#### Handling null values\n\nBulk operations properly handle null values in both encryption and decryption:\n\n```typescript\nconst plaintexts = [\n  { id: \"user1\", plaintext: \"alice@example.com\" },\n  { id: \"user2\", plaintext: null },\n  { id: \"user3\", plaintext: \"charlie@example.com\" },\n];\n\nconst encryptedResult = await protectClient.bulkEncrypt(plaintexts, {\n  column: users.email,\n  table: users,\n});\n\n// Null values are preserved in the encrypted result\n// encryptedResult.data[1].data will be null\n\nconst decryptedResult = await protectClient.bulkDecrypt(encryptedResult.data);\n\n// Null values are preserved in the decrypted result\n// decryptedResult.data[1].data will be null\n```\n\n#### Using bulk operations with lock contexts\n\nBulk operations support identity-aware encryption through lock contexts:\n\n```typescript\nimport { LockContext } from \"@cipherstash/protect/identify\";\n\nconst lc = new LockContext();\nconst lockContext = await lc.identify(userJwt);\n\nif (lockContext.failure) {\n  // Handle the failure\n}\n\nconst plaintexts = [\n  { id: \"user1\", plaintext: \"alice@example.com\" },\n  { id: \"user2\", plaintext: \"bob@example.com\" },\n];\n\n// Encrypt with lock context\nconst encryptedResult = await protectClient\n  .bulkEncrypt(plaintexts, {\n    column: users.email,\n    table: users,\n  })\n  .withLockContext(lockContext.data);\n\n// Decrypt with lock context\nconst decryptedResult = await protectClient\n  .bulkDecrypt(encryptedResult.data)\n  .withLockContext(lockContext.data);\n```\n\n#### Performance considerations\n\nBulk operations are optimized for performance and can handle thousands of values efficiently:\n\n```typescript\n// Create a large array of values\nconst plaintexts = Array.from({ length: 1000 }, (_, i) =\u003e ({\n  id: `user${i}`,\n  plaintext: `user${i}@example.com`,\n}));\n\n// Single call to ZeroKMS for all 1000 values\nconst encryptedResult = await protectClient.bulkEncrypt(plaintexts, {\n  column: users.email,\n  table: users,\n});\n\n// Single call to ZeroKMS for all 1000 values\nconst decryptedResult = await protectClient.bulkDecrypt(encryptedResult.data);\n```\n\nThe bulk operations maintain the same security guarantees as individual operations - each value gets a unique key - while providing optimal performance through ZeroKMS's bulk processing capabilities.\n\n### Store encrypted data in a database\n\nEncrypted data can be stored in any database that supports JSONB, noting that searchable encryption is only supported in PostgreSQL at the moment.\n\nTo store the encrypted data, specify the column type as `jsonb`.\n\n```sql\nCREATE TABLE users (\n  id SERIAL PRIMARY KEY,\n  email jsonb NOT NULL,\n);\n```\n\n#### Searchable encryption in PostgreSQL\n\nTo enable searchable encryption in PostgreSQL, [install the EQL custom types and functions](https://github.com/cipherstash/encrypt-query-language?tab=readme-ov-file#installation).\n\n1. Download the latest EQL install script:\n\n   ```sh\n   curl -sLo cipherstash-encrypt.sql https://github.com/cipherstash/encrypt-query-language/releases/latest/download/cipherstash-encrypt.sql\n   ```\n\n   Using [Supabase](https://supabase.com/)? We ship an EQL release specifically for Supabase.\n   Download the latest EQL install script:\n\n   ```sh\n   curl -sLo cipherstash-encrypt-supabase.sql https://github.com/cipherstash/encrypt-query-language/releases/latest/download/cipherstash-encrypt-supabase.sql\n   ```\n\n2. Run this command to install the custom types and functions:\n\n   ```sh\n   psql -f cipherstash-encrypt.sql\n   ```\n\n   or with Supabase:\n\n   ```sh\n   psql -f cipherstash-encrypt-supabase.sql\n   ```\n\nEQL is now installed in your database and you can enable searchable encryption by adding the `eql_v2_encrypted` type to a column.\n\n```sql\nCREATE TABLE users (\n    id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,\n    email eql_v2_encrypted\n);\n```\n\n\u003e [!WARNING]\n\u003e The `eql_v2_encrypted` type is a [composite type](https://www.postgresql.org/docs/current/rowtypes.html) and each ORM/client has a different way of handling inserts and selects.\n\u003e We've documented how to handle inserts and selects for the different ORMs/clients in the [docs](./docs/reference/working-with-composite-types.md).\n\nRead more about [how to search encrypted data](./docs/reference/searchable-encryption-postgres.md) in the docs.\n\n## Identity-aware encryption\n\n\u003e [!IMPORTANT]\n\u003e Right now identity-aware encryption is only supported if you are using [Clerk](https://clerk.com/) as your identity provider.\n\u003e Read more about [lock contexts with Clerk and Next.js](./docs/how-to/lock-contexts-with-clerk.md).\n\nProtect.js can add an additional layer of protection to your data by requiring a valid JWT to perform a decryption.\n\nThis ensures that only the user who encrypted data is able to decrypt it.\n\nProtect.js does this through a mechanism called a _lock context_.\n\n### Lock context\n\nLock contexts ensure that only specific users can access sensitive data.\n\n\u003e [!CAUTION]\n\u003e You must use the same lock context to encrypt and decrypt data.\n\u003e If you use different lock contexts, you will be unable to decrypt the data.\n\nTo use a lock context, initialize a `LockContext` object with the identity claims.\n\n```typescript\nimport { LockContext } from \"@cipherstash/protect/identify\";\n\n// protectClient from the previous steps\nconst lc = new LockContext();\n```\n\n\u003e [!NOTE]\n\u003e When initializing a `LockContext`, the default context is set to use the `sub` Identity Claim.\n\n### Identifying a user for a lock context\n\nA lock context needs to be locked to a user.\nTo identify the user, call the `identify` method on the lock context object, and pass a valid JWT from a user's session:\n\n```typescript\nconst identifyResult = await lc.identify(jwt);\n\n// The identify method returns the same Result pattern as the encrypt and decrypt methods.\nif (identifyResult.failure) {\n  // Hanlde the failure\n}\n\nconst lockContext = identifyResult.data;\n```\n\n### Encrypting data with a lock context\n\nTo encrypt data with a lock context, call the optional `withLockContext` method on the `encrypt` function and pass the lock context object as a parameter:\n\n```typescript\nimport { protectClient } from \"./protect\";\nimport { users } from \"./protect/schema\";\n\nconst encryptResult = await protectClient\n  .encrypt(\"plaintext\", {\n    table: users,\n    column: users.email,\n  })\n  .withLockContext(lockContext);\n\nif (encryptResult.failure) {\n  // Handle the failure\n}\n\nconsole.log(\"EQL Payload containing ciphertexts:\", encryptResult.data);\n```\n\n### Decrypting data with a lock context\n\nTo decrypt data with a lock context, call the optional `withLockContext` method on the `decrypt` function and pass the lock context object as a parameter:\n\n```typescript\nimport { protectClient } from \"./protect\";\n\nconst decryptResult = await protectClient\n  .decrypt(encryptResult.data)\n  .withLockContext(lockContext);\n\nif (decryptResult.failure) {\n  // Handle the failure\n}\n\nconst plaintext = decryptResult.data;\n```\n\n### Model encryption with lock context\n\nAll model operations support lock contexts for identity-aware encryption:\n\n```typescript\nimport { protectClient } from \"./protect\";\nimport { users } from \"./protect/schema\";\n\nconst myUsers = [\n  {\n    id: \"1\",\n    email: \"user@example.com\",\n    address: \"123 Main St\",\n    createdAt: new Date(\"2024-01-01\"),\n  },\n  {\n    id: \"2\",\n    email: \"user2@example.com\",\n    address: \"456 Oak Ave\",\n  },\n];\n\n// Encrypt a model with lock context\nconst encryptedResult = await protectClient\n  .encryptModel(myUsers[0], users)\n  .withLockContext(lockContext);\n\nif (encryptedResult.failure) {\n  // Handle the failure\n}\n\n// Decrypt a model with lock context\nconst decryptedResult = await protectClient\n  .decryptModel(encryptedResult.data)\n  .withLockContext(lockContext);\n\n// Bulk operations also support lock contexts\nconst bulkEncryptedResult = await protectClient\n  .bulkEncryptModels(myUsers, users)\n  .withLockContext(lockContext);\n\nconst bulkDecryptedResult = await protectClient\n  .bulkDecryptModels(bulkEncryptedResult.data)\n  .withLockContext(lockContext);\n```\n\n## Supported data types\n\nProtect.js supports a number of different data types with support for additional types on the roadmap.\n\n| JS/TS Type | Available | Notes |\n|--|--|--|\n| `string` | ✅ |\n| `number` | ✅ |\n| `json` (opaque)  | ✅ |  |\n| `json` (searchable)  | ⚙️ | Coming soon |\n| `bigint` | ⚙️ | Coming soon |\n| `boolean`| ⚙️ | Coming soon |\n| `date`   | ⚙️ | Coming soon |\n\nIf you need support for ther data types please [raise an issue](https://github.com/cipherstash/protectjs/issues) and we'll do our best to add it to Protect.js.\n\n### Type casting\n\nWhen encrypting types other than `string`, Protect requires the data type to be specified explicitly using the `dataType` function on the column definition.\n\nFor example, to handle encryption of a `number` field called `score`:\n\n```ts\nconst users = csTable('users', {\n  score: csColumn('score').dataType('number')\n})\n```\n\nThis means that any JavaScript/TypeScript `number` will encrypt correctly but if an attempt to encrypt a value of a different type is made the operation will fail with an error.\nThis is particularly important for searchable index schemes that require data types (and their encodings) to be consistent.\n\nIn an unencrypted setup, this type checking is usually handled by the database (the column type in a table) but when the data is encrypted, the database can't determine what type the plaintext value should be so we must specify it in the Protect schema instead.\n\n\u003e [!IMPORTANT]\n\u003e If the data type of a column is set to `bigint`, floating point numbers will be converted to integers (via truncation).\n\n### Handling of null and special values\n\nThere are some important special cases to be aware of when encrypting values with Protect.js.\nFor example, encrypting `null` or `undefined` will just return a `null`/`undefined` value.\n\nWhen `dataType` is `number`, attempting to encrypt `NaN`, `Infinity` or `-Infinity` will fail with an error.\nEncrypting `-0.0` will coerce the value into `0.0`.\n\nThe table below summarizes these cases.\n\n| Data type | Plaintext | Encryption |\n|--|--|--|\n|`any`| `null` | `null` |\n| `any` | `undefined` | `undefined` |\n| `number` | `-0.0` | Encryption of `0.0` |\n| `number` | `NaN` | _Error_ |\n| `number` | `Infinity` | _Error_| \n| `number` | `-Infinity` | _Error_| \n\n\n## Searchable encryption\n\nRead more about [searching encrypted data](./docs/concepts/searchable-encryption.md) in the docs.\n\n## Logging\n\n\u003e [!IMPORTANT] \n\u003e `@cipherstash/protect` will NEVER log plaintext data.\n\u003e This is by design to prevent sensitive data from leaking into logs.\n\n`@cipherstash/protect` and `@cipherstash/nextjs` will log to the console with a log level of `info` by default.\nTo enable the logger, configure the following environment variable:\n\n```bash\nPROTECT_LOG_LEVEL=debug  # Enable debug logging\nPROTECT_LOG_LEVEL=info   # Enable info logging\nPROTECT_LOG_LEVEL=error  # Enable error logging\n```\n\n## CipherStash Client\n\nProtect.js is built on top of the CipherStash Client Rust SDK which is embedded with the `@cipherstash/protect-ffi` package.\nThe `@cipherstash/protect-ffi` source code is available on [GitHub](https://github.com/cipherstash/protectjs-ffi).\n\nRead more about configuring the CipherStash Client in the [configuration docs](./docs/reference/configuration.md).\n\n## Example applications\n\nLooking for examples of how to use Protect.js?\nCheck out the [example applications](./examples):\n\n- [Basic example](/examples/basic) demonstrates how to perform encryption operations\n- [Drizzle example](/examples/drizzle) demonstrates how to use Protect.js with an ORM\n- [Next.js and lock contexts example using Clerk](/examples/nextjs-clerk) demonstrates how to protect data with identity-aware encryption\n\n`@cipherstash/protect` can be used with most ORMs.\nIf you're interested in using `@cipherstash/protect` with a specific ORM, please [create an issue](https://github.com/cipherstash/protectjs/issues/new).\n\n## Builds and bundling\n\n`@cipherstash/protect` is a native Node.js module, and relies on native Node.js `require` to load the package.\n\nHere are a few resources to help based on your tool set:\n\n- [Required Next.js configuration](./docs/how-to/nextjs-external-packages.md).\n- [SST and AWS serverless functions](./docs/how-to/sst-external-packages.md).\n  \n\u003e [!TIP]\n\u003e Deploying to Linux (e.g., AWS Lambda) with npm lockfile v3 and seeing runtime module load errors? See the troubleshooting guide: [`docs/how-to/npm-lockfile-v3`](./docs/how-to/npm-lockfile-v3-linux-deployments.md).\n\n## Contributing\n\nPlease read the [contribution guide](CONTRIBUTE.md).\n\n## License\n\nProtect.js is [MIT licensed](./LICENSE.md).\n\n---\n\n### Didn't find what you wanted?\n\n[Click here to let us know what was missing from our docs.](https://github.com/cipherstash/protectjs/issues/new?template=docs-feedback.yml\u0026title=[Docs:]%20Feedback%20on%20README.md)\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcipherstash%2Fprotectjs","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcipherstash%2Fprotectjs","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcipherstash%2Fprotectjs/lists"}