╭──────────╮ │← previous│ ╰──────────╯ ╭ changed ────╮ │src/event.zig│ ╰─────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │354 }, │ │408 - .bool => @field(value, field.name) = try readBool(DB, hash_kind, map, field.name),│ │355 + .bool => @field(event, field.name) = try readBool(DB, hash_kind, map, field.name),│ │356 else => @compileError("unsupported read field type: " ++ @typeName(field.type)), │ │357 } │ │358 } │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────╮ │360 else => @compileError("read expects a struct"),│ │361 } │ │362 │ │416 - return value; │ │363 + return event; │ │364 } │ │365 │ │366 fn readBytes( │ ╰────────────────────────────────────────────────────────────╯ ╭───────────────────────────────────────────╮ │394 // writing to xitdb │ │395 // │ │396 │ │450 -fn upsert( │ │397 +pub fn upsert( │ │398 comptime T: type, │ │399 comptime DB: type, │ │400 comptime hash_kind: hash.HashKind,│ ╰───────────────────────────────────────────╯ ╭──────────────────────────────────────────╮ │401 map: DB.HashMap(.read_write), │ │455 - data: T, │ │402 + event: T, │ │403 ) !void { │ │404 switch (@typeInfo(T)) { │ │405 .@"struct" => |struct_info| {│ ╰──────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │406 inline for (struct_info.fields) |field| { │ │460 - try upsertField(DB, hash_kind, map, field.name, field.type, @field(data, field.name)); │ │407 + try upsertField(DB, hash_kind, map, field.name, field.type, @field(event, field.name));│ │408 } │ │409 }, │ │410 else => @compileError("upsert expects a struct"), │ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ──────────╮ │src/event/Issue.zig│ ╰───────────────────╯ ╭───────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ 7 description: []const u8, │ │ 8 tags: []const u8, │ │ 9 │ │10 +pub fn consume( │ │11 + comptime DB: type, │ │12 + comptime hash_kind: hash.HashKind, │ │13 + haxy_moment: DB.HashMap(.read_write), │ │14 + event_id: *const [evt.event_id_size]u8, │ │15 + event_maybe: ?@This(), │ │16 +) !void { │ │17 + const issue_key = hash.hashInt(hash_kind, event_id); │ │18 + │ │19 + const event_id_to_issue_cursor = try haxy_moment.putCursor(hash.hashInt(hash_kind, "event-id->issue"));│ │20 + const event_id_to_issue = try DB.HashMap(.read_write).init(event_id_to_issue_cursor); │ │21 + │ │22 + if (event_maybe) |event| { │ │23 + const issue_cursor = try event_id_to_issue.putCursor(issue_key); │ │24 + const issue = try DB.HashMap(.read_write).init(issue_cursor); │ │25 + try evt.upsert(@This(), DB, hash_kind, issue, event); │ │26 + } else { │ │27 + if (!try event_id_to_issue.remove(issue_key)) return error.EventNotFound; │ │28 + } │ │29 +} │ │30 + │ ╰───────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ─────────╮ │src/event/Repo.zig│ ╰──────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ 7 name: []const u8, │ │ 8 enable_issue: bool, │ │ 9 │ │10 +pub fn consume( │ │11 + comptime DB: type, │ │12 + comptime hash_kind: hash.HashKind, │ │13 + haxy_moment: DB.HashMap(.read_write), │ │14 + event_id: *const [evt.event_id_size]u8, │ │15 + event_maybe: ?@This(), │ │16 + arena: *std.heap.ArenaAllocator, │ │17 +) !void { │ │18 + const repo_key = hash.hashInt(hash_kind, event_id); │ │19 + │ │20 + const event_id_to_repo_cursor = try haxy_moment.putCursor(hash.hashInt(hash_kind, "event-id->repo")); │ │21 + const event_id_to_repo = try DB.HashMap(.read_write).init(event_id_to_repo_cursor); │ │22 + │ │23 + if (event_maybe) |event| { │ │24 + const repo_cursor = try event_id_to_repo.putCursor(repo_key); │ │25 + const repo = try DB.HashMap(.read_write).init(repo_cursor); │ │26 + try evt.upsert(@This(), DB, hash_kind, repo, event); │ │27 + │ │28 + const user_id_to_repos_cursor = try haxy_moment.putCursor(hash.hashInt(hash_kind, "user-id->repos")); │ │29 + const user_id_to_repos = try DB.HashMap(.read_write).init(user_id_to_repos_cursor); │ │30 + │ │31 + const user_repos_cursor = try user_id_to_repos.putCursor(hash.hashInt(hash_kind, event.user_id)); │ │32 + const user_repos = try DB.CountedHashSet(.read_write).init(user_repos_cursor); │ │33 + try user_repos.put(repo_key, .{ .bytes = event_id }); │ │34 + } else { │ │35 + if (try event_id_to_repo.getCursor(repo_key)) |existing_repo_cursor| { │ │36 + const existing_repo = try DB.HashMap(.read_only).init(existing_repo_cursor); │ │37 + const existing_repo_event = try evt.read(@This(), DB, hash_kind, arena, existing_repo); │ │38 + │ │39 + const user_id_to_repos_cursor = try haxy_moment.putCursor(hash.hashInt(hash_kind, "user-id->repos"));│ │40 + const user_id_to_repos = try DB.HashMap(.read_write).init(user_id_to_repos_cursor); │ │41 + │ │42 + const user_key = hash.hashInt(hash_kind, existing_repo_event.user_id); │ │43 + const user_repos_cursor = try user_id_to_repos.putCursor(user_key); │ │44 + const user_repos = try DB.CountedHashSet(.read_write).init(user_repos_cursor); │ │45 + _ = try user_repos.remove(repo_key); │ │46 + } │ │47 + if (!try event_id_to_repo.remove(repo_key)) return error.EventNotFound; │ │48 + } │ │49 +} │ │50 + │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ─────────╮ │src/event/User.zig│ ╰──────────────────╯ ╭─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │ 8 email: []const u8, │ │ 9 password_hash: []const u8, │ │10 │ │11 +pub fn consume( │ │12 + comptime DB: type, │ │13 + comptime hash_kind: hash.HashKind, │ │14 + haxy_moment: DB.HashMap(.read_write), │ │15 + event_id: *const [evt.event_id_size]u8, │ │16 + event_maybe: ?@This(), │ │17 +) !void { │ │18 + const user_key = hash.hashInt(hash_kind, event_id); │ │19 + │ │20 + const event_id_to_user_cursor = try haxy_moment.putCursor(hash.hashInt(hash_kind, "event-id->user")); │ │21 + const event_id_to_user = try DB.HashMap(.read_write).init(event_id_to_user_cursor); │ │22 + │ │23 + if (event_maybe) |event| { │ │24 + const user_cursor = try event_id_to_user.putCursor(user_key); │ │25 + const user = try DB.HashMap(.read_write).init(user_cursor); │ │26 + try evt.upsert(@This(), DB, hash_kind, user, event); │ │27 + } else { │ │28 + if (!try event_id_to_user.remove(user_key)) return error.EventNotFound; │ │29 + │ │30 + const user_id_to_repos_cursor = try haxy_moment.putCursor(hash.hashInt(hash_kind, "user-id->repos"));│ │31 + const user_id_to_repos = try DB.HashMap(.read_write).init(user_id_to_repos_cursor); │ │32 + _ = try user_id_to_repos.remove(user_key); │ │33 + } │ │34 +} │ │35 + │ │36 pub const password_hash_max_len = bcrypt.hash_length * 2; │ │37 │ │38 pub fn hashPassword( │ ╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭ changed ─────────╮ │src/test/event.zig│ ╰──────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │41 │ │42 var prng = std.Random.DefaultPrng.init(std.testing.random_seed); │ │43 │ │44 - const first_event_id = evt.Event.randomId(prng.random()); │ │44 + const first_event_id = evt.EventWithId.randomId(prng.random()); │ │45 │ │46 - const events_to_consume = [_]evt.Event{ │ │46 + const events_to_consume = [_]evt.EventWithId{ │ │47 .{ │ │48 .id = std.fmt.bytesToHex(first_event_id, .lower), │ │49 - .data = .{ │ │49 + .event = .{ │ │50 .issue = .{ │ │51 .title = "Login form clears password on validation error", │ │52 .description = "Submitting an invalid email address resets the password field. Preserve the field value and show an inline validation message.",│ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮ │57 // this event edits the previous one because it has the same id │ │58 .{ │ │59 .id = std.fmt.bytesToHex(first_event_id, .lower), │ │60 - .data = .{ │ │60 + .event = .{ │ │61 .issue = .{ │ │62 .title = "Login form clears password on validation error", │ │63 .description = "Submitting an invalid email address resets the password field and removes typed input. Preserve the field value and show an inline validation message.",│ ╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭──────╮ │next →│ ╰──────╯