Composite/compound primary keys break encryption: "key path did not yield a value" DataError
Nobody has claimed this yet.
- Dominant language
- JavaScript
- Stars
- 29
- Forks
- 23
- PR merge metrics
- No merged PRs in 30d
Description
## Summary
Tables that use a Dexie composite (compound) primary key like `[id+lang]` fail to write when wrapped with `applyEncryptionMiddleware`. Every `put()` / `bulkPut()` throws:
> DataError: Failed to execute 'put' on 'IDBObjectStore': Evaluating the object store's key path did not yield a value.
Affects v2.0.0 and v4.2.0-beta.2 (and presumably all versions in between).
## Reproduction
```ts
import Dexie from 'dexie'
import { applyEncryptionMiddleware, cryptoOptions } from 'dexie-encrypted'
const db = new Dexie('repro')
db.version(1).stores({
// Composite primary key:
meta: '[id+lang], last_synced_millis',
})
applyEncryptionMiddleware(
db,
new Uint8Array(32), // any 32-byte key
{ meta: cryptoOptions.NON_INDEXED_FIELDS },
async () => {},
)
await db.table('meta').put({ id: 'foo', lang: 'en', last_synced_millis: 0, payload: 'hello' })
// → DataError: Evaluating the object store's key path did not yield a value
```
The same code works fine if the primary key is changed to a simple `'id'`.
## Root Cause
In `dist/installHooks.js`, `encryptEntity()` reads the primary key:
```js
const primaryKey = 'primKey' in table.schema
? table.schema.primKey.keyPath
: table.schema.primaryKey.keyPath;
```
For a composite key, `primaryKey` is an **array** (e.g. `["id", "lang"]`).
But the comparisons in all three branches treat it as a string:
```js
if (key === primaryKey || indices.includes(key)) { ... } // NON_INDEXED_FIELDS
if (key !== primaryKey && rule.fields.includes(key)) { ... } // ENCRYPT_LIST
if (key !== primaryKey && ...) { ... } // UNENCRYPTED_LIST
```
`"id" === ["id","lang"]` is always `false`, so the composite-key fields are moved into the encrypted blob instead of being kept as plaintext top-level properties. IndexedDB then can't extract the key from the stored record and throws.
## Suggested Fix
Add an array-aware helper and use it in all three branches:
```js
const primaryKey = 'primKey' in table.schema
? table.schema.primKey.keyPath
: table.schema.primaryKey.keyPath;
const isPrimaryKey = (key) =>
Array.isArray(primaryKey) ? primaryKey.includes(key) : key === primaryKey;
// NON_INDEXED_FIELDS branch:
if (isPrimaryKey(key) || indices.includes(key)) { ... }
// ENCRYPT_LIST branch:
if (!isPrimaryKey(key) && rule.fields.includes(key)) { ... }
// UNENCRYPTED_LIST branch:
if (!isPrimaryKey(key) && entity.hasOwnProperty(key) && ...) { ... }
```
I've been running this patch in production via `pnpm patch` for a database with two composite-key tables (one with `[id+lang]`, one with `[category+name+lang]`) and 20+ regular tables — all encryption/decryption works correctly, including queries via `.where('[id+lang]').equals([...])`.
## Environment
- `dexie-encrypted`: 4.2.0-beta.2
- `dexie`: 4.4.2
- Browser: Chromium (also reproducible in Firefox)
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start in dist/installHooks.js at encryptEntity and reproduce the failure with the composite-key schema shown in the issue. Check all three encryption-option branches and verify that composite primary-key fields remain available to IndexedDB, then confirm put() and bulkPut() work for the example and the listed multi-field keys.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- javascript
- Domain
- databases, security
- Issue type
- Bug
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 72/100