Skip to content

supabase: EncryptedQueryBuilder.withLockContext is typed narrower than core operations #592

Description

@coderdan

Summary

EncryptedQueryBuilder.withLockContext() accepts only a LockContext instance, while every core encryption operation accepts LockContextInput (LockContext | Context). The Supabase adapter is the odd one out, and the inconsistency pushes users toward code that looks like the deprecated identity flow even when it isn't.

Detail

dist/supabase/index.d.ts:

withLockContext(lockContext: LockContext): EncryptedQueryBuilder<T, FK>;

dist/encryption/index.d.ts (and every other operation: EncryptOperation, DecryptOperation, EncryptModelOperation, DecryptModelOperation, BulkEncryptOperation, BulkDecryptOperation, BulkEncryptModelsOperation, BulkDecryptModelsOperation, EncryptQueryOperation, BatchEncryptQueryOperation):

withLockContext(lockContext: LockContextInput): EncryptOperationWithLockContext;

where

type LockContextInput = LockContext | Context;  // Context = { identityClaim: string[] }

The LockContext class docstring explicitly says constructing one is optional:

You can pass a plain { identityClaim } directly — constructing a LockContext is optional.

That's true everywhere except the Supabase path.

Why it matters

Post-protect-ffi 0.25, the recommended flow is OidcFederationStrategy plus a plain claim object:

await client
  .encrypt(value, { column: users.email, table: users })
  .withLockContext({ identityClaim: ["sub"] })   // ✅

On the Supabase adapter the equivalent does not typecheck, so the docs have to say:

const lockContext = new LockContext({ context: { identityClaim: ["sub"] } })

await eSupabase
  .from("users", users)
  .insert({ email: "alice@example.com" })
  .withLockContext(lockContext)                  // ⚠️ only shape that compiles

Two costs. The Supabase integration page now reads differently from every other page for no semantic reason. And new LockContext(...) appearing in Supabase examples is easily mistaken for the deprecated new LockContext()identify() flow, which is exactly the pattern we're trying to retire (see #591).

Suggested fix

Widen the adapter to accept LockContextInput and route it through the existing resolveLockContext() helper, which already normalises both shapes synchronously:

declare function resolveLockContext(input: LockContextInput): Context;

This is source-compatible: existing callers passing a LockContext keep working.

Worth checking whether the Drizzle and DynamoDB adapters have the same narrowing.

Environment

  • @cipherstash/stack 0.19.0
  • @cipherstash/protect-ffi 0.28.0

Surfaced while updating the docs in cipherstash/docs#57. Related: #591.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Fields

No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions