search for standard sites pub-search.waow.tech
search zig blog atproto
11
fork

Configure Feed

Select the types of activity you want to include in your feed.

performance: reuse http client, batch stats query

- turso client now persists http connection instead of creating per query
- /stats uses single query with subselects instead of two round trips
- move fromRow to domain structs (Doc, Pub, TagCount)
- update zql hash

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

zzstoatzz e72a2238 1a172392

+74 -42
+1 -1
backend/build.zig.zon
··· 10 10 }, 11 11 .zql = .{ 12 12 .url = "https://github.com/zzstoatzz/zql/archive/main.tar.gz", 13 - .hash = "zql-0.0.1-alpha-xNRI4II5AAB3EZKhw1ER_m5yJLon0WgAJj_KANA2BwEl", 13 + .hash = "zql-0.0.1-alpha-xNRI4IRNAABUb9gLat5FWUaZDD5HvxAxet_-elgR_A_y", 14 14 }, 15 15 }, 16 16 .paths = .{
+66 -37
backend/src/db/mod.zig
··· 89 89 c.exec("DELETE FROM publications_fts WHERE uri = ?", &.{uri}) catch {}; 90 90 } 91 91 92 - // query types with comptime column extraction 92 + const Doc = struct { 93 + uri: []const u8, 94 + did: []const u8, 95 + title: []const u8, 96 + snippet: []const u8, 97 + created_at: []const u8, 98 + rkey: []const u8, 99 + base_path: []const u8, 100 + has_publication: bool, 101 + 102 + fn fromRow(row: Row) Doc { 103 + return .{ 104 + .uri = row.text(0), 105 + .did = row.text(1), 106 + .title = row.text(2), 107 + .snippet = row.text(3), 108 + .created_at = row.text(4), 109 + .rkey = row.text(5), 110 + .base_path = row.text(6), 111 + .has_publication = row.int(7) != 0, 112 + }; 113 + } 114 + }; 115 + 93 116 const DocsByTag = zql.Query( 94 117 \\SELECT d.uri, d.did, d.title, '' as snippet, 95 118 \\ d.created_at, d.rkey, p.base_path, ··· 126 149 \\ORDER BY rank LIMIT 40 127 150 ); 128 151 129 - const Doc = struct { 152 + const Pub = struct { 130 153 uri: []const u8, 131 154 did: []const u8, 132 - title: []const u8, 155 + name: []const u8, 133 156 snippet: []const u8, 134 - created_at: []const u8, 135 157 rkey: []const u8, 136 158 base_path: []const u8, 137 - has_publication: bool, 159 + 160 + fn fromRow(row: Row) Pub { 161 + return .{ 162 + .uri = row.text(0), 163 + .did = row.text(1), 164 + .name = row.text(2), 165 + .snippet = row.text(3), 166 + .rkey = row.text(4), 167 + .base_path = row.text(5), 168 + }; 169 + } 138 170 }; 139 171 140 172 const PubSearch = zql.Query( ··· 147 179 \\ORDER BY rank LIMIT 10 148 180 ); 149 181 150 - const Pub = struct { 151 - uri: []const u8, 152 - did: []const u8, 153 - name: []const u8, 154 - snippet: []const u8, 155 - rkey: []const u8, 156 - base_path: []const u8, 182 + const TagCount = struct { 183 + tag: []const u8, 184 + count: i64, 185 + 186 + fn fromRow(row: Row) TagCount { 187 + return .{ .tag = row.text(0), .count = row.int(1) }; 188 + } 157 189 }; 190 + 191 + const TagsQuery = zql.Query( 192 + \\SELECT tag, COUNT(*) as count 193 + \\FROM document_tags 194 + \\GROUP BY tag 195 + \\ORDER BY count DESC 196 + \\LIMIT 100 197 + ); 158 198 159 199 pub fn search(alloc: Allocator, query: []const u8, tag_filter: ?[]const u8) ![]const u8 { 160 200 var c = &(client orelse return error.NotInitialized); ··· 178 218 if (doc_result) |*res| { 179 219 defer res.deinit(); 180 220 for (res.rows) |row| { 181 - const doc = DocsByFts.fromRow(Doc, row); 221 + const doc = Doc.fromRow(row); 182 222 try jw.beginObject(); 183 223 try jw.objectField("type"); 184 224 try jw.write(if (doc.has_publication) "article" else "looseleaf"); ··· 210 250 if (pub_result) |*res| { 211 251 defer res.deinit(); 212 252 for (res.rows) |row| { 213 - const p = PubSearch.fromRow(Pub, row); 253 + const p = Pub.fromRow(row); 214 254 try jw.beginObject(); 215 255 try jw.objectField("type"); 216 256 try jw.write("publication"); ··· 241 281 var output: std.Io.Writer.Allocating = .init(alloc); 242 282 errdefer output.deinit(); 243 283 244 - var res = c.query( 245 - \\SELECT tag, COUNT(*) as count 246 - \\FROM document_tags 247 - \\GROUP BY tag 248 - \\ORDER BY count DESC 249 - \\LIMIT 100 250 - , &.{}) catch { 284 + var res = c.query(TagsQuery.positional, &.{}) catch { 251 285 try output.writer.writeAll("[]"); 252 286 return try output.toOwnedSlice(); 253 287 }; ··· 257 291 try jw.beginArray(); 258 292 259 293 for (res.rows) |row| { 294 + const tag = TagCount.fromRow(row); 260 295 try jw.beginObject(); 261 296 try jw.objectField("tag"); 262 - try jw.write(row.text(0)); 297 + try jw.write(tag.tag); 263 298 try jw.objectField("count"); 264 - try jw.write(row.int(1)); 299 + try jw.write(tag.count); 265 300 try jw.endObject(); 266 301 } 267 302 ··· 272 307 pub fn getStats() struct { documents: i64, publications: i64 } { 273 308 var c = &(client orelse return .{ .documents = 0, .publications = 0 }); 274 309 275 - const docs = blk: { 276 - var res = c.query("SELECT COUNT(*) FROM documents", &.{}) catch break :blk 0; 277 - defer res.deinit(); 278 - const row = res.first() orelse break :blk 0; 279 - break :blk row.int(0); 280 - }; 310 + var res = c.query( 311 + \\SELECT 312 + \\ (SELECT COUNT(*) FROM documents) as docs, 313 + \\ (SELECT COUNT(*) FROM publications) as pubs 314 + , &.{}) catch return .{ .documents = 0, .publications = 0 }; 315 + defer res.deinit(); 281 316 282 - const pubs = blk: { 283 - var res = c.query("SELECT COUNT(*) FROM publications", &.{}) catch break :blk 0; 284 - defer res.deinit(); 285 - const row = res.first() orelse break :blk 0; 286 - break :blk row.int(0); 287 - }; 288 - 289 - return .{ .documents = docs, .publications = pubs }; 317 + const row = res.first() orelse return .{ .documents = 0, .publications = 0 }; 318 + return .{ .documents = row.int(0), .publications = row.int(1) }; 290 319 } 291 320 292 321 /// Build FTS5 query with prefix matching: "cat dog" -> "cat* dog*"
+7 -4
backend/src/db/turso.zig
··· 43 43 url: []const u8, 44 44 token: []const u8, 45 45 mutex: std.Thread.Mutex = .{}, 46 + http_client: http.Client, 46 47 47 48 pub fn init(allocator: Allocator) !Client { 48 49 const url = std.posix.getenv("TURSO_URL") orelse { ··· 67 68 .allocator = allocator, 68 69 .url = host, 69 70 .token = token, 71 + .http_client = .{ .allocator = allocator }, 70 72 }; 73 + } 74 + 75 + pub fn deinit(self: *Client) void { 76 + self.http_client.deinit(); 71 77 } 72 78 73 79 /// Execute a query and return parsed results. ··· 153 159 const auth = std.fmt.bufPrint(&auth_buf, "Bearer {s}", .{self.token}) catch 154 160 return error.AuthTooLong; 155 161 156 - var client: http.Client = .{ .allocator = self.allocator }; 157 - defer client.deinit(); 158 - 159 162 var response_body: std.Io.Writer.Allocating = .init(self.allocator); 160 163 errdefer response_body.deinit(); 161 164 162 - const res = client.fetch(.{ 165 + const res = self.http_client.fetch(.{ 163 166 .location = .{ .url = url }, 164 167 .method = .POST, 165 168 .headers = .{