Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion packages/app/api-contracts/src/sse/dualModeContracts.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { z } from 'zod/v4'
import type { RoutePathResolver } from '../apiContracts.ts'
import type { CommonRouteDefinitionMetadata, RoutePathResolver } from '../apiContracts.ts'
import type { HttpStatusCode } from '../HttpStatusCodes.ts'
import type { SSEMethod } from './sseContracts.ts'
import type { SSEEventSchemas } from './sseTypes.ts'
Expand Down Expand Up @@ -55,6 +55,10 @@ export type DualModeContractDefinition<
responseBodySchemasByStatusCode?: ResponseSchemasByStatusCode
serverSentEventSchemas: Events
isDualMode: true
metadata?: CommonRouteDefinitionMetadata
description?: string
summary?: string
tags?: readonly string[]
}

/**
Expand All @@ -75,4 +79,8 @@ export type AnyDualModeContractDefinition = {
responseBodySchemasByStatusCode?: Partial<Record<HttpStatusCode, z.ZodTypeAny>>
serverSentEventSchemas: SSEEventSchemas
isDualMode: true
metadata?: CommonRouteDefinitionMetadata
description?: string
summary?: string
tags?: readonly string[]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
import { describe, expect, expectTypeOf, it } from 'vitest'
import { z } from 'zod/v4'
import { buildSseContract } from './sseContractBuilders.ts'

const SCHEMA = z.object({})
const EVENTS = { data: z.object({ value: z.string() }) }

type Metadata = {
myTestProp?: string[]
mySecondTestProp?: number
}

declare module '../apiContracts.ts' {
interface CommonRouteDefinitionMetadata extends Metadata {}
}

describe('buildSseContract metadata augmentation', () => {
describe('SSE GET route', () => {
it('should respect metadata type and reflect it on contract', () => {
const contract = buildSseContract({
method: 'get',
pathResolver: () => '/',
serverSentEventSchemas: EVENTS,
metadata: {
myTestProp: ['test'],
mySecondTestProp: 1,
extra: 'extra field',
},
})

expectTypeOf(contract.metadata).toMatchTypeOf<Metadata | undefined>()
expect(contract.metadata).toEqual({
myTestProp: ['test'],
mySecondTestProp: 1,
extra: 'extra field',
})
})

it('should reflect description, summary, and tags on contract', () => {
const contract = buildSseContract({
method: 'get',
pathResolver: () => '/',
serverSentEventSchemas: EVENTS,
description: 'Stream events',
summary: 'Event stream',
tags: ['streaming', 'sse'],
})

expect(contract.description).toBe('Stream events')
expect(contract.summary).toBe('Event stream')
expect(contract.tags).toEqual(['streaming', 'sse'])
})
})

describe('SSE POST route', () => {
it('should respect metadata type and reflect it on contract', () => {
const contract = buildSseContract({
method: 'post',
pathResolver: () => '/',
requestBodySchema: SCHEMA,
serverSentEventSchemas: EVENTS,
metadata: {
myTestProp: ['test2'],
mySecondTestProp: 2,
extra: 'extra field2',
},
})

expectTypeOf(contract.metadata).toMatchTypeOf<Metadata | undefined>()
expect(contract.metadata).toEqual({
myTestProp: ['test2'],
mySecondTestProp: 2,
extra: 'extra field2',
})
})

it('should reflect description, summary, and tags on contract', () => {
const contract = buildSseContract({
method: 'post',
pathResolver: () => '/',
requestBodySchema: SCHEMA,
serverSentEventSchemas: EVENTS,
description: 'Stream with body',
summary: 'Body stream',
tags: ['streaming'],
})

expect(contract.description).toBe('Stream with body')
expect(contract.summary).toBe('Body stream')
expect(contract.tags).toEqual(['streaming'])
})
})

describe('Dual-mode GET route', () => {
it('should respect metadata type and reflect it on contract', () => {
const contract = buildSseContract({
method: 'get',
pathResolver: () => '/',
successResponseBodySchema: SCHEMA,
serverSentEventSchemas: EVENTS,
metadata: {
myTestProp: ['test3'],
mySecondTestProp: 3,
extra: 'extra field3',
},
})

expectTypeOf(contract.metadata).toMatchTypeOf<Metadata | undefined>()
expect(contract.metadata).toEqual({
myTestProp: ['test3'],
mySecondTestProp: 3,
extra: 'extra field3',
})
})

it('should reflect description, summary, and tags on contract', () => {
const contract = buildSseContract({
method: 'get',
pathResolver: () => '/',
successResponseBodySchema: SCHEMA,
serverSentEventSchemas: EVENTS,
description: 'Dual-mode GET',
summary: 'GET dual',
tags: ['dual-mode', 'sse'],
})

expect(contract.description).toBe('Dual-mode GET')
expect(contract.summary).toBe('GET dual')
expect(contract.tags).toEqual(['dual-mode', 'sse'])
})
})

describe('Dual-mode POST route', () => {
it('should respect metadata type and reflect it on contract', () => {
const contract = buildSseContract({
method: 'post',
pathResolver: () => '/',
requestBodySchema: SCHEMA,
successResponseBodySchema: SCHEMA,
serverSentEventSchemas: EVENTS,
metadata: {
myTestProp: ['test4'],
mySecondTestProp: 4,
extra: 'extra field4',
},
})

expectTypeOf(contract.metadata).toMatchTypeOf<Metadata | undefined>()
expect(contract.metadata).toEqual({
myTestProp: ['test4'],
mySecondTestProp: 4,
extra: 'extra field4',
})
})

it('should reflect description, summary, and tags on contract', () => {
const contract = buildSseContract({
method: 'post',
pathResolver: () => '/',
requestBodySchema: SCHEMA,
successResponseBodySchema: SCHEMA,
serverSentEventSchemas: EVENTS,
description: 'Dual-mode POST',
summary: 'POST dual',
tags: ['dual-mode'],
})

expect(contract.description).toBe('Dual-mode POST')
expect(contract.summary).toBe('POST dual')
expect(contract.tags).toEqual(['dual-mode'])
})
})
})
22 changes: 21 additions & 1 deletion packages/app/api-contracts/src/sse/sseContractBuilders.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { z } from 'zod/v4'
import type { RoutePathResolver } from '../apiContracts.ts'
import type { CommonRouteDefinitionMetadata, RoutePathResolver } from '../apiContracts.ts'
import type { HttpStatusCode } from '../HttpStatusCodes.ts'
import type { DualModeContractDefinition } from './dualModeContracts.ts'
import type { SSEContractDefinition } from './sseContracts.ts'
Expand Down Expand Up @@ -40,6 +40,10 @@ export type SSEGetContractConfig<
responseBodySchemasByStatusCode?: ResponseSchemasByStatusCode
requestBodySchema?: never
successResponseBodySchema?: never
metadata?: CommonRouteDefinitionMetadata
description?: string
summary?: string
tags?: readonly string[]
}

/**
Expand Down Expand Up @@ -78,6 +82,10 @@ export type SSEPayloadContractConfig<
*/
responseBodySchemasByStatusCode?: ResponseSchemasByStatusCode
successResponseBodySchema?: never
metadata?: CommonRouteDefinitionMetadata
description?: string
summary?: string
tags?: readonly string[]
}

