syntax = "proto3"; package waymaker.kv; option go_package = "/apis/waymaker_kv"; // ============================================================ // WaymakerKvService — distributed key-value store implemented // as a thin façade over the streams subsystem. Each KV bucket // is backed by a stream named `kv:`; KV revisions are // stream sequence numbers; KV deletes are tombstones (per the // WIRE_SPEC.md tombstone header convention). // // The service lives in its own proto so the wire surface is // composable: an operator could ship a KV-only build, or layer // KV on top of someone else's streams implementation. // ============================================================ service WaymakerKvService { // ----- Bucket lifecycle ------------------------------------- rpc CreateBucket (KvCreateBucketRequest) returns (KvCreateBucketResponse); rpc DeleteBucket (KvDeleteBucketRequest) returns (KvDeleteBucketResponse); // ----- Mutations -------------------------------------------- rpc Put (KvPutRequest) returns (KvPutResponse); // CAS create — succeeds only when the key has never been // written or its current value is a tombstone. rpc Create (KvCreateRequest) returns (KvPutResponse); // CAS update — succeeds only when `expected_revision` // matches the server-side revision. rpc Update (KvUpdateRequest) returns (KvPutResponse); rpc Delete (KvDeleteRequest) returns (KvDeleteResponse); // ----- Reads ------------------------------------------------ rpc Get (KvGetRequest) returns (KvGetResponse); rpc Keys (KvKeysRequest) returns (KvKeysResponse); rpc History (KvHistoryRequest) returns (KvHistoryResponse); // ----- TTL refresh ------------------------------------------ rpc Touch (KvTouchRequest) returns (KvPutResponse); // ----- Watch ------------------------------------------------ // Server-streamed event flow for a single bucket. When `key` // is empty, every put/delete in the bucket fans out; when // `key` is set, only events at that key are emitted. rpc Watch (KvWatchRequest) returns (stream KvWatchEvent); } message KvCreateBucketRequest { string bucket = 1; uint64 max_bytes = 2; // 0 = unbounded uint64 max_value_size = 3; // 0 = no per-value cap // Bucket-level TTL (ms). 0 = no time-based eviction. // Independent of per-key TTL set via Put. uint64 max_age_ms = 4; bool ephemeral = 5; // Per-key revision cap. 0 (default) = unbounded — history depth // is then bounded only by the bucket's stream-level retention // (max_age_ms / max_bytes). When N > 0, after each successful // write to a key, older revisions of *that key* beyond the N // most recent are dropped via per-message pruning. Useful when // one bucket hosts many keys with very different write rates — // a fast-churning key won't crowd out older revisions of a // slow-changing key. NATS JetStream's "MaxRevisions" semantic. uint64 max_revisions = 6; } message KvCreateBucketResponse { bool success = 1; string result_code = 2; // "ok" | "already_exists" | "invalid_config" | "internal" string message = 3; } message KvDeleteBucketRequest { string bucket = 1; } message KvDeleteBucketResponse { bool success = 1; string result_code = 2; // "ok" | "no_such_bucket" | "internal" string message = 3; } message KvPutRequest { string bucket = 1; string key = 2; bytes value = 3; uint64 ttl_ms = 4; // per-key TTL; 0 = no TTL } message KvCreateRequest { string bucket = 1; string key = 2; bytes value = 3; uint64 ttl_ms = 4; } message KvUpdateRequest { string bucket = 1; string key = 2; bytes value = 3; // The revision the caller believes is current. Server returns // wrong_revision if mismatch. uint64 expected_revision = 4; uint64 ttl_ms = 5; } message KvPutResponse { bool success = 1; // "ok" | "no_such_bucket" | "wrong_revision" | "invalid_key" | "internal" string result_code = 2; string message = 3; uint64 revision = 4; } message KvGetRequest { string bucket = 1; string key = 2; } message KvGetResponse { bool success = 1; string result_code = 2; string message = 3; optional KvEntry entry = 4; } message KvEntry { bytes value = 1; uint64 revision = 2; int64 ts_ms = 3; } message KvDeleteRequest { string bucket = 1; string key = 2; } message KvDeleteResponse { bool success = 1; string result_code = 2; string message = 3; uint64 revision = 4; } message KvKeysRequest { string bucket = 1; } message KvKeysResponse { bool success = 1; string result_code = 2; string message = 3; repeated KvKeyEntry entries = 4; } message KvKeyEntry { string key = 1; uint64 revision = 2; bool deleted = 3; } message KvHistoryRequest { string bucket = 1; string key = 2; uint64 from_revision = 3; // 0 = from beginning uint64 limit = 4; // 0 = server default } message KvHistoryResponse { bool success = 1; string result_code = 2; string message = 3; repeated KvHistoryEntry entries = 4; } message KvHistoryEntry { bytes value = 1; uint64 revision = 2; int64 ts_ms = 3; bool deleted = 4; } message KvTouchRequest { string bucket = 1; string key = 2; uint64 ttl_ms = 3; } message KvWatchRequest { string bucket = 1; string key = 2; // empty = whole bucket } message KvWatchEvent { oneof event { KvPutEvent put = 1; KvDeleteEvent delete = 2; } } message KvPutEvent { string key = 1; bytes value = 2; uint64 revision = 3; int64 ts_ms = 4; } message KvDeleteEvent { string key = 1; uint64 revision = 2; int64 ts_ms = 3; }