Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
806593b
chore(protect): upgrade @cipherstash/protect-ffi to 0.20.1
tobyhede Feb 2, 2026
55e38d0
feat(protect): add encryptQuery API for query term encryption
tobyhede Feb 2, 2026
59aa419
refactor(protect): deprecate createSearchTerms in favor of encryptQuery
tobyhede Feb 2, 2026
b4b31c5
fix(protect): address encryptQuery code review issues
tobyhede Feb 2, 2026
fea303d
test(protect): expand encryptQuery test coverage from 16 to 36 tests
tobyhede Feb 2, 2026
6956b39
fix(drizzle): use encryptQuery with queryType for correct index selec…
tobyhede Feb 3, 2026
7691953
docs(protect): add TSDoc for encryptQuery API types and operations
tobyhede Feb 3, 2026
db72e2c
chore(protect): add changeset for encryptQuery API
tobyhede Feb 3, 2026
c03f1f0
fix(protect): address PR #276 code review feedback
tobyhede Feb 3, 2026
e7531ce
refactor(protect): extract shared helpers and test fixtures for encry…
tobyhede Feb 3, 2026
afb448a
docs(changeset): use public QueryTypeName values instead of FFI names
tobyhede Feb 3, 2026
0f8cc78
fix(protect): export LockContext as value and fix typo
tobyhede Feb 3, 2026
a9df691
refactor(protect): migrate createSearchTerms usage to encryptQuery
tobyhede Feb 3, 2026
76f0857
fix(protect): add encryptedToCompositeLiteral helper for Supabase que…
tobyhede Feb 3, 2026
3395d28
feat(protect): add returnType support to encryptQuery batch API
tobyhede Feb 3, 2026
921a59b
refactor(protect): address code review suggestions for returnType
tobyhede Feb 3, 2026
ac23cfd
fix(protect): add null guards to composite literal helpers
tobyhede Feb 3, 2026
a7cee20
docs(protect): deprecate encryptedToEscapedCompositeLiteral helper
tobyhede Feb 3, 2026
01042d1
chore: cleanup
tobyhede Feb 4, 2026
9c87c82
test(protect): assert EQL version 2 explicitly in encryptQuery tests
tobyhede Feb 4, 2026
d1273ef
feat(protect): validate number + match index incompatibility in encry…
tobyhede Feb 4, 2026
5981406
test(protect): add test for null lockContext from getLockContext
tobyhede Feb 4, 2026
ec3be12
chore: remove hallucination
tobyhede Feb 4, 2026
203f6a2
fix(protect): correct Result type handling in validateNumericValue re…
tobyhede Feb 4, 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
11 changes: 11 additions & 0 deletions .changeset/encrypt-query-api.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
"@cipherstash/protect": minor
"@cipherstash/drizzle": patch
---

Add `encryptQuery` API for encrypting query terms with explicit query type selection.

