소스 정보
- 저장소
- AzureFoxStudios/wabi
- 최근 소스 활동
- 2026년 8월 28일 15:43
- 감지된 SKILL.md 언어
- 영어
- 스타
- 4
- 포크
- 2
설치 방법
기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.
소스 파일 검토
설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.
메뉴
기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.
설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
직접 명령은 검토 Prompt를 거치지 않습니다. 실행하기 전에 소스를 확인하세요.
npx skills add https://github.com/AzureFoxStudios/wabi --skill wabidb-wabi-server-adapter명령은 한 줄로 유지됩니다. 복사하기 전에 가로로 스크롤해 전체 내용을 확인하세요.
로컬 사본을 원하시나요? SkillsMP에서 현재 제공할 수 있는 파일을 다운로드하세요.
Implementation pattern for Wabi channel categories: adding ChannelKind::Category, position/parentId support, and type updates.
Wabi production deployment and runtime debugging. Covers the Rust wabi-server binary (rust_embed static frontend), the SvelteKit build-mode mismatch (adapter-static SPA vs adapter-node SSR and why the Rust server needs index.html), Cloudflare/caddy/cloudflared tunnel WebSockets, WabiDB data-dir locks, and the selfhostability check. Use when a Wabi deploy will not boot, the login page is blank or stuck on "Starting Wabi"/"Work Offline", socket.io WS fails through Cloudflare, wabi-server restart-loops with "engine already running", or the user asks whether Wabi is still selfhostable or hardwired to wabi.chat.
Verify a Wabi binary before swap: addons, probes, locks.
SKILL.md 표시 중
| name | wabidb-wabi-server-adapter |
| version | 0.1.0 |
| author | Hermes |
| description | Learn WabiDB integration patterns for wabi-server adapter. |
| platforms | ["linux","macos","windows"] |
| metadata | {"hermes":{"tags":["Integration","WabiDB","Adapter","WabiServer"]}} |
This skill provides a structured approach to learning WabiDB's integration patterns specifically for the wabi-server adapter, focusing on the WabiStore trait implementation and adapter patterns.
Use these commands to explore WabiDB integration patterns:
# View WabiStore trait implementation
read_file /var/home/Ronin/wabi/core/crates/wabidb/src/engine/wabi_store.rs
# View adapter implementation
read_file /var/home/Ronin/wabi/core/crates/wabidb/src/adapter/mod.rs
# Run integration tests
cargo test -p wabidb --test wabi_store
cargo test -p wabidb --test adapter
| Component | Key Files | Key Methods | Purpose |
|---|---|---|---|
| WabiStore | wabi_store.rs | send_message, create_user, get_message | Define storage API |
| Adapter | adapter/mod.rs | WdbAdapter, WabiStore | Implement adapter |
| LocalStore | wabi_store.rs | LocalWabiStore | In-memory implementation |
| Domain | domain.rs | Message, User, Channel | Define domain model |
Pattern: The WabiStore trait defines the storage API for WabiDB.
Key Components:
send_message: Persist a message in a channelcreate_user: Create a new usercreate_channel: Create a new channeladd_reaction: Add or update a reaction on a messageadd_channel_member: Add a user to a channelget_message: Retrieve a single message by IDget_user: Look up a user by IDget_channel: Look up a channel by IDExample Implementation:
pub trait WabiStore: Send + Sync {
async fn send_message(
&self,
channel_id: &str,
user_id: u64,
content: &str,
) -> Result<String>;
async fn create_user(
&self,
username: &str,
handle: Option<&str>,
password_hash: &str,
) -> Result<u64>;
async fn create_channel(
&self,
name: &str,
channel_kind: crate::domain::ChannelKind,
owner_user_id: u64,
) -> Result<String>;
async fn get_message(&self, message_id: &str) -> Result<Option<String>>;
}
Pattern: Implement the WabiStore trait for wabi-server.
Key Components:
WdbAdapter: The main adapter implementationLocalWabiStore: In-memory implementation for testingWabiDbEngine: The core engine implementationWabiStore: The trait definitionExample Implementation:
pub struct WdbAdapter {
engine: Arc<WabiDbEngine>,
}
impl WabiStore for WdbAdapter {
async fn send_message(
&self,
channel_id: &str,
user_id: u64,
content: &str,
) -> Result<String> {
let cmd = CommandCommit {
caller_user_id: user_id,
caller_device_id: "adapter".into(),
command_name: "send_message".into(),
idempotency_key: None,
events: vec![EventToWrite {
stream_id: format!("channel:{}", channel_id),
event_type: "message_sent".into(),
stream_kind: 1,
record_kind: RecordKind::Event,
plaintext: content.as_bytes().to_vec(),
}],
essential: true,
response_tx: oneshot::channel().0,
};
let outcome = self.engine.run_command(cmd).await?;
Ok(format!("msg_{}", outcome.commit_seq))
}
}
Pattern: Implement adapter patterns for WabiDB.
Key Components:
WdbAdapter: The main adapter implementationWabiDbEngine: The core engine implementationWabiStore: The trait definitionLocalWabiStore: In-memory implementation for testingExample Adapter:
pub struct WdbAdapter {
engine: Arc<WabiDbEngine>,
}
impl WdbAdapter {
pub fn new(engine: Arc<WabiDbEngine>) -> Self {
Self { engine }
}
}
impl WabiStore for WdbAdapter {
async fn send_message(
&self,
channel_id: &str,
user_id: u64,
content: &str,
) -> Result<String> {
let cmd = CommandCommit {
caller_user_id: user_id,
caller_device_id: "adapter".into(),
command_name: "send_message".into(),
idempotency_key: None,
events: vec![EventToWrite {
stream_id: format!("channel:{}", channel_id),
event_type: "message_sent".into(),
stream_kind: 1,
record_kind: RecordKind::Event,
plaintext: content.as_bytes().to_vec(),
}],
essential: true,
response_tx: oneshot::channel().0,
};
let = .engine.(cmd).?;
((, outcome.commit_seq))
}
}
Pattern: Integrate WabiDB's domain model with wabi-server.
Key Components:
Message: Domain model for messagesUser: Domain model for usersChannel: Domain model for channelsReaction: Domain model for reactionsExample Domain Model:
pub struct Message {
pub id: String,
pub channel_id: String,
pub user_id: u64,
pub content: String,
pub created_at_micros: i64,
}
pub struct User {
pub id: u64,
pub username: String,
pub handle: Option<String>,
pub password_hash: String,
}
pub struct Channel {
pub id: String,
pub name: String,
pub channel_kind: ChannelKind,
pub owner_user_id: u64,
}
Pattern: Comprehensive tests validate the integration.
Key Test Cases:
Example Test:
#[tokio::test]
async fn test_send_message() {
let engine = Arc::new(WabiDbEngine::open(config).await.unwrap());
let adapter = WdbAdapter::new(engine);
let message_id = adapter.send_message("ch_test", 1, "Hello, world!").await.unwrap();
let message = adapter.get_message(&message_id).await.unwrap().unwrap();
assert_eq!(message.content, "Hello, world!");
}
Wabi's public/text chat has three storage classes, resolved in the send path BEFORE any durable write:
wdb.send_message entirely; assign a live_<uuid> id, push to the in-memory session_messages cache, emit the socket message event, and SKIP the TTL-delete spawn. Gone on process restart. Operator-readable while live (NOT E2EE — never market it as private).wdb.send_message + schedule delete after DEFAULT_CHANNEL_AUTO_DELETE_MS (24h) unless an explicit map/policy overrides.wdb.send_message, no TTL spawn. Explicit opt-in.Load-bearing rule — do NOT add a class field to the Channel domain struct. Adding a trailing field to a postcard-encoded record (Channel, UserRecord, MessageRecord, etc.) breaks replay of older on-disk events unless you also write a RecordV0/V1 dual-decode fallback (the same bug class that dropped Tim's user accounts). For a per-channel feature flag that does NOT need to survive restart the same way message bodies do, key it off an existing in-memory map instead:
// live/forever sentinels live in the existing in-memory label map,
// NOT in the postcard-encoded Channel record.
pub async fn channel_is_live(app: &AppState, channel_id: &str) -> bool {
app.channel_auto_delete_label // Arc<RwLock<HashMap<String,String>>>
.read().await
.get(channel_id)
.map(|s| s == "live") // "forever" is another sentinel in the same map
.unwrap_or(false)
}
Both live send paths must carry the gate: socketio/messages.rs::on_message (registered at socketio/wiring.rs via socket.on("message", ...) — the #[allow(dead_code)] on it is only lint suppression; it IS the live handler) AND the REST api/messages.rs::send_message. Fixing only the socket path leaves an HTTP persistence hole.
Contract test must prove "never written," not "not listed after restart." Use a tempfile::TempDir data dir, send a unique LIVE-CANARY-<uuid> body, then recursively scan every file under the data dir and assert the canary bytes are absent (and a control non-live channel's canary IS present, i.e. new .wseg segment appears). A test that only checks history-after-restart can pass even if the body was briefly written then deleted.
See references/message-storage-classes.md for the full send-path split, default-retention nesting, and the OpenCode-delegation verification notes from the 2026-07-17 live-rooms build.
WdbAdapter::list_users() reads UsersProjection::list(state, UsersFilter::default()) — EVERY user row (registered + guests + bots), profile fields included. It feeds the socket init payload's serverMembers key (socketio/presence.rs); the frontend renders the People panel's greyed-out "Offline — N" section as serverMembers − online. Guest discriminator = empty password_hash, exposed as is_registered on the wire (UserView → generated isRegistered?: boolean | null). Admin UI merges $serverMembers + $users (online wins, keyed dbUserId ?? id). Never hardcode serverMembers: Vec::new() — the offline roster silently vanishes.
API surface drift (audited 2026-08-21): wabi-server/src/api/ now includes modules these skills never covered: payments/ (dir), jobs, mesh, nodes, standby, lan, push, operator, steam, whiteboard, calls. When wiring anything new, read routes.rs for the current nest layout instead of assuming the older module list.
Trait Object Limitations: Ensure the WabiStore trait is object-safe
Async Trait Implementation: Properly handle async trait methods
Error Handling: Implement comprehensive error handling
Domain Model Mismatches: Ensure domain model compatibility
Adapter Initialization: Properly initialize the adapter
Testing Limitations: Use LocalWabiStore for testing
Adapter emit-shape is NOT uniform across modules (load-bearing). When you add a new event from WdbAdapter (in wabi-server/src/adapter/mod.rs), the emit call differs by module. The forum adapter uses self.wdb.emit(event).await?; the wiki adapter uses self.run(actor_user_id, "op_name", channel_id, "event_type", 6, payload, true, None).await? (where payload = encode_record(&record) and the returned seq becomes the id, e.g. format!("page_{:x}", seq)). Always read the target module's EXISTING create method and paste its exact emit call into the delegate prompt — do not assume one shape. A wrong emit call either fails to compile or (worse) silently doesn't persist the event, so the post-apply projection lookup test fails. (Verified 2026-07-18 building gallery + wiki surfaces.)
Multi-projection surfaces. A surface can need two projections (e.g. gallery: GalleryWorkRecord + GalleryFeedbackRecord; wiki: WikiPageRecord extended + WikiRevisionRecord). Register BOTH in engine/mod.rs::build_type_registry() as separate entries with distinct /. For the second projection in an existing module (wiki), add the new record + to the SAME file — no new module entry needed.
When the server returns 500s (auth, user, theme, places) or server_owner.json is stale/missing after drift or test data, perform a data dir reset.
Key lessons from sessions:
bun run dev). 3001 = backend wabi-server. serverUrl.ts rewrites all backend URLs to :3001 when the page is on 5173 (source: 'dev_vite'). This is why users ask "why the swap" — there is no swap; they are two processes.WABIDB_ROOT_KEY (or equivalent from_passphrase). Missing it produces: validation failed for load_bootstrap_key: env var WABIDB_ROOT_KEY not set.wabidb/.lock before restart.See references/wabi-data-dir-reset.md for the exact backup + rm -rf wabidb server_owner.json + restart steps using the active --data-dir.
This was the recovery path used when the live frontend hit 500 while connected as "wabi" (owner marker existed but state was inconsistent). First registration after a wipe creates the owner.
Confirm your understanding by running these commands:
# Run WabiStore tests
cargo test -p wabidb --test wabi_store
# Run adapter tests
cargo test -p wabidb --test adapter
# Run domain model tests
cargo test -p wabidb --test domain
These tests should all pass, demonstrating the key aspects of WabiDB's integration patterns for the wabi-server adapter.
ProjectionRegistrationindex_namerecord_type_nameXxxProjectionwiki.rsprojections/mod.rs