Caller-supplied Message-ID, direction, and message key on writes; one-transaction batch write - #15
Merged
Merged
Conversation
…ch write Covers WriteMailboxMessageArgs.messageId/direction, the messageKey default (and override), and the new one-transaction writeMailboxMessages batch: atomic commit, mid-batch rollback, dedupe-on-retry, and post-commit bus events.
…write
WriteMailboxMessageArgs gains messageId (validated as a bracketed msg-id,
becomes the frame's Message-ID and the cached column) and direction
(default inbound). Omitting messageKey no longer leaves a row unkeyed: it
defaults to mailboxKey.transport(messageId, principalId), the same shape
persist.ts's transport dual-write already uses, so a retry with the same
caller-supplied messageId dedupes without a caller minting its own key.
New writeMailboxMessages(db, items, opts?) writes an arbitrary batch of
{ scope, args } pairs in one transaction, with per-row onConflictDoNothing
dedupe and bus events published only after commit for rows actually
inserted. deliverInboxItems is unchanged.
ARCHITECTURE.md documents writeMailboxMessages as the conversation path alongside deliverInboxItems's notify-item path, and the messageId/ direction/messageKey defaulting behavior. README points to both from a new Write paths section. CHANGELOG records the addition.
TheGreatAxios
force-pushed
the
cl-7452-caller-message-id
branch
from
September 5, 2026 01:29
7742177 to
d75049b
Compare
…(CL-7452)
Fold critique findings into the caller-message-id write path:
- export writeMailboxMessage(s) and their arg/opts types from the package
entry point, so a consumer never has to reach into src/write.js directly
- an outbound row is now created already-read (mailbox.read_at pinned to
its own created_at), so the unread count and unread view exclude a
sender's own copy without a direction predicate; listUserMailbox and
getMailboxMessage gain an optional direction filter (default "inbound")
for a caller that does need a principal's sent copies
- the default messageKey now folds direction into
mailboxKey.transport(messageId, principalId, direction): the inbound
default is unchanged (byte-for-byte what persist.ts's transport
dual-write already writes), while outbound gets a distinct :outbound
suffix so the same caller-supplied Message-ID in both directions no
longer collapses onto one row
- writeMailboxMessages now returns Array<{ messageKey, id }> in item
order, matching deliverInboxItems's DeliveredInboxItem shape, instead
of a filtered id list; WriteMailboxMessagesItem's args drops the
duplicated tenantId/principalId (Omit<..., "tenantId" | "principalId">)
so scope is the only source of either
…-7452) Move the write-path default-key behavior under Changed (it changed shape mid-flight, not merely landed), and document the outbound pre-read ruling, the listUserMailbox/getMailboxMessage direction filter, the writeMailboxMessages return shape, and the Omit-based WriteMailboxMessagesItem in both CHANGELOG.md and ARCHITECTURE.md.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
WriteMailboxMessageArgsgainsmessageId?: string(validated withassertMsgId) — when supplied it becomes the built frame'sMessage-ID:header and the cachedprincipal_mail.message_id; omitted, one is still minted as before.WriteMailboxMessageArgsgainsdirection?: "inbound" | "outbound"(default"inbound"). OmittingmessageKeynow defaults tomailboxKey.transport(messageId, principalId)(same shape aspersist.ts's transport dual-write key) instead of leaving the row unkeyed; a caller-suppliedmessageKeystill overrides it.writeMailboxMessages(db, items: Array<{ scope, args }>, opts?): an arbitrary batch of writes (e.g. a sender's outbound copy alongside every recipient's inbound copy of one conversation turn) committed in a single transaction, with per-rowonConflictDoNothingdedupe on the existingmessageKeypartial unique index. Returns the ids of rows actually inserted; bus events publish only after commit, one per written row. A throw from any single item rolls back the whole batch.deliverInboxItemsis unchanged — it remains the notify-item path;writeMailboxMessagesis the new conversation path. Documented in README and ARCHITECTURE.md.Fixes CL-7452
Part of CL-7445
Test plan
bun run typecheck(only pre-existing reference-host dist errors remain, unrelated)bun test src— 369 pass, 0 failbun run buildbun test --cwd examples/reference-host) — 20 pass, 0 fail