- New `encryptQuery()` method replaces `createSearchTerms()` with improved query type handling
- Supports `equality`, `freeTextSearch`, and `orderAndRange` query types
- Deprecates `createSearchTerms()` - use `encryptQuery()` instead
- Updates drizzle operators to use correct index selection via `queryType` parameter
20 changes: 11 additions & 9 deletions examples/dynamo/src/encrypted-key-in-gsi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,21 +66,23 @@ const main = async () => {

await dynamoClient.send(putCommand)

const searchTermsResult = await protectDynamo.createSearchTerms([
{
value: 'abc@example.com',
column: users.email,
table: users,
},
// Use encryptQuery to create the search term for GSI query
const encryptedResult = await protectClient.encryptQuery([
{ value: 'abc@example.com', column: users.email, table: users, queryType: 'equality' },
])

if (searchTermsResult.failure) {
if (encryptedResult.failure) {
throw new Error(
`Failed to create search terms: ${searchTermsResult.failure.message}`,
`Failed to encrypt query: ${encryptedResult.failure.message}`,
)
}

const [emailHmac] = searchTermsResult.data
// Extract the HMAC for DynamoDB key lookup
const encryptedEmail = encryptedResult.data[0]
if (!encryptedEmail) {
throw new Error('Failed to encrypt query: no result returned')
}
const emailHmac = encryptedEmail.hm

const queryCommand = new QueryCommand({
TableName: tableName,
Expand Down
20 changes: 11 additions & 9 deletions examples/dynamo/src/encrypted-partition-key.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,21 +49,23 @@ const main = async () => {

await docClient.send(putCommand)

const searchTermsResult = await protectDynamo.createSearchTerms([
{
value: 'abc@example.com',
column: users.email,
table: users,
},
// Use encryptQuery to create the search term for partition key lookup
const encryptedResult = await protectClient.encryptQuery([
{ value: 'abc@example.com', column: users.email, table: users, queryType: 'equality' },
])

if (searchTermsResult.failure) {
if (encryptedResult.failure) {
throw new Error(
`Failed to create search terms: ${searchTermsResult.failure.message}`,
`Failed to encrypt query: ${encryptedResult.failure.message}`,
)
}

const [emailHmac] = searchTermsResult.data
// Extract the HMAC for DynamoDB key lookup
const encryptedEmail = encryptedResult.data[0]
if (!encryptedEmail) {
throw new Error('Failed to encrypt query: no result returned')
}
const emailHmac = encryptedEmail.hm

const getCommand = new GetCommand({
TableName: tableName,
Expand Down
20 changes: 11 additions & 9 deletions examples/dynamo/src/encrypted-sort-key.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,21 +58,23 @@ const main = async () => {

await docClient.send(putCommand)

const searchTermsResult = await protectDynamo.createSearchTerms([
{
value: 'abc@example.com',
column: users.email,
table: users,
},
// Use encryptQuery to create the search term for sort key range query
const encryptedResult = await protectClient.encryptQuery([
{ value: 'abc@example.com', column: users.email, table: users, queryType: 'equality' },
])

if (searchTermsResult.failure) {
if (encryptedResult.failure) {
throw new Error(
`Failed to create search terms: ${searchTermsResult.failure.message}`,
`Failed to encrypt query: ${encryptedResult.failure.message}`,
)
}

const [emailHmac] = searchTermsResult.data
// Extract the HMAC for DynamoDB key lookup
const encryptedEmail = encryptedResult.data[0]
if (!encryptedEmail) {
throw new Error('Failed to encrypt query: no result returned')
}
const emailHmac = encryptedEmail.hm

const getCommand = new GetCommand({
TableName: tableName,
Expand Down
18 changes: 12 additions & 6 deletions examples/typeorm/src/helpers/protect-entity.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import type { ProtectClient } from '@cipherstash/protect'
import {
type ProtectClient,
encryptedToPgComposite,
} from '@cipherstash/protect'
import type { EntityTarget } from 'typeorm'
import { AppDataSource } from '../data-source'

Expand Down Expand Up @@ -199,25 +202,28 @@ export class ProtectEntityHelper {
// biome-ignore lint/suspicious/noExplicitAny: Required for Protect.js schema types
fieldConfig: { table: any; column: any },
): Promise<T | null> {
const searchTermsResult = await this.protectClient.createSearchTerms([
// Use encryptQuery instead of deprecated createSearchTerms
const encryptedResult = await this.protectClient.encryptQuery([
{
value: searchValue,
column: fieldConfig.column,
table: fieldConfig.table,
returnType: 'composite-literal',
queryType: 'equality',
},
])

if (searchTermsResult.failure) {
if (encryptedResult.failure) {
throw new Error(
`Failed to create search terms: ${searchTermsResult.failure.message}`,
`Failed to encrypt query: ${encryptedResult.failure.message}`,
)
}

const [encrypted] = encryptedResult.data

const repository = AppDataSource.getRepository(entityClass)
return repository.findOne({
where: {
[fieldName]: searchTermsResult.data[0],
[fieldName]: encryptedToPgComposite(encrypted),
// biome-ignore lint/suspicious/noExplicitAny: Required for dynamic field access
} as any,
})
Expand Down
Loading