Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
77 commits
Select commit Hold shift + click to select a range
e941688
feat(schema): enable searchableJson() method for SteVec indexing
tobyhede Jan 15, 2026
6a9208c
feat(protect): add JSON search term types for containment and path qu…
tobyhede Jan 15, 2026
9558fc2
feat(protect): add query encryption operations with comprehensive tests
tobyhede Jan 16, 2026
13ab8d3
refactor(protect): remove unintended public query API
tobyhede Jan 16, 2026
a112a44
chore: update protect-ffi to 0.20.0
tobyhede Jan 19, 2026
bedda29
fix: use local type definitions until protect-ffi 0.20.0 release
tobyhede Jan 19, 2026
b0c00d2
chore: update protect-ffi to 0.20.0
tobyhede Jan 19, 2026
795e082
test(protect): add comprehensive JSON search terms tests
tobyhede Jan 19, 2026
e4bc7a6
feat(protect): expose JSON and query search operations via public API
tobyhede Jan 19, 2026
b660611
docs: add documentation for searchable encrypted JSON
tobyhede Jan 19, 2026
f5e4793
test(protect): add comprehensive tests for explicit query encryption …
tobyhede Jan 19, 2026
def4f0d
feat(protect): extend SearchTerm type to support JSON search terms
tobyhede Jan 19, 2026
3c82c71
feat(protect): implement JSON support in SearchTermsOperation
tobyhede Jan 19, 2026
6be18b4
test(protect): add JSON support tests for createSearchTerms
tobyhede Jan 19, 2026
2d7a40d
deprecate(protect): mark createJsonSearchTerms as deprecated
tobyhede Jan 19, 2026
b6f3fd3
refactor(protect): remove deprecated createJsonSearchTerms API
tobyhede Jan 20, 2026
3f9aed2
test(protect): add lock context tests and optimize client initialization
tobyhede Jan 20, 2026
8596d2e
refactor(schema): replace magic string with ste_vec prefix inference
tobyhede Jan 20, 2026
8be7455
test(protect): add missing test coverage for edge cases
tobyhede Jan 20, 2026
6854834
fix(schema): resolve ste_vec prefix type mismatch in DTS build
tobyhede Jan 20, 2026
3181da1
docs: address PR #257 code review feedback for searchable JSON API
tobyhede Jan 21, 2026
c0d66df
refactor(docs): address code review suggestions
tobyhede Jan 21, 2026
6a90fcd
feat(types): add QueryTerm union types for unified encryptQuery API
tobyhede Jan 21, 2026
1523dec
feat(types): add type guards for QueryTerm variants
tobyhede Jan 21, 2026
5357cb6
feat(exports): export QueryTerm types and type guards
tobyhede Jan 21, 2026
2415c4d
feat(operations): add BatchEncryptQueryOperation for batch encryptQuery
tobyhede Jan 21, 2026
30e6cfc
feat(encryptQuery): add batch overload for array of QueryTerms
tobyhede Jan 21, 2026
c888923
test(encryptQuery): add comprehensive batch tests for JSON and mixed …
tobyhede Jan 21, 2026
6700f45
deprecate(createQuerySearchTerms): mark as deprecated in favor of enc…
tobyhede Jan 21, 2026
7b4a95f
deprecate(createSearchTerms): mark as deprecated in favor of encryptQ…
tobyhede Jan 21, 2026
e311d32
fix(types): resolve DTS build error in encryptQuery overload type nar…
tobyhede Jan 21, 2026
354818a
style: fix linting issues in batch-encrypt-query and related files
tobyhede Jan 21, 2026
dbcc596
docs: update all documentation to use unified encryptQuery API
tobyhede Jan 21, 2026
4775db2
test(encryptQuery): add withLockContext test for batch operations
tobyhede Jan 21, 2026
37d6d60
refactor(encryptQuery): extract isQueryTermArray type guard for clean…
tobyhede Jan 21, 2026
97b2270
docs: sync documentation with encryptQuery unified API implementation
tobyhede Jan 21, 2026
14301bf
fix(encryptQuery): handle empty array input correctly
tobyhede Jan 21, 2026
e389d3f
feat(encryptQuery): make indexType optional with auto-inference support
tobyhede Jan 21, 2026
f32cc1d
fix(encryptQuery): correct docs and export missing types
tobyhede Jan 21, 2026
c6bc59b
chore: merge
calvinbrewer Jan 21, 2026
50d5f27
refactor(encryptQuery): rename indexType to queryType with schema-mat…
tobyhede Jan 22, 2026
ba3aca3
test: tighten query search term assertions
tobyhede Jan 22, 2026
453932b
refactor(search): encrypt ste_vec selectors and simplify search API
tobyhede Jan 22, 2026
e1ea208
docs: migrate reference markdown files to TSDoc
tobyhede Jan 22, 2026
16333e0
test(protect): strengthen encrypted payload assertions with reusable …
tobyhede Jan 22, 2026
48ea765
refactor(protect): simplify JS layer for FFI query mode inference
tobyhede Jan 27, 2026
f61862f
feat(protect): export FFI error types and preserve error codes
tobyhede Jan 28, 2026
18d8359
test(protect): add ste_vec type inference and error code tests
tobyhede Jan 28, 2026
b8f0b02
chore(protect): update protect-ffi to git branch
tobyhede Jan 28, 2026
44d8f2c
docs: update API naming from indexType to queryType
tobyhede Jan 28, 2026
c4fed10
docs: add queryType clarity notes
tobyhede Jan 28, 2026
37c9a94
chore(protect): update protect-ffi to 0.20.1
tobyhede Jan 28, 2026
c3dc0bd
test(protect): update error message assertion for protect-ffi 0.20.1
tobyhede Jan 28, 2026
85de1f0
test(protect): add coverage for JSON extraction operations
tobyhede Jan 28, 2026
b65022d
docs: align documentation with encryptQuery API changes
tobyhede Jan 28, 2026
93b7ec6
test(protect): add JSONB proxy parity tests for comprehensive coverage
tobyhede Jan 29, 2026
1bc4eb6
refactor(protect): address code review feedback for JSONB parity tests
tobyhede Jan 29, 2026
ccea674
test(protect): add JSONB test coverage for unknown fields and large d…
tobyhede Jan 29, 2026
0fc3d48
test(drizzle): add encryption/decryption verification tests to JSONB …
tobyhede Jan 29, 2026
1a6ef39
fix(drizzle): create test tables dynamically in JSONB test suite
tobyhede Jan 29, 2026
649b883
fix(drizzle): use eql_v2_encrypted column type in JSONB test tables
tobyhede Jan 29, 2026
5c60e50
feat(drizzle): add searchableJson option to encryptedType
tobyhede Jan 29, 2026
7896281
refactor(drizzle): migrate JSONB tests from createSearchTerms to encr…
tobyhede Jan 29, 2026
63cebec
chore(local): add test_run_id column to CI table schema
tobyhede Jan 29, 2026
cd8f850
refactor(protect): migrate JSONB tests to encryptQuery API
tobyhede Jan 29, 2026
0fefd71
test(protect): add comprehensive encryptQuery API tests
tobyhede Jan 29, 2026
362f587
test(protect): improve JSON encryption test assertions
tobyhede Jan 29, 2026
8f6e035
docs(protect): remove version-specific deprecation notice from create…
tobyhede Feb 1, 2026
57004e6
refactor(protect): rename toDollarPath to toJsonPath
tobyhede Feb 1, 2026
b00285d
test(protect): add unit tests for toJsonPath function
tobyhede Feb 1, 2026
8fa52ea
test(protect): add string vs array path distinction tests for toJsonPath
tobyhede Feb 1, 2026
ddced84
refactor(protect): move deprecated search-terms test to deprecated di…
tobyhede Feb 1, 2026
7c33795
refactor(protect): update import paths for deprecated search-terms mo…
tobyhede Feb 1, 2026
4486895
test(protect): add number encryption tests for encryptQuery API
tobyhede Feb 1, 2026
38c09af
refactor(protect): move deprecated search-terms operation to deprecat…
tobyhede Feb 1, 2026
c4fd596
fix(protect): address code review suggestions for deprecated API refa…
tobyhede Feb 1, 2026
7658095
refactor(drizzle): reorganize JSONB tests to eliminate duplication
tobyhede Feb 2, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/searchable-json-query-api.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@cipherstash/protect": major
"@cipherstash/schema": major
---

