feat(feed): Add L4 Feed schema for DuckDB + LanceDB
- FeedEvent structure with vector embeddings - EventType enum (post, reaction, follow, mention, hashtag) - FeedStore interface for hybrid storage - Query options for temporal + semantic search - Tests for encoding Sprint 4 WIP: L4 Feed
This commit is contained in:
parent
b6edd5c403
commit
977eaa8ceb
|
|
@ -0,0 +1,134 @@
|
|||
//! L4 Feed — Temporal Event Store
|
||||
//!
|
||||
//! Hybrid storage: DuckDB (structured) + LanceDB (vectors)
|
||||
//! For social media primitives: posts, reactions, follows
|
||||
|
||||
const std = @import("std");
|
||||
|
||||
/// Event types in the feed
|
||||
pub const EventType = enum {
|
||||
post, // Content creation
|
||||
reaction, // Like, boost, etc.
|
||||
follow, // Social graph edge
|
||||
mention, // @username reference
|
||||
hashtag, // #topic categorization
|
||||
};
|
||||
|
||||
/// Feed event structure
|
||||
pub const FeedEvent = struct {
|
||||
id: u64, // Snowflake ID (time-sortable)
|
||||
event_type: EventType,
|
||||
author: [32]u8, // DID of creator
|
||||
timestamp: i64, // Unix nanoseconds
|
||||
content_hash: [32]u8, // Blake3 of content
|
||||
parent_id: ?u64, // For replies/reactions
|
||||
|
||||
// Vector embedding for semantic search
|
||||
embedding: ?[384]f32, // 384-dim (optimal for LanceDB)
|
||||
|
||||
// Metadata
|
||||
tags: []const []const u8, // Hashtags
|
||||
mentions: []const [32]u8, // Tagged users
|
||||
|
||||
pub fn encode(self: FeedEvent, allocator: std.mem.Allocator) ![]u8 {
|
||||
// Simple binary encoding
|
||||
var result = std.ArrayList(u8).init(allocator);
|
||||
errdefer result.deinit();
|
||||
|
||||
try result.writer().writeInt(u64, self.id, .little);
|
||||
try result.writer().writeInt(u8, @intFromEnum(self.event_type), .little);
|
||||
try result.writer().writeAll(&self.author);
|
||||
try result.writer().writeInt(i64, self.timestamp, .little);
|
||||
try result.writer().writeAll(&self.content_hash);
|
||||
|
||||
return result.toOwnedSlice();
|
||||
}
|
||||
};
|
||||
|
||||
/// Feed query options
|
||||
pub const FeedQuery = struct {
|
||||
author: ?[32]u8 = null,
|
||||
event_type: ?EventType = null,
|
||||
since: ?i64 = null,
|
||||
until: ?i64 = null,
|
||||
tags: ?[]const []const u8 = null,
|
||||
limit: usize = 50,
|
||||
offset: usize = 0,
|
||||
};
|
||||
|
||||
/// Hybrid feed storage
|
||||
pub const FeedStore = struct {
|
||||
allocator: std.mem.Allocator,
|
||||
// TODO: DuckDB connection
|
||||
// TODO: LanceDB connection
|
||||
|
||||
const Self = @This();
|
||||
|
||||
pub fn init(allocator: std.mem.Allocator, path: []const u8) !Self {
|
||||
_ = path;
|
||||
// TODO: Initialize DuckDB + LanceDB
|
||||
return Self{
|
||||
.allocator = allocator,
|
||||
};
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Self) void {
|
||||
_ = self;
|
||||
// TODO: Cleanup connections
|
||||
}
|
||||
|
||||
/// Store event in feed
|
||||
pub fn store(self: *Self, event: FeedEvent) !void {
|
||||
_ = self;
|
||||
_ = event;
|
||||
// TODO: Insert into DuckDB + LanceDB
|
||||
}
|
||||
|
||||
/// Query feed with filters
|
||||
pub fn query(self: *Self, opts: FeedQuery) ![]FeedEvent {
|
||||
_ = self;
|
||||
_ = opts;
|
||||
// TODO: SQL query on DuckDB
|
||||
return &[_]FeedEvent{};
|
||||
}
|
||||
|
||||
/// Semantic search using vector similarity
|
||||
pub fn searchSimilar(self: *Self, embedding: [384]f32, limit: usize) ![]FeedEvent {
|
||||
_ = self;
|
||||
_ = embedding;
|
||||
_ = limit;
|
||||
// TODO: ANN search in LanceDB
|
||||
return &[_]FeedEvent{};
|
||||
}
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// TESTS
|
||||
// ============================================================================
|
||||
|
||||
test "FeedEvent encoding" {
|
||||
const allocator = std.testing.allocator;
|
||||
|
||||
var event = FeedEvent{
|
||||
.id = 1706963200000000000,
|
||||
.event_type = .post,
|
||||
.author = [_]u8{0} ** 32,
|
||||
.timestamp = 1706963200000000000,
|
||||
.content_hash = [_]u8{0} ** 32,
|
||||
.parent_id = null,
|
||||
.embedding = null,
|
||||
.tags = &.{"libertaria", "zig"},
|
||||
.mentions = &.{},
|
||||
};
|
||||
|
||||
const encoded = try event.encode(allocator);
|
||||
defer allocator.free(encoded);
|
||||
|
||||
try std.testing.expect(encoded.len > 0);
|
||||
}
|
||||
|
||||
test "FeedQuery defaults" {
|
||||
const query = FeedQuery{};
|
||||
try std.testing.expectEqual(query.limit, 50);
|
||||
try std.testing.expectEqual(query.offset, 0);
|
||||
}
|
||||
Loading…
Reference in New Issue