/**
Expand Down Expand Up @@ -130,6 +138,10 @@ export type DualModeGetContractConfig<
responseBodySchemasByStatusCode?: ResponseSchemasByStatusCode
serverSentEventSchemas: Events
requestBodySchema?: never
metadata?: CommonRouteDefinitionMetadata
description?: string
summary?: string
tags?: readonly string[]
}

/**
Expand Down Expand Up @@ -183,6 +195,10 @@ export type DualModePayloadContractConfig<
*/
responseBodySchemasByStatusCode?: ResponseSchemasByStatusCode
serverSentEventSchemas: Events
metadata?: CommonRouteDefinitionMetadata
description?: string
summary?: string
tags?: readonly string[]
}

/**
Expand Down Expand Up @@ -250,6 +266,10 @@ function buildBaseFields(config: any, hasBody: boolean) {
requestHeaderSchema: config.requestHeaderSchema,
requestBodySchema: hasBody ? config.requestBodySchema : undefined,
serverSentEventSchemas: config.serverSentEventSchemas,
metadata: config.metadata,
description: config.description,
summary: config.summary,
tags: config.tags,
}
}

Expand Down
10 changes: 9 additions & 1 deletion packages/app/api-contracts/src/sse/sseContracts.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { z } from 'zod/v4'
import type { RoutePathResolver } from '../apiContracts.ts'
import type { CommonRouteDefinitionMetadata, RoutePathResolver } from '../apiContracts.ts'
import type { HttpStatusCode } from '../HttpStatusCodes.ts'
import type { SSEEventSchemas } from './sseTypes.ts'

Expand Down Expand Up @@ -58,6 +58,10 @@ export type SSEContractDefinition<
*/
responseBodySchemasByStatusCode?: ResponseSchemasByStatusCode
isSSE: true
metadata?: CommonRouteDefinitionMetadata
description?: string
summary?: string
tags?: readonly string[]
}

/**
Expand All @@ -75,4 +79,8 @@ export type AnySSEContractDefinition = {
serverSentEventSchemas: SSEEventSchemas
responseBodySchemasByStatusCode?: Partial<Record<HttpStatusCode, z.ZodTypeAny>>
isSSE: true
metadata?: CommonRouteDefinitionMetadata
description?: string
summary?: string
tags?: readonly string[]
}
Loading