Follow-up from the portable-encryptedSupabase design (#708). Named as a deferred item by three of the four plans in that series.
What exists
After the portable-runtime series lands there will be three hand-rolled structural views of the encryption client, in three published packages:
| Type |
Location |
Methods |
OperandEncryptionClient |
packages/stack-drizzle/src/operators.ts:67-73 |
encryptQuery only |
CipherstashV3Client |
packages/prisma-next/src/v3/sdk-adapter-v3.ts:74-90 |
bulkEncrypt, decrypt, bulkDecrypt, encryptQuery |
AdapterEncryptionClient |
packages/stack-supabase (new) |
five model/bulk methods, no encryptQuery |
Plus a fourth, smaller duplication: two structural v3-column predicates — hasBuildColumnKeyMap (packages/stack/src/types.ts:276) and the new isV3ColumnLike in stack-supabase.
The trap
The three method sets are disjoint. Drizzle's view is operand-shaped, Prisma Next's is model-shaped, the Supabase one is the five methods that pipeline actually calls — no two of them are a subset of a third. So consolidation is a union of three views, not a pick-the-biggest, and it is a design job (which methods are core, which stay per-adapter) rather than a copy-paste.
packages/stack/src/adapter-kit.ts is 60 lines today with no client interface at all, so there is nothing to reuse yet and nothing currently being duplicated in the strict sense — the three existing views solve a genuinely different problem (a structural view so a test double needs no cast) and none of them has a WASM adapter.
The shape
AdapterEncryptionClient + fromNativeEncryptionClient → packages/stack/src/adapter-kit.ts
fromWasmEncryptionClient → packages/stack/src/wasm-inline.ts
Split that way so neither entry drags the other's dependency. Only core knows both client shapes, which is the argument for the move. The Adapter… name was chosen up front specifically so this becomes a file move rather than a rename.
Why it was deferred
Additions to adapter-kit are audit decisions (packages/stack/src/adapter-kit.ts:1-19), the move touches three published packages, and the second consumer of the new port does not exist yet.
Two smaller items to fold in
- An entry check on top of the name check for
options.encryptionClient. assertClientShape validates method names, and both client shapes carry all five, so it cannot detect a native client passed through fromWasmEncryptionClient. bulkEncrypt.length discriminates reliably — 2 on nominal (encryption/index.ts:651), 2 on typed (v3.ts:367), 1 on WASM (wasm-inline.ts:944) — where decryptModel.length does not. That is validation of an already-bound choice, not runtime sniffing.
- Verify
supportsLockContext rather than hardcoding it. fromNativeEncryptionClient sets it true unconditionally. A client whose ops lack .withLockContext passes the builder guard and then dies at execute with a raw TypeError, which query-builder.ts:596-598 reports as encryptionError: undefined — the anonymous 500 that a lock-context failure is specifically supposed to never become.
Follow-up from the portable-
encryptedSupabasedesign (#708). Named as a deferred item by three of the four plans in that series.What exists
After the portable-runtime series lands there will be three hand-rolled structural views of the encryption client, in three published packages:
OperandEncryptionClientpackages/stack-drizzle/src/operators.ts:67-73encryptQueryonlyCipherstashV3Clientpackages/prisma-next/src/v3/sdk-adapter-v3.ts:74-90bulkEncrypt,decrypt,bulkDecrypt,encryptQueryAdapterEncryptionClientpackages/stack-supabase(new)encryptQueryPlus a fourth, smaller duplication: two structural v3-column predicates —
hasBuildColumnKeyMap(packages/stack/src/types.ts:276) and the newisV3ColumnLikeinstack-supabase.The trap
The three method sets are disjoint. Drizzle's view is operand-shaped, Prisma Next's is model-shaped, the Supabase one is the five methods that pipeline actually calls — no two of them are a subset of a third. So consolidation is a union of three views, not a pick-the-biggest, and it is a design job (which methods are core, which stay per-adapter) rather than a copy-paste.
packages/stack/src/adapter-kit.tsis 60 lines today with no client interface at all, so there is nothing to reuse yet and nothing currently being duplicated in the strict sense — the three existing views solve a genuinely different problem (a structural view so a test double needs no cast) and none of them has a WASM adapter.The shape
AdapterEncryptionClient+fromNativeEncryptionClient→packages/stack/src/adapter-kit.tsfromWasmEncryptionClient→packages/stack/src/wasm-inline.tsSplit that way so neither entry drags the other's dependency. Only core knows both client shapes, which is the argument for the move. The
Adapter…name was chosen up front specifically so this becomes a file move rather than a rename.Why it was deferred
Additions to
adapter-kitare audit decisions (packages/stack/src/adapter-kit.ts:1-19), the move touches three published packages, and the second consumer of the new port does not exist yet.Two smaller items to fold in
options.encryptionClient.assertClientShapevalidates method names, and both client shapes carry all five, so it cannot detect a native client passed throughfromWasmEncryptionClient.bulkEncrypt.lengthdiscriminates reliably — 2 on nominal (encryption/index.ts:651), 2 on typed (v3.ts:367), 1 on WASM (wasm-inline.ts:944) — wheredecryptModel.lengthdoes not. That is validation of an already-bound choice, not runtime sniffing.supportsLockContextrather than hardcoding it.fromNativeEncryptionClientsets ittrueunconditionally. A client whose ops lack.withLockContextpasses the builder guard and then dies at execute with a rawTypeError, whichquery-builder.ts:596-598reports asencryptionError: undefined— the anonymous 500 that a lock-context failure is specifically supposed to never become.