Skip to content

Commit e8358a7

Browse files
Igor Savinclaude
andcommitted
types(core): reject the removed messageTypeField option at compile time
`messageTypeField` was removed in core 25.x in favor of `messageTypeResolver`. Until now the option was simply absent from `CommonQueueOptions`, but TypeScript's excess-property checks are weakened across the long `Omit`/`&`/generic chains used by publisher options (`SnsPublisherManager.newPublisherOptions` etc.), so callers who hadn't migrated were silently passing a no-op option. The runtime impact is severe and silent: with payload offloading enabled, the offload step in `AbstractQueueService` only preserves the message `type` field on the offloaded body when `messageTypeResolver` is configured. Callers still on `messageTypeField` end up publishing SNS messages without `type` in the body, and any subscription with `FilterPolicyScope: 'MessageBody'` filtering on `type` silently drops the message before SQS delivery. Production-confirmed against file-storage-service after its 24.x → 25.x bump. Type the field as `?: never` with deprecation guidance so it now fails compilation with a clear error pointing at the migration. Add type-level guards in core and sns test suites that lock the contract in place. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent c78b37c commit e8358a7

3 files changed

Lines changed: 83 additions & 0 deletions

File tree

packages/core/lib/types/queueOptionsTypes.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,16 @@ export type CommonQueueOptions = {
114114
* }
115115
*/
116116
messageTypeResolver?: MessageTypeResolverConfig
117+
/**
118+
* @deprecated Removed in core 25.x. Use `messageTypeResolver: { messageTypePath: '<field>' }`
119+
* (or `{ literal: '<type>' }` / `{ resolver: fn }`) instead. See UPGRADING.md.
120+
*
121+
* Typed as `never` so callers that still pass it get a compile-time error rather than
122+
* a silent runtime drop. Notably, leaving this set on a publisher with payload offloading
123+
* causes the message `type` field to be stripped from the offloaded SNS body, which then
124+
* silently fails any downstream subscription whose FilterPolicy filters on `type`.
125+
*/
126+
messageTypeField?: never
117127
messageIdField?: string
118128
messageTimestampField?: string
119129
messageDeduplicationIdField?: string
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/**
2+
* Type-level tests for `CommonQueueOptions`. These guard against silent removals of legacy
3+
* options. The lib historically exposed `messageTypeField`, removed in core 25.x in favor
4+
* of `messageTypeResolver`. Callers that still pass the legacy option must get a compile-time
5+
* error rather than silently broken runtime behavior (for example: payload offloading drops
6+
* the message `type` field, then SNS subscriptions with `FilterPolicyScope: 'MessageBody'`
7+
* filter on `type` fail to deliver to SQS).
8+
*
9+
* Run with: npx tsc --noEmit
10+
*/
11+
import { describe, it } from 'vitest'
12+
import type { CommonQueueOptions } from '../../lib/types/queueOptionsTypes.ts'
13+
14+
describe('CommonQueueOptions — legacy `messageTypeField`', () => {
15+
it('rejects passing the removed `messageTypeField` option', () => {
16+
const _bad: CommonQueueOptions = {
17+
// @ts-expect-error - `messageTypeField` was removed in core 25.x; use `messageTypeResolver` instead
18+
messageTypeField: 'type',
19+
}
20+
})
21+
22+
it('accepts the supported `messageTypeResolver` shape', () => {
23+
const _ok1: CommonQueueOptions = { messageTypeResolver: { messageTypePath: 'type' } }
24+
const _ok2: CommonQueueOptions = { messageTypeResolver: { literal: 'order.created' } }
25+
const _ok3: CommonQueueOptions = {
26+
messageTypeResolver: { resolver: () => 'order.created' },
27+
}
28+
})
29+
})
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/**
2+
* Type-level guard for `SnsPublisherManager`. Reproduces the call shape that file-storage-service
3+
* was using when payload offloading silently broke after the core 24.x → 25.x bump:
4+
*
5+
* ```ts
6+
* new SnsPublisherManager(deps, {
7+
* ...
8+
* newPublisherOptions: {
9+
* messageTypeField: 'type', // <- removed in core 25.x; ignored at runtime, dropped `type` from offloaded SNS body
10+
* ...
11+
* },
12+
* })
13+
* ```
14+
*
15+
* This must now fail at compile time.
16+
*
17+
* Run with: npx tsc --noEmit
18+
*/
19+
import { describe, it } from 'vitest'
20+
import type { SNSPublisherOptions } from '../lib/sns/AbstractSnsPublisher.ts'
21+
22+
type AnyMessage = { type: string }
23+
24+
type NewPublisherOptions = Omit<
25+
SNSPublisherOptions<AnyMessage>,
26+
'messageSchemas' | 'creationConfig' | 'locatorConfig'
27+
>
28+
29+
describe('SnsPublisherManager `newPublisherOptions` — legacy `messageTypeField`', () => {
30+
it('rejects the removed `messageTypeField` option', () => {
31+
const _bad: NewPublisherOptions = {
32+
// @ts-expect-error - `messageTypeField` was removed in core 25.x; use `messageTypeResolver` instead
33+
messageTypeField: 'type',
34+
messageIdField: 'id',
35+
}
36+
})
37+
38+
it('accepts `messageTypeResolver`', () => {
39+
const _ok: NewPublisherOptions = {
40+
messageTypeResolver: { messageTypePath: 'type' },
41+
messageIdField: 'id',
42+
}
43+
})
44+
})

0 commit comments

Comments
 (0)