Add searchable JSON query API with path and containment query support
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -66,3 +66,10 @@ mise.local.toml
cipherstash.toml
cipherstash.secret.toml
sql/cipherstash-*.sql

# work files
.claude/
.serena/
.work/
**/.work/
PR_REVIEW.md
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ If these variables are missing, tests that require live encryption will fail or
- `encryptModel(model, table)` / `decryptModel(model)`
- `bulkEncrypt(plaintexts[], { table, column })` / `bulkDecrypt(encrypted[])`
- `bulkEncryptModels(models[], table)` / `bulkDecryptModels(models[])`
- `createSearchTerms(terms)` for searchable queries
- `encryptQuery(terms)` for searchable queries (note: `createSearchTerms` is deprecated, use `encryptQuery` instead)
- **Identity-aware encryption**: Use `LockContext` from `@cipherstash/protect/identify` and chain `.withLockContext()` on operations. Same context must be used for both encrypt and decrypt.

## Critical Gotchas (read before coding)
Expand Down
1 change: 1 addition & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ The documentation for Protect.js is organized into the following sections:
## Concepts

- [Searchable encryption](./concepts/searchable-encryption.md)
- [Searchable JSON](./reference/schema.md#searchable-json) - Query encrypted JSON documents

## Reference

Expand Down
7 changes: 4 additions & 3 deletions docs/concepts/aws-kms-vs-cipherstash-comparison.md
Original file line number Diff line number Diff line change
Expand Up @@ -165,11 +165,12 @@ const encryptResult = await protectClient.encrypt(
);

// Create search terms and query directly in PostgreSQL
const searchTerms = await protectClient.createSearchTerms({
terms: ['secret'],
const searchTerms = await protectClient.encryptQuery([{
value: 'secret',
column: users.email,
table: users,
});
queryType: 'freeTextSearch',
}]);

// Use with your ORM (Drizzle integration included)
```
Expand Down
11 changes: 6 additions & 5 deletions docs/concepts/searchable-encryption.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,14 +69,16 @@ CipherStash uses [EQL](https://github.com/cipherstash/encrypt-query-language) to
// 1) Encrypt the search term
const searchTerm = 'alice.johnson@example.com'

const encryptedParam = await protectClient.createSearchTerms([{
const encryptedParam = await protectClient.encryptQuery([{
value: searchTerm,
table: protectedUsers, // Reference to the Protect table schema
column: protectedUsers.email, // Your Protect column definition
queryType: 'equality', // Use 'equality' for exact match queries
}])

if (encryptedParam.failure) {
// Handle the failure
throw new Error(encryptedParam.failure.message)
}

// 2) Build an equality query noting that EQL must be installed in order for the operation to work successfully
Expand All @@ -86,10 +88,9 @@ const equalitySQL = `
WHERE email = $1
`

// 3) Execute the query, passing in the Postgres column name
// and the encrypted search term as the second parameter
// 3) Execute the query, passing in the encrypted search term
// (client is an arbitrary Postgres client)
const result = await client.query(equalitySQL, [ protectedUser.email.getName(), encryptedParam.data ])
const result = await client.query(equalitySQL, [encryptedParam.data[0]])
```

Using the above approach, Protect.js is generating the EQL payloads and which means you never have to drop down to writing complex SQL queries.
Expand Down Expand Up @@ -132,7 +133,7 @@ With searchable encryption, you can:
With searchable encryption:

- Data can be encrypted, stored, and searched in your existing PostgreSQL database.
- Encrypted data can be searched using equality, free text search, and range queries.
- Encrypted data can be searched using equality, free text search, range queries, and JSON path/containment queries.
- Data remains encrypted, and will be decrypted using the Protect.js library in your application.
- Queries are blazing fast, and won't slow down your application experience.
- Every decryption event is logged, giving you an audit trail of data access events.
Expand Down
8 changes: 8 additions & 0 deletions docs/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,14 @@ CREATE TABLE users (
);
```

## Next steps

Now that you have the basics working, explore these advanced features:

- **[Searchable Encryption](./reference/searchable-encryption-postgres.md)** - Learn how to search encrypted data using `encryptQuery()` with PostgreSQL and EQL
- **[Model Operations](./reference/model-operations.md)** - Encrypt and decrypt entire objects with bulk operations
- **[Schema Configuration](./reference/schema.md)** - Configure indexes for equality, free text search, range queries, and JSON search

---

### Didn't find what you wanted?
Expand Down
4 changes: 2 additions & 2 deletions docs/reference/model-operations.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ For better performance when working with multiple models, use these bulk encrypt
### Bulk encryption

```typescript
const users = [
const usersList = [
{
id: "1",
email: "user1@example.com",
Expand All @@ -88,7 +88,7 @@ const users = [
},
];

const encryptedResult = await protectClient.bulkEncryptModels(users, users);
const encryptedResult = await protectClient.bulkEncryptModels(usersList, usersSchema);

if (encryptedResult.failure) {
console.error("Bulk encryption failed:", encryptedResult.failure.message);
Expand Down
34 changes: 29 additions & 5 deletions docs/reference/schema.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,9 +76,29 @@ export const protectedUsers = csTable("users", {
});
```

### Searchable JSON

To enable searching within JSON columns, use the `searchableJson()` method. This automatically sets the column data type to `json` and configures the necessary indexes for path and containment queries.

```ts
import { csTable, csColumn } from "@cipherstash/protect";

export const protectedUsers = csTable("users", {
metadata: csColumn("metadata").searchableJson(),
});
```

> [!WARNING]
> `searchableJson()` is mutually exclusive with other index types (`equality()`, `freeTextSearch()`, `orderAndRange()`) on the same column. Combining them will result in runtime errors. This is enforced by the encryption backend, not at the TypeScript type level.


### Nested objects

Protect.js supports nested objects in your schema, allowing you to encrypt **but not search on** nested properties. You can define nested objects up to 3 levels deep.
Protect.js supports nested objects in your schema, allowing you to encrypt nested properties. You can define nested objects up to 3 levels deep using `csValue`. For **searchable** JSON data, use `.searchableJson()` on a JSON column instead.

> [!TIP]
> If you need to search within JSON data, use `.searchableJson()` on the column instead of nested `csValue` definitions. See [Searchable JSON](#searchable-json) above.

This is useful for data stores that have less structured data, like NoSQL databases.

You can define nested objects by using the `csValue` function to define a value in a nested object. The value naming convention of the `csValue` function is a dot-separated string of the nested object path, e.g. `profile.name` or `profile.address.street`.
Expand All @@ -105,15 +125,15 @@ export const protectedUsers = csTable("users", {
```

When working with nested objects:
- Searchable encryption is not supported on nested objects
- Searchable encryption is not supported on nested `csValue` objects (use `.searchableJson()` for searchable JSON)
- Each level can have its own encrypted fields
- The maximum nesting depth is 3 levels
- Null and undefined values are supported at any level
- Optional nested objects are supported

> [!WARNING]
> TODO: The schema builder does not validate the values you supply to the `csValue` or `csColumn` functions.
> These values are meant to be unique, and and cause unexpected behavior if they are not defined correctly.
> The schema builder does not currently validate the values you supply to the `csValue` or `csColumn` functions.
> These values must be unique within your schema - duplicate values may cause unexpected behavior.

## Available index options

Expand All @@ -124,8 +144,12 @@ The following index options are available for your schema:
| equality | Enables a exact index for equality queries. | `WHERE email = 'example@example.com'` |
| freeTextSearch | Enables a match index for free text queries. | `WHERE description LIKE '%example%'` |
| orderAndRange | Enables an sorting and range queries index. | `ORDER BY price ASC` |
| searchableJson | Enables searching inside JSON columns. | `WHERE data->'user'->>'email' = '...'` |

You can chain these methods to your column to configure them in any combination.
You can chain `equality()`, `freeTextSearch()`, and `orderAndRange()` methods in any combination.

> [!WARNING]
> `searchableJson()` is **mutually exclusive** with other index types. Do not combine `searchableJson()` with `equality()`, `freeTextSearch()`, or `orderAndRange()` on the same column.

## Initializing the Protect client

Expand Down
Loading