@wataruoguchi/emmett-crypto-shredding-kysely
Kysely adapters for Emmett Crypto Shredding, providing PostgreSQL-backed implementations for key storage and policy resolution.
Installation
npm install @wataruoguchi/emmett-crypto-shredding-kysely @wataruoguchi/emmett-crypto-shredding kysely pgQuick Start
1. Run Database Migration
Copy the migration file from database/migrations/ to your migrations directory and run it.
2. Create Encryption Policies
⚠️ Important: Policies must be created before events can be encrypted. Create them during:
- Tenant onboarding - When a new tenant/partition is created
- Application setup - As a one-time bootstrap step
- Feature enablement - When enabling crypto shredding for existing streams
import { createPolicies } from '@wataruoguchi/emmett-crypto-shredding-kysely';
// Example: During tenant onboarding
async function onboardTenant(tenantId: string) {
// Create tenant...
// Set up encryption policies for sensitive streams
await createPolicies(db, [
{
policyId: `${tenantId}-generator`,
partition: tenantId,
streamTypeClass: 'generator', // Stream type to encrypt
encryptionAlgorithm: 'AES-GCM',
keyRotationIntervalDays: 180,
keyScope: 'stream', // 'stream' or 'type'
},
]);
}Alternative: Use default policies for common use cases:
import { createDefaultPolicies } from '@wataruoguchi/emmett-crypto-shredding-kysely';
// Creates policies for 'user-data' and 'audit-log' stream types
await createDefaultPolicies(db, 'tenant-123');3. Create Encrypted Event Store in Your Module
Wire up the crypto event store in your module factory:
import {
createCryptoEventStore,
createWebCryptoProvider,
type CryptoContext,
} from '@wataruoguchi/emmett-crypto-shredding';
import {
createKeyManagement,
createPolicyResolver,
} from '@wataruoguchi/emmett-crypto-shredding-kysely';
import { getKyselyEventStore } from '@wataruoguchi/emmett-event-store-kysely';
export function createGeneratorModule({ db, logger }) {
const eventStore = createCryptoEventStore(
getKyselyEventStore({ db, logger }),
{
policy: createPolicyResolver(db, logger),
keys: createKeyManagement(db),
crypto: createWebCryptoProvider(),
buildAAD: ({ partition, streamId }: CryptoContext) =>
new TextEncoder().encode(`${partition}:${streamId}`),
logger,
},
);
// Use eventStore in your event handler...
return createService({ eventStore });
}How it works:
- When events are appended, the policy resolver checks if the stream type has a policy
- If a policy exists, events are encrypted using the specified algorithm
- Keys are automatically generated and managed per the policy's
keyScope - If no policy exists for a stream type, events are stored unencrypted
API Reference
Policy Management
Creating Policies
import {
createPolicies,
createDefaultPolicies,
type DatabasePolicyConfig
} from '@wataruoguchi/emmett-crypto-shredding-kysely';
// Create custom policies
await createPolicies(db, [
{
policyId: 'tenant-123-generator',
partition: 'tenant-123',
streamTypeClass: 'generator',
encryptionAlgorithm: 'AES-GCM',
keyRotationIntervalDays: 180,
keyScope: 'stream', // 'stream' = one key per stream, 'type' = one key per stream type
},
]);
// Or use defaults (creates 'user-data' and 'audit-log' policies)
await createDefaultPolicies(db, 'tenant-123');Managing Policies
import {
updatePolicy,
listPolicies,
deletePolicy
} from '@wataruoguchi/emmett-crypto-shredding-kysely';
// Update policy
await updatePolicy(db, 'tenant-123-generator', 'tenant-123', {
encryptionAlgorithm: 'AES-GCM',
keyRotationIntervalDays: 365,
});
// List all policies for a partition
const policies = await listPolicies(db, 'tenant-123');
// Delete policy (stops encrypting new events for this stream type)
await deletePolicy(db, 'tenant-123-generator', 'tenant-123');Key Management
The createKeyManagement function returns a service that automatically manages encryption keys based on your policies:
import { createKeyManagement } from '@wataruoguchi/emmett-crypto-shredding-kysely';
const keyManagement = createKeyManagement(db);
// Get or create active key (usually done automatically by crypto event store)
const key = await keyManagement.getActiveKey({
partition: 'tenant-123',
keyRef: 'generator-stream-123', // Varies based on keyScope
});
// Manually rotate key for a specific reference
await keyManagement.rotateKey({
partition: 'tenant-123',
keyRef: 'generator-stream-123',
});
// Crypto shredding: Destroy all keys for a partition
// This makes all encrypted events for this partition unrecoverable
await keyManagement.destroyPartitionKeys({
partition: 'tenant-123',
});Common Patterns
Tenant Onboarding with Encryption
async function createTenantWithEncryption(tenantData: { name: string }) {
// 1. Create tenant
const tenant = await tenantRepository.create(tenantData);
// 2. Set up encryption policies for sensitive streams
await createPolicies(db, [
{
policyId: `${tenant.id}-user-data`,
partition: tenant.id,
streamTypeClass: 'user-data',
encryptionAlgorithm: 'AES-GCM',
keyRotationIntervalDays: 180,
keyScope: 'stream',
},
]);
return tenant;
}Selective Encryption by Stream Type
Only encrypt sensitive stream types. Other streams remain unencrypted for performance:
// Encrypt sensitive streams
await createPolicies(db, [
{
policyId: `${tenantId}-user-data`,
partition: tenantId,
streamTypeClass: 'user-data', // ✅ Encrypted
encryptionAlgorithm: 'AES-GCM',
keyRotationIntervalDays: 180,
keyScope: 'stream',
},
]);
// No policy for 'cart' stream type → stored unencrypted ✓Crypto Shredding (Right to be Forgotten)
async function forgetTenant(tenantId: string) {
// Destroy all encryption keys for the tenant
// This makes all encrypted events permanently unrecoverable
await keyManagement.destroyPartitionKeys({ partition: tenantId });
// Optionally: Delete unencrypted data or anonymize tenant records
// ...
}Troubleshooting
Events Not Being Encrypted
Problem: Events are stored in plaintext even though policies exist.
Checklist:
- ✅ Verify policy exists:
await listPolicies(db, tenantId) - ✅ Check
streamTypeClassmatches your stream type exactly - ✅ Ensure policy was created before appending events
- ✅ Verify
createCryptoEventStoreis being used (not just base event store)
Cannot Read Encrypted Events
Problem: Events cannot be decrypted or appear as [encrypted].
Possible causes:
- Keys have been destroyed (crypto shredding)
- Wrong partition/tenant context
- Database connectivity issues with
encryption_keystable
See Also
- Crypto Shredding Core - Core encryption functionality
- Event Store Kysely - Base event store implementation
- Example Application - Complete working example with crypto shredding