@secret — @hash + @omitjson Composite
Combines @hash (Balloon hashing, memory-hard) and @omitjson (stripped from all read results) into a single annotation. Ideal for API keys, OAuth tokens, webhook signing secrets — values that must be hashed at rest and never returned to callers.
Syntax
src/interfaces.ts
// @secret — hashed on write, stripped from all reads
apiKey?: string;Usage
src/auth.ts
// Create — raw value returned ONCE
const key = await ApiKey.insert({ apiKey: "sk_live_a1b2c3d4e5f6" });
console.log(key.apiKey);
// "sk_live_a1b2c3d4e5f6" ← copy this now!
// Any subsequent read:
const stored = await ApiKey.get({ id: key.id });
console.log(stored.apiKey);
// undefined (stripped by @omitjson)
// The hash is still there internally for verification:
const match = await stored.apiKey.verify("sk_live_a1b2c3d4e5f6");
// true (the hash is retrievable internally, just not serialized)How It Works
| Operation | Behavior |
|---|---|
| Insert / Update write | Value is Balloon-hashed (memory-hard) via @hash before storage |
| get() / getAll() / query() | Field is stripped from result by @omitjson |
.toJSON() | Field is excluded from serialized output |
Internal .verify() | The hash stays attached to the entity — .verify(plaintext) works for comparison |
Errors
| Issue | Cause | Fix |
|---|---|---|
Field is undefined on fetched entity | Expected — @omitjson strips it from reads | Use .select('apiKey') in query builder if you need the hash |
| Logs show the plaintext value | @secret does NOT redact SQL logs | Use a separate log filter or avoid logging the field |
Conventions
- Use
@secretinstead of@hash+@omitjsonseparately — it's the same thing - The raw value is only available on the insert response — always copy it before proceeding
- Not log-redacted — contrary to some older docs, @secret does not mask SQL logs. Add application-level log filtering if needed