fix(server-runtime): preserve explicit empty data destinations (#1635)
This commit is contained in:
@@ -99,22 +99,6 @@ describe('route middleware', () => {
|
||||
|
||||
expect(collectDestinations(event)).toEqual(['label:env=prod'])
|
||||
})
|
||||
it('respects explicit empty destinations as an override', () => {
|
||||
const event = createSparkNotifyEvent({
|
||||
data: {
|
||||
id: 'evt-3',
|
||||
eventId: 'spark-3',
|
||||
kind: 'ping',
|
||||
urgency: 'soon',
|
||||
headline: 'hello',
|
||||
destinations: ['module:character'],
|
||||
},
|
||||
route: { destinations: [] },
|
||||
})
|
||||
|
||||
expect(collectDestinations(event)).toEqual([])
|
||||
})
|
||||
|
||||
it('treats an explicit empty route destination list as the override', () => {
|
||||
const event = createSparkNotifyEvent({
|
||||
data: {
|
||||
@@ -131,6 +115,36 @@ describe('route middleware', () => {
|
||||
expect(collectDestinations(event)).toEqual([])
|
||||
})
|
||||
|
||||
it('treats an explicit empty data destination list as the override', () => {
|
||||
const event = createSparkNotifyEvent({
|
||||
data: {
|
||||
id: 'evt-data-empty',
|
||||
eventId: 'spark-data-empty',
|
||||
kind: 'ping',
|
||||
urgency: 'soon',
|
||||
headline: 'hello',
|
||||
destinations: [],
|
||||
},
|
||||
route: undefined,
|
||||
})
|
||||
|
||||
expect(collectDestinations(event)).toEqual([])
|
||||
})
|
||||
|
||||
it('ignores primitive data payloads when checking destinations', () => {
|
||||
const event = {
|
||||
type: 'spark:notify',
|
||||
data: 'not-an-object',
|
||||
metadata: {
|
||||
source: { kind: 'plugin', plugin: { id: 'server-runtime' }, id: 'test' },
|
||||
event: { id: 'evt-primitive' },
|
||||
},
|
||||
route: undefined,
|
||||
} as unknown as WebSocketBaseEvent<'spark:notify', WebSocketEvents['spark:notify'], any>
|
||||
|
||||
expect(collectDestinations(event)).toBeUndefined()
|
||||
})
|
||||
|
||||
it('matches destinations by label selector', () => {
|
||||
const peer = createPeer({
|
||||
id: 'peer-2',
|
||||
|
||||
@@ -25,6 +25,8 @@ export interface RouteContext {
|
||||
|
||||
export type RouteMiddleware = (context: RouteContext) => RouteDecision | void
|
||||
|
||||
type DestinationList = Array<string | RouteTargetExpression>
|
||||
|
||||
function getPeerLabels(peer: AuthenticatedPeer) {
|
||||
return {
|
||||
...peer.identity?.plugin?.labels,
|
||||
@@ -118,25 +120,29 @@ export function createPolicyMiddleware(policy: RoutingPolicy): RouteMiddleware {
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the destinations attached to an event.
|
||||
* Collects explicit route destinations from the route envelope or event payload.
|
||||
*
|
||||
* Use when:
|
||||
* - Route-level destinations should override payload-level destinations
|
||||
* - Delivery logic needs to distinguish between "broadcast" and "explicitly send nowhere"
|
||||
* - Routing middleware needs the effective destination override for a websocket event
|
||||
* - Callers must preserve explicit empty destination lists instead of falling back to broadcast
|
||||
*
|
||||
* Expects:
|
||||
* - An explicit empty `route.destinations` array is a meaningful override
|
||||
* - A websocket event whose `route.destinations` or `data.destinations` may be present
|
||||
* - `data.destinations` is only treated as valid when it is an array-shaped override
|
||||
*
|
||||
* Returns:
|
||||
* - The route destinations, payload destinations, or `undefined` when the event is unrestricted
|
||||
* - The explicit destination list when present
|
||||
* - `undefined` when no destination override was provided
|
||||
*/
|
||||
export function collectDestinations(event: WebSocketEvent | (Omit<WebSocketEvent, 'metadata'> & Partial<Pick<WebSocketEvent, 'metadata'>>)) {
|
||||
export function collectDestinations(
|
||||
event: WebSocketEvent | (Omit<WebSocketEvent, 'metadata'> & Partial<Pick<WebSocketEvent, 'metadata'>>),
|
||||
): DestinationList | undefined {
|
||||
if (event.route && 'destinations' in event.route) {
|
||||
return event.route.destinations
|
||||
}
|
||||
|
||||
const data = event.data as { destinations?: Array<string | RouteTargetExpression> } | undefined
|
||||
if (data?.destinations?.length) {
|
||||
const data = event.data as unknown
|
||||
if (typeof data === 'object' && data !== null && 'destinations' in data && Array.isArray(data.destinations)) {
|
||||
return data.destinations
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user