Skip to content

wasm-inline has no model helpers: encryptModel/decryptModel (and their bulk forms) are Node-only #742

Description

@coderdan

Summary

@cipherstash/stack/wasm-inline exposes no model operations at all. The native entry offers encryptModel / decryptModel / bulkEncryptModels / bulkDecryptModels; the WASM entry offers none of them, so edge code has to map every encrypted field by hand.

Verified in tree

$ grep -n "encryptModel\|decryptModel\|bulkEncryptModels\|bulkDecryptModels" packages/stack/src/wasm-inline.ts
(no matches)

The full WASM client surface today is encrypt, decrypt, isEncrypted, encryptQuery, encryptQueryBulk, and — as of #741bulkEncrypt / bulkDecrypt.

Why this is separate from #737

#737 asked for bulk values "and the *Models variants, for parity with the native entry". #741 implemented the value-level halves only, deliberately: there is no single-model operation on this entry to build a bulk one on top of. Adding bulkEncryptModels while encryptModel doesn't exist would be an incoherent surface, and the round-trip cost #737 was actually about is fully addressed by the value-level primitives.

So this is the remaining, and larger, half: port the model layer itself.

Impact

Model helpers are the ergonomic surface — they walk a model against its table schema, encrypt every declared column, and leave everything else alone. Without them, edge code does that traversal by hand for every read and write:

// Today, on the edge — manual, and easy to get wrong as the schema grows
const [email, bio] = await client.bulkEncrypt([
  { plaintext: user.email, table: users, column: users.email },
  { plaintext: user.bio,   table: users, column: users.bio },
])
const row = { ...user, email, bio }

// What the native entry gives you
const row = await client.encryptModel(user, users)

The failure mode is a quiet one: add a column to the schema and forget to add it to the hand-written mapping, and it silently persists in plaintext. The model helpers close that gap by construction, which is exactly why they matter more on a surface where people are hand-rolling the traversal.

Scope

  1. encryptModel / decryptModel — the single-model traversal, ported to this entry.
  2. bulkEncryptModels / bulkDecryptModels — layered on those plus the bulkEncrypt / bulkDecrypt primitives added in feat(stack)!: align wasm-inline to the Result contract, and add bulkEncrypt/bulkDecrypt #741, so a list of models stays one ZeroKMS round trip.

Worth deciding up front which conventions to follow, since the WASM entry has deliberately diverged from the native one before (see #741): this surface throws rather than returning { data } | { failure }, and its bulk methods take plain index-aligned arrays rather than { id, … } envelopes. The model helpers should match the local surface for the same reason the bulk ones did — a WASM caller is reading this module's API, not the Node one.

Related

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions