Context
Codra relies on a high-speed Key-Value store to manage ephemeral state. In the Cloudflare environment, this is backed by env.APP_KV. It is heavily used for caching user sessions, storing rate-limit counters, and maintaining the system:active_jobs flag (which prevents the system from polling the database when no jobs are active). For the Node deployment, we must implement this KV interface using a standard Redis database. Using Redis ensures that our primary Postgres database is protected from high-frequency, transient read/write operations.
Task
- Dependencies:
- Install
ioredis in the apps/node workspace. Crucial: Do not install this in @codraoss/core, as the core must remain platform-agnostic to compile securely for Cloudflare's V8 isolates.
- Adapter Creation:
- Create
apps/node/src/adapters/redis-kv.ts.
- Export a class
RedisKVAdapter that implements the KeyValueStore interface defined in @codraoss/core/src/ports/kv.ts.
- Method Implementations:
- Constructor: Accept a fully initialized
ioredis client instance.
get<T>(key: string): Promise<T | null>: Map to redis.get(key). Since Redis returns raw strings, you must parse the string back into a JSON object/type before returning it, matching the behavior of Cloudflare KV. Return null if the key does not exist.
put(key: string, value: any, options?: { expirationTtl?: number }): Promise<void>: Map to redis.set(). Ensure the value is correctly JSON stringified. Critically, you must handle the TTL. Cloudflare KV uses expirationTtl representing seconds. You must translate this to the Redis EX argument (e.g., await redis.set(key, JSON.stringify(value), 'EX', options.expirationTtl)).
delete(key: string): Promise<void>: Map directly to redis.del(key).
- Wiring & Integration:
- In
apps/node/src/index.ts, initialize the ioredis client using the REDIS_URL environment variable.
- Instantiate
new RedisKVAdapter(redisClient) and inject it into the Hono API context (AppBindings.APP_KV), replacing the mock stub created in the scaffolding issue.
- Ensure you catch and log any Redis connection errors using
@codraoss/core/logger.
Acceptance Criteria
- The
RedisKVAdapter strictly adheres to the @codraoss/core KeyValueStore TypeScript interface.
- Reading a non-existent key returns
null without throwing an error.
- Writing a key with an
expirationTtl correctly sets the TTL in Redis (verifiable via the Redis TTL command).
- The API successfully stores and retrieves complex JSON objects from the local Redis instance.
Relevant Files/Paths
packages/core/src/ports/kv.ts (Reference only)
apps/node/src/adapters/redis-kv.ts (New)
apps/node/src/index.ts
Context
Codra relies on a high-speed Key-Value store to manage ephemeral state. In the Cloudflare environment, this is backed by
env.APP_KV. It is heavily used for caching user sessions, storing rate-limit counters, and maintaining thesystem:active_jobsflag (which prevents the system from polling the database when no jobs are active). For the Node deployment, we must implement this KV interface using a standard Redis database. Using Redis ensures that our primary Postgres database is protected from high-frequency, transient read/write operations.Task
ioredisin theapps/nodeworkspace. Crucial: Do not install this in@codraoss/core, as the core must remain platform-agnostic to compile securely for Cloudflare's V8 isolates.apps/node/src/adapters/redis-kv.ts.RedisKVAdapterthat implements theKeyValueStoreinterface defined in@codraoss/core/src/ports/kv.ts.ioredisclient instance.get<T>(key: string): Promise<T | null>: Map toredis.get(key). Since Redis returns raw strings, you must parse the string back into a JSON object/type before returning it, matching the behavior of Cloudflare KV. Returnnullif the key does not exist.put(key: string, value: any, options?: { expirationTtl?: number }): Promise<void>: Map toredis.set(). Ensure the value is correctly JSON stringified. Critically, you must handle the TTL. Cloudflare KV usesexpirationTtlrepresenting seconds. You must translate this to the RedisEXargument (e.g.,await redis.set(key, JSON.stringify(value), 'EX', options.expirationTtl)).delete(key: string): Promise<void>: Map directly toredis.del(key).apps/node/src/index.ts, initialize theioredisclient using theREDIS_URLenvironment variable.new RedisKVAdapter(redisClient)and inject it into the Hono API context (AppBindings.APP_KV), replacing the mock stub created in the scaffolding issue.@codraoss/core/logger.Acceptance Criteria
RedisKVAdapterstrictly adheres to the@codraoss/coreKeyValueStoreTypeScript interface.nullwithout throwing an error.expirationTtlcorrectly sets the TTL in Redis (verifiable via the RedisTTLcommand).Relevant Files/Paths
packages/core/src/ports/kv.ts(Reference only)apps/node/src/adapters/redis-kv.ts(New)apps/node/src/index.ts