Skip to content

Caller-supplied Message-ID, direction, and message key on writes; one-transaction batch write - #15

Merged
TheGreatAxios merged 5 commits into
mainfrom
cl-7452-caller-message-id
Sep 5, 2026
Merged

Caller-supplied Message-ID, direction, and message key on writes; one-transaction batch write#15
TheGreatAxios merged 5 commits into
mainfrom
cl-7452-caller-message-id

Conversation

@TheGreatAxios

Copy link
Copy Markdown
Contributor

Summary

  • WriteMailboxMessageArgs gains messageId?: string (validated with assertMsgId) — when supplied it becomes the built frame's Message-ID: header and the cached principal_mail.message_id; omitted, one is still minted as before.
  • WriteMailboxMessageArgs gains direction?: "inbound" | "outbound" (default "inbound"). Omitting messageKey now defaults to mailboxKey.transport(messageId, principalId) (same shape as persist.ts's transport dual-write key) instead of leaving the row unkeyed; a caller-supplied messageKey still overrides it.
  • New 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-row onConflictDoNothing dedupe on the existing messageKey partial 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.
  • deliverInboxItems is unchanged — it remains the notify-item path; writeMailboxMessages is the new conversation path. Documented in README and ARCHITECTURE.md.
  • CHANGELOG entry.

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 fail
  • bun run build
  • reference-host acceptance suite (bun test --cwd examples/reference-host) — 20 pass, 0 fail
  • CI green

…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
TheGreatAxios force-pushed the cl-7452-caller-message-id branch from 7742177 to d75049b Compare September 5, 2026 01:29
…(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.
@TheGreatAxios
TheGreatAxios merged commit 625e487 into main Sep 5, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant