Skip to content

docs(packages): fix interface-shape drift in 5 adapter docs#80

Merged
ssilvius merged 2 commits intomainfrom
fix/doc-accuracy-interface-drift
Apr 11, 2026
Merged

docs(packages): fix interface-shape drift in 5 adapter docs#80
ssilvius merged 2 commits intomainfrom
fix/doc-accuracy-interface-drift

Conversation

@ssilvius
Copy link
Copy Markdown
Contributor

Summary

After shipping the docs in PR #76 I spot-read them and found real interface drift -- type names, method signatures, field tables, and import paths that did not match the current source. These would have broken every consumer that copy-pasted the examples on npm. The prior @vault-2026 technical accuracy review covered the schema sections of `core-reference.md` but did not cover the adapter package docs, which is where these bugs sit.

What was wrong

`packages/resend/docs/outbound.md`

  • `EmailProvider.sendEmail`: type names wrong. Doc said `sendEmail(params: SendEmailParams): Promise`. Actual is `sendEmail(params: EmailParams): Promise<{ id: string }>`.
  • `EmailParams` field table wrong: `from` is optional, `to` is a single string (not array), `cc` / `bcc` / `headers` do not exist in the type.
  • Missing: full `EmailProvider` surface (mailing lists, subscribers, campaigns, audiences) was omitted entirely.
  • Method calls wrong: doc called `mailService.compose(...)` and `mailService.replyToThread(...)`. Actual methods are `InboxEmailService.composeEmail` and `replyToThread`.
  • Thread metadata update: doc said `messageCount`, schema has `unreadCount`.

`packages/cloudflare/docs/blob-storage.md`

  • `BlobStorage` interface signatures wrong: missing `options?` parameter on `put` and `get`; `get` return type was `string | undefined` (actual is `BlobObject | null`); and `generateKey(contentHash, extension)` was missing entirely.
  • Added `BlobObject`, `BlobPutOptions`, and `BlobGetOptions` alongside so consumers see the full shape.

`packages/cloudflare/docs/inbound.md`

  • `InboundAdapter.handleIncoming` return type wrong: said `Promise`, actual is `Promise<{ messageId, threadId }>`.
  • "Inbound email structure" section described a 14-field parsed form as if it were the input type. The actual `InboundEmail` is 4 fields: `raw: ArrayBuffer`, `from`, `to`, `headers`. The parsed fields land on the message row AFTER the adapter parses the raw bytes. Rewrote with a clear split between input shape and parsed-form schema fields.
  • Classification section: `ClassificationAdapter` should be `EmailClassifier`; `summary` field does not exist on `EmailClassification` (actual fields are `tags`, `priority`).

`packages/workers-ai/docs/classification.md`

  • Interface name wrong: `ClassificationAdapter` should be `EmailClassifier`.
  • Method signature wrong: `classify(message: ClassificationInput)` should be `classify(from: string, subject: string, body: string)`.
  • Result type wrong: `ClassificationResult` with `summary` field should be `EmailClassification` with `tags` and `priority` fields.
  • Example implementation updated to the correct shape.

`packages/cloudflare/docs/quickstart.md`

  • Worker example imported `createR2BlobStorage` and `parseInboundEmail` from the package root. Actual exports are `createR2Storage` (from `/storage` subpath) and `parseEmailHeaders` / `hashContent` (from `/parsing` subpath). Also `createR2Storage` takes `{ bucket }` config, not a raw `R2Bucket`.
  • `blobStorage.put(parsed.rawEmail)` was missing the required `key` parameter. Rewrote to use `storage.generateKey` + `storage.put(key, raw)`.
  • `sendEmail` example showed `to: [array]`; actual is a single string.
  • "Next steps" link list pointed at 5 sibling docs that only partially exist in this package. Replaced with per-package npm links.

Verification

  • `pnpm test`: 609 passing across 37 files (no code change, same count as before)
  • `pnpm typecheck`: clean
  • `pnpm lint`: 0 warnings, 0 errors
  • `oxfmt` normalized the 5 modified docs
  • `legion-simplify` gate: clean

Stack

Chains off `chore/package-readmes` (#76). When #76 merges this branch rebases cleanly onto main.

@ssilvius ssilvius force-pushed the fix/doc-accuracy-interface-drift branch from 4aa8cd7 to 6eb28d7 Compare April 11, 2026 19:02
ssilvius and others added 2 commits April 11, 2026 12:29
After shipping the docs in PR #76 I spot-read them and found real
interface drift -- types, method signatures, and import paths that
did not match the current source. These would have broken every
consumer that copy-pasted the examples. The @vault-2026 technical
accuracy review covered the schema sections of core-reference.md
but did not cover the adapter package docs, which is where these
errors sit.

- EmailProvider.sendEmail: type names wrong. Doc said
  `sendEmail(params: SendEmailParams): Promise<SendEmailResult>`.
  Actual is `sendEmail(params: EmailParams): Promise<{ id: string }>`.
- EmailParams field table wrong: `from` is optional not required,
  `to` is a single string not array, `cc`/`bcc`/`headers` do not
  exist in EmailParams.
- Documented the full EmailProvider surface (mailing lists,
  subscribers, campaigns, audiences), which was omitted.
- Method calls wrong: doc called `mailService.compose(...)` and
  `mailService.replyToThread(...)`. Actual methods are
  `InboxEmailService.composeEmail` and `replyToThread`.
- Thread metadata update: doc said `messageCount`, schema has
  `unreadCount`.

- BlobStorage interface signatures wrong: missing `options?` on
  `put` and `get`, `get` return type was `string | undefined`
  (actual is `BlobObject | null`), and `generateKey` was missing
  entirely.
- Added BlobObject, BlobPutOptions, and BlobGetOptions types
  alongside so consumers see the full shape.

- InboundAdapter.handleIncoming return type wrong: said
  `Promise<void>`, actual is `Promise<{ messageId, threadId }>`.
- "Inbound email structure" section described a 14-field parsed
  form as if it were the input type. The actual InboundEmail is
  4 fields: `raw: ArrayBuffer`, `from`, `to`, `headers`. The
  parsed fields land on the message row AFTER the adapter does
  parsing work. Rewrote with a clear split between input shape
  and parsed-form schema fields.
- Classification section: `ClassificationAdapter` should be
  `EmailClassifier`, `summary` field does not exist on
  `EmailClassification` (actual fields are `tags`, `priority`).

- Interface name wrong: `ClassificationAdapter` should be
  `EmailClassifier`.
- Method signature wrong: `classify(message: ClassificationInput)`
  should be `classify(from: string, subject: string, body: string)`.
- Result type wrong: `ClassificationResult` with `summary` field
  should be `EmailClassification` with `tags` and `priority` fields.
- Example classifier implementation updated to the correct shape.

- Worker example imported `createR2BlobStorage` and
  `parseInboundEmail` from the package root. Actual exports are
  `createR2Storage` (from `/storage` subpath) and
  `parseEmailHeaders` / `hashContent` (from `/parsing` subpath).
  Also `createR2Storage` takes `{ bucket }` config, not a raw
  R2Bucket.
- `blobStorage.put(parsed.rawEmail)` was missing the required
  `key` parameter. Rewrote to use `storage.generateKey` and
  `storage.put(key, raw)`.
- `sendEmail` example showed `to: [array]`, actual is a single
  string.
- "Next steps" link list pointed at 5 docs at
  `./classification.md`, `./imap-quickstart.md`, etc. Only
  `./classification.md` does not exist in this package -- the
  rest live in other packages. Replaced with per-package npm
  links.

- pnpm test: 609 passing across 37 files (no code change, same
  count as before)
- pnpm typecheck: clean
- pnpm lint: 0 warnings, 0 errors
- oxfmt normalized the 5 modified docs

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Three more core docs had significant accuracy drift that would have
misled consumers reading them on npm. These were NOT covered by the
prior @vault-2026 schema review (which focused on core-reference.md).

## packages/core/docs/threading.md

- The doc described a threading engine that matches messages to
  threads using In-Reply-To / References / subject fallback. That
  engine does not exist in the core threading module -- threading.ts
  only exports generateMessageId, buildReferences, and generateSnippet
  as header-building blocks. Thread matching is the inbound adapter's
  responsibility and has a TODO in threading.ts pointing to #13.
- The doc claimed "subject matching within the same mailbox
  (configurable)". Grep for it -- no such feature exists. Subject
  matching is intentionally NOT part of the design because subject
  lines collide across unrelated conversations.
- The doc claimed "Deleting a thread soft-deletes all its messages".
  Checked the schema: inbox_message.threadId has ON DELETE CASCADE,
  which is a hard-delete cascade, not soft-delete. Soft-deleting a
  thread (setting deletedAt) does NOT cascade to messages. Clarified.
- The doc's thread model table was missing columns that exist in the
  schema: startedAt, updatedAt, archivedAt. Added.

## packages/core/docs/migrations.md

- The doc said "13 tables across two domains" and listed 3 newsletter
  tables as if they were part of migrationSQL. Actually migrationSQL
  only contains 10 inbox tables. The 3 newsletter tables
  (platform_audience, platform_subscriber, broadcast_audit) are
  Drizzle schema exports but are NOT in migrationSQL and are not
  written to by any shipped service.
- The newsletter table names were wrong: doc said mailing_list,
  subscriber, campaign. Actual names are platform_audience,
  platform_subscriber, broadcast_audit.
- The doc said system folders are "initialized automatically" when a
  mailbox is created. They are not. The consumer has to explicitly
  call FolderService.initSystemFolders(mailboxId).
- The "Conventions" section said "every table has deletedAt". Two
  label join tables (inbox_message_label, inbox_thread_label) do not
  have deletedAt because their delete semantic is direct row removal.

## packages/core/docs/newsletters.md

- The entire schema section described a completely different design
  than what exists in newsletter.ts. The doc had tables mailing_list,
  subscriber, campaign with fields like status, metadata, scheduledAt,
  htmlBody -- none of which exist. Actual tables are platform_audience
  (providerListId, name, description, slug), platform_subscriber
  (userId, audienceId, providerSubscriberId, subscribedAt), and
  broadcast_audit (providerCampaignId, subject, contentHash, sentBy,
  audienceName, recipientCount, sentAt).
- The doc described a "status lifecycle" (subscribed -> unsubscribed
  / bounced / complained) that does not exist because there is no
  status column on platform_subscriber.
- The doc described a campaign lifecycle (draft -> scheduled ->
  sending -> sent) that does not exist because there is no status
  column on broadcast_audit (which is an audit log, not a campaign
  record).
- Rewrote to accurately describe: (1) the EmailProvider surface as
  the authoritative API for mailing lists / subscribers / campaigns,
  (2) the 3 platform tables as OPTIONAL local mirror / audit tables
  the consumer may include in their own migrations, (3) that the
  provider (Resend) owns the authoritative data and the framework
  does not duplicate it locally by default.

## Verification

- pnpm test: 609 passing
- pnpm typecheck: clean
- pnpm lint: 0 warnings, 0 errors
- pnpm format:check: clean on modified files (.claude/settings.json
  is untracked session state, not part of this PR)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@ssilvius ssilvius force-pushed the fix/doc-accuracy-interface-drift branch from 6e36a5d to 09a9af7 Compare April 11, 2026 19:29
@ssilvius ssilvius merged commit bff23c9 into main Apr 11, 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