╭ changed ────╮ │src/event.zig│ ╰─────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │371 try consume(repo_opts, io, allocator, repo, ref); │ │372 } │ │373 │ │374 -// build the key for SortedMaps sorted by timestamp. the big-endian timestamp │ │374 +// build the key for SortedSets sorted by timestamp. the big-endian timestamp │ │375 // makes byte order match creation order; the event id breaks ties and keeps │ │376 // keys unique within the same timestamp. │ │377 pub fn orderKey(timestamp: u64, event_id: *const [event_id_size]u8) [@sizeOf(u64) + event_id_size]u8 {│ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ─────────╮ │src/event/Repo.zig│ ╰──────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │74 │ │75 try name_to_repo_id.put(hash.hashInt(hash_kind, repo_path), .{ .bytes = event_id }); │ │76 │ │77 - // first time we've seen this repo: add it to the ordered map the repos │ │78 - // view paginates through │ │77 + // first time we've seen this repo: add it to the ordered set the repos │ │78 + // view paginates through. the key (orderKey) embeds the event id, so the │ │79 + // set needs no value. │ │80 if (existing_cursor_maybe == null) { │ │80 - const created_ts_to_repo_id_cursor = try haxy_moment.putCursor(hash.hashInt(hash_kind, "created-ts->repo-id"));│ │81 - const created_ts_to_repo_id = try DB.SortedMap(.read_write).init(created_ts_to_repo_id_cursor); │ │81 + const repo_id_set_cursor = try haxy_moment.putCursor(hash.hashInt(hash_kind, "repo-id-set")); │ │82 + const repo_id_set = try DB.SortedSet(.read_write).init(repo_id_set_cursor); │ │83 const order_key = evt.orderKey(event_to_write.created_ts, event_id); │ │83 - try created_ts_to_repo_id.put(&order_key, .{ .bytes = event_id }); │ │84 + try repo_id_set.put(&order_key); │ │85 } │ │86 │ │86 - const user_id_to_repos_cursor = try haxy_moment.putCursor(hash.hashInt(hash_kind, "user-id->repos")); │ │87 - const user_id_to_repos = try DB.HashMap(.read_write).init(user_id_to_repos_cursor); │ │87 + const user_id_to_repo_id_set_cursor = try haxy_moment.putCursor(hash.hashInt(hash_kind, "user-id->repo-id-set")); │ │88 + const user_id_to_repo_id_set = try DB.HashMap(.read_write).init(user_id_to_repo_id_set_cursor); │ │89 │ │89 - const user_repos_cursor = try user_id_to_repos.putCursor(hash.hashInt(hash_kind, event.user_id)); │ │90 - const user_repos = try DB.CountedHashSet(.read_write).init(user_repos_cursor); │ │91 - try user_repos.put(repo_key, .{ .bytes = event_id }); │ │90 + // each user's repos are a set ordered by creation time (oldest first), so │ │91 + // the user page paginates them the same way the home repos list does. │ │92 + const user_repos_cursor = try user_id_to_repo_id_set.putCursor(hash.hashInt(hash_kind, event.user_id)); │ │93 + const user_repos = try DB.SortedSet(.read_write).init(user_repos_cursor); │ │94 + const user_repo_order_key = evt.orderKey(event_to_write.created_ts, event_id); │ │95 + try user_repos.put(&user_repo_order_key); │ │96 } else { │ │97 if (try event_id_to_repo.getCursor(repo_key)) |existing_repo_cursor| { │ │98 const existing_repo = try DB.HashMap(.read_only).init(existing_repo_cursor); │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │101 const existing_path = try std.fmt.allocPrint(arena.allocator(), "{s}/{s}", .{ existing_repo_event.user_id, existing_repo_event.name });│ │102 _ = try name_to_repo_id.remove(hash.hashInt(hash_kind, existing_path)); │ │103 │ │100 - // drop it from the ordered map using its recorded creation timestamp │ │101 - const created_ts_to_repo_id_cursor = try haxy_moment.putCursor(hash.hashInt(hash_kind, "created-ts->repo-id")); │ │102 - const created_ts_to_repo_id = try DB.SortedMap(.read_write).init(created_ts_to_repo_id_cursor); │ │104 + // drop it from the ordered set using its recorded creation timestamp │ │105 + const repo_id_set_cursor = try haxy_moment.putCursor(hash.hashInt(hash_kind, "repo-id-set")); │ │106 + const repo_id_set = try DB.SortedSet(.read_write).init(repo_id_set_cursor); │ │107 const order_key = evt.orderKey(existing_repo_event.created_ts, event_id); │ │104 - _ = try created_ts_to_repo_id.remove(&order_key); │ │108 + _ = try repo_id_set.remove(&order_key); │ │109 │ │106 - const user_id_to_repos_cursor = try haxy_moment.putCursor(hash.hashInt(hash_kind, "user-id->repos")); │ │107 - const user_id_to_repos = try DB.HashMap(.read_write).init(user_id_to_repos_cursor); │ │110 + const user_id_to_repo_id_set_cursor = try haxy_moment.putCursor(hash.hashInt(hash_kind, "user-id->repo-id-set")); │ │111 + const user_id_to_repo_id_set = try DB.HashMap(.read_write).init(user_id_to_repo_id_set_cursor); │ │112 │ │113 const user_key = hash.hashInt(hash_kind, existing_repo_event.user_id); │ │110 - const user_repos_cursor = try user_id_to_repos.putCursor(user_key); │ │111 - const user_repos = try DB.CountedHashSet(.read_write).init(user_repos_cursor); │ │112 - _ = try user_repos.remove(repo_key); │ │114 + const user_repos_cursor = try user_id_to_repo_id_set.putCursor(user_key); │ │115 + const user_repos = try DB.SortedSet(.read_write).init(user_repos_cursor); │ │116 + _ = try user_repos.remove(&order_key); │ │117 } │ │118 if (!try event_id_to_repo.remove(repo_key)) return error.EventNotFound; │ │119 } │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ─────────╮ │src/event/User.zig│ ╰──────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │83 │ │84 try name_to_user_id.put(hash.hashInt(hash_kind, event.name), .{ .bytes = event_id }); │ │85 │ │86 - // first time we've seen this user: add it to the ordered map the users │ │87 - // view paginates through │ │86 + // first time we've seen this user: add it to the ordered set the users │ │87 + // view paginates through. the key (orderKey) embeds the event id, so the │ │88 + // set needs no value. │ │89 if (existing_cursor_maybe == null) { │ │89 - const created_ts_to_user_id_cursor = try haxy_moment.putCursor(hash.hashInt(hash_kind, "created-ts->user-id"));│ │90 - const created_ts_to_user_id = try DB.SortedMap(.read_write).init(created_ts_to_user_id_cursor); │ │90 + const user_id_set_cursor = try haxy_moment.putCursor(hash.hashInt(hash_kind, "user-id-set")); │ │91 + const user_id_set = try DB.SortedSet(.read_write).init(user_id_set_cursor); │ │92 const order_key = evt.orderKey(event_to_write.created_ts, event_id); │ │92 - try created_ts_to_user_id.put(&order_key, .{ .bytes = event_id }); │ │93 + try user_id_set.put(&order_key); │ │94 } │ │95 } else { │ │96 // read the user's name so we can drop its name->id index entry │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ 99 const existing_event = try evt.read(@This(), DB, hash_kind, arena, existing_user); │ │100 _ = try name_to_user_id.remove(hash.hashInt(hash_kind, existing_event.name)); │ │101 │ │101 - // drop it from the ordered map using its recorded creation timestamp │ │102 - const created_ts_to_user_id_cursor = try haxy_moment.putCursor(hash.hashInt(hash_kind, "created-ts->user-id"));│ │103 - const created_ts_to_user_id = try DB.SortedMap(.read_write).init(created_ts_to_user_id_cursor); │ │102 + // drop it from the ordered set using its recorded creation timestamp │ │103 + const user_id_set_cursor = try haxy_moment.putCursor(hash.hashInt(hash_kind, "user-id-set")); │ │104 + const user_id_set = try DB.SortedSet(.read_write).init(user_id_set_cursor); │ │105 const order_key = evt.orderKey(existing_event.created_ts, event_id); │ │105 - _ = try created_ts_to_user_id.remove(&order_key); │ │106 + _ = try user_id_set.remove(&order_key); │ │107 } │ │108 │ │109 if (!try event_id_to_user.remove(user_key)) return error.EventNotFound; │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │110 │ │110 - const user_id_to_repos_cursor = try haxy_moment.putCursor(hash.hashInt(hash_kind, "user-id->repos")); │ │111 - const user_id_to_repos = try DB.HashMap(.read_write).init(user_id_to_repos_cursor); │ │112 - _ = try user_id_to_repos.remove(user_key); │ │111 + const user_id_to_repo_id_set_cursor = try haxy_moment.putCursor(hash.hashInt(hash_kind, "user-id->repo-id-set"));│ │112 + const user_id_to_repo_id_set = try DB.HashMap(.read_write).init(user_id_to_repo_id_set_cursor); │ │113 + _ = try user_id_to_repo_id_set.remove(user_key); │ │114 } │ │115 } │ │116 │ ╰──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ─────────╮ │src/test/event.zig│ ╰──────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │738 try std.testing.expectEqualStrings("ziglings", repo_event.name); │ │739 │ │740 // get the repos created by the user │ │741 - const user_id_to_repos_cursor = try haxy_moment.getCursor(hash.hashInt(repo_opts.hash, "user-id->repos")) orelse return error.NotFound; │ │742 - const user_id_to_repos = try Repo.DB.HashMap(.read_only).init(user_id_to_repos_cursor); │ │741 + const user_id_to_repo_id_set_cursor = try haxy_moment.getCursor(hash.hashInt(repo_opts.hash, "user-id->repo-id-set")) orelse return error.NotFound;│ │742 + const user_id_to_repo_id_set = try Repo.DB.HashMap(.read_only).init(user_id_to_repo_id_set_cursor); │ │743 │ │744 - const user_repos_cursor = try user_id_to_repos.getCursor(hash.hashInt(repo_opts.hash, &user_event_id)) orelse return error.NotFound; │ │745 - const user_repos = try Repo.DB.CountedHashSet(.read_only).init(user_repos_cursor); │ │744 + const user_repos_cursor = try user_id_to_repo_id_set.getCursor(hash.hashInt(repo_opts.hash, &user_event_id)) orelse return error.NotFound; │ │745 + const user_repos = try Repo.DB.SortedSet(.read_only).init(user_repos_cursor); │ │746 │ │747 try std.testing.expectEqual(1, try user_repos.count()); │ │748 │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │749 - try std.testing.expect(null != try user_repos.getCursor(hash.hashInt(repo_opts.hash, &repo_event_id)));│ │749 + // the set is keyed by orderKey([created-ts][event-id]) │ │750 + const order_key = evt.orderKey(repo_event.created_ts, &repo_event_id); │ │751 + try std.testing.expect(try user_repos.contains(&order_key)); │ │752 } │ │753 │ │754 // │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │775 try std.testing.expect(null == try event_id_to_repo.getCursor(hash.hashInt(repo_opts.hash, &repo_event_id))); │ │776 │ │777 // get the repos created by the user │ │776 - const user_id_to_repos_cursor = try haxy_moment.getCursor(hash.hashInt(repo_opts.hash, "user-id->repos")) orelse return error.NotFound; │ │777 - const user_id_to_repos = try Repo.DB.HashMap(.read_only).init(user_id_to_repos_cursor); │ │778 + const user_id_to_repo_id_set_cursor = try haxy_moment.getCursor(hash.hashInt(repo_opts.hash, "user-id->repo-id-set")) orelse return error.NotFound;│ │779 + const user_id_to_repo_id_set = try Repo.DB.HashMap(.read_only).init(user_id_to_repo_id_set_cursor); │ │780 │ │779 - const user_repos_cursor = try user_id_to_repos.getCursor(hash.hashInt(repo_opts.hash, &user_event_id)) orelse return error.NotFound; │ │780 - const user_repos = try Repo.DB.CountedHashSet(.read_only).init(user_repos_cursor); │ │781 + const user_repos_cursor = try user_id_to_repo_id_set.getCursor(hash.hashInt(repo_opts.hash, &user_event_id)) orelse return error.NotFound; │ │782 + const user_repos = try Repo.DB.SortedSet(.read_only).init(user_repos_cursor); │ │783 │ │784 + // removing the repo emptied the user's set │ │785 try std.testing.expectEqual(0, try user_repos.count()); │ │783 - try std.testing.expect(null == try user_repos.getCursor(hash.hashInt(repo_opts.hash, &repo_event_id))); │ │786 } │ │787 │ │788 // │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────╮ │846 var pw_buf: [evt.User.password_hash_max_len]u8 = undefined; │ │847 const pw = try evt.User.hashPassword("pw", &pw_buf, io); │ │848 │ │847 - // asserts the timestamp->repo-id map holds exactly `expected` ids in that order│ │849 + // asserts the repo-id-set holds exactly `expected` ids in that order │ │850 const Check = struct { │ │851 fn order( │ │852 comptime DB: type, │ ╰─────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