diff --git a/packages/aws-lambda/src/response.test.ts b/packages/aws-lambda/src/response.test.ts index 48872f8..4b11b2b 100644 --- a/packages/aws-lambda/src/response.test.ts +++ b/packages/aws-lambda/src/response.test.ts @@ -64,7 +64,7 @@ describe('sendStandardResponse', () => { it('buffered (empty)', async () => { const responseStream = createResponseStream() - const options = { eventStream: { keepAlive: { enabled: true } } } + const options = { eventStream: { keepAlive: true } } await sendStandardResponse( responseStream, { diff --git a/packages/aws-lambda/src/response.ts b/packages/aws-lambda/src/response.ts index 9a4c979..78f8cd1 100644 --- a/packages/aws-lambda/src/response.ts +++ b/packages/aws-lambda/src/response.ts @@ -1,10 +1,10 @@ import type { StandardResponse } from '@standard-server/core' import type { ToNodeHttpBodyOptions } from '@standard-server/node' import { - canWriteToNodeResponse, + canWriteNodeHttpResponse, destroyNodeHttpBody, - getNodeResponseError, - isNodeResponseDisconnectError, + getNodeHttpResponseError, + isNodeHttpResponseDisconnectError, toNodeHttpBody, } from '@standard-server/node' @@ -36,12 +36,12 @@ export async function sendStandardResponse( ) return new Promise((resolve, reject) => { - if (!canWriteToNodeResponse(responseStream)) { - const error = getNodeResponseError(responseStream) + if (!canWriteNodeHttpResponse(responseStream)) { + const error = getNodeHttpResponseError(responseStream) destroyNodeHttpBody(resBody, error, reject) - if (error && !isNodeResponseDisconnectError(error)) { + if (error && !isNodeHttpResponseDisconnectError(error)) { reject(error) } else { resolve() @@ -51,7 +51,7 @@ export async function sendStandardResponse( } responseStream.once('error', (error) => { - if (!isNodeResponseDisconnectError(error)) { + if (!isNodeHttpResponseDisconnectError(error)) { reject(error) } }) @@ -80,7 +80,7 @@ export async function sendStandardResponse( } else { res.once('close', () => { if (!resBody.closed) { - resBody.destroy(getNodeResponseError(res) ?? undefined) + resBody.destroy(getNodeHttpResponseError(res) ?? undefined) } }) diff --git a/packages/core/README.md b/packages/core/README.md index d7f451c..cfad970 100644 --- a/packages/core/README.md +++ b/packages/core/README.md @@ -164,7 +164,7 @@ Standard Server uses [AsyncIteratorObject](https://developer.mozilla.org/en-US/d ```ts import type { StandardResponse } from '@standard-server/core' -import { ErrorEvent, withEventMeta } from '@standard-server/core' +import { EventStreamErrorEvent, withEventMeta } from '@standard-server/core' const response: StandardResponse = { status: 200, @@ -175,7 +175,7 @@ const response: StandardResponse = { { id: '1', retry: 3000, comments: ['hidden'] }, ) - throw new ErrorEvent({ message: 'Something went wrong' }) + throw new EventStreamErrorEvent({ message: 'Something went wrong' }) return { message: 'This is the end of the stream' } })(), @@ -463,13 +463,13 @@ The package also exports: - `EventStreamEncoderError` for invalid outbound SSE messages - `EventStreamDecoderError` for incomplete or invalid inbound stream decoding -- `ErrorEvent` for wrapping structured event-stream error payloads in an `Error` -- `assertEventStreamMessageId()`, `assertEventStreamMessageName()`, `assertEventStreamMessageRetry()`, and `assertEventStreamMessageComment()` for low-level validation when building custom SSE tooling +- `EventStreamErrorEvent` for wrapping structured event-stream error payloads in an `Error` +- `assertEventStreamMessageId()`, `assertEventStreamMessageEvent()`, `assertEventStreamMessageRetry()`, and `assertEventStreamMessageComment()` for low-level validation when building custom SSE tooling ```ts -import { ErrorEvent } from '@standard-server/core' +import { EventStreamErrorEvent } from '@standard-server/core' -const error = new ErrorEvent( +const error = new EventStreamErrorEvent( { code: 'E_STREAM', detail: 'Connection lost' }, { message: 'stream error' }, ) diff --git a/packages/core/src/event-stream/encoder.test.ts b/packages/core/src/event-stream/encoder.test.ts index ac7cf7e..876172c 100644 --- a/packages/core/src/event-stream/encoder.test.ts +++ b/packages/core/src/event-stream/encoder.test.ts @@ -2,7 +2,7 @@ import { decodeEventStreamMessage } from './decoder.ts' import { assertEventStreamMessageComment, assertEventStreamMessageId, - assertEventStreamMessageName, + assertEventStreamMessageEvent, assertEventStreamMessageRetry, encodeEventStreamMessage, encodeEventStreamMessageComments, @@ -51,7 +51,7 @@ describe('predicates', () => { describe('assertions', () => { it('accept values without line breaks', () => { expect(() => assertEventStreamMessageId('123')).not.toThrow() - expect(() => assertEventStreamMessageName('message')).not.toThrow() + expect(() => assertEventStreamMessageEvent('message')).not.toThrow() expect(() => assertEventStreamMessageComment('hi')).not.toThrow() expect(() => assertEventStreamMessageRetry(0)).not.toThrow() expect(() => assertEventStreamMessageRetry(10000)).not.toThrow() @@ -67,7 +67,7 @@ describe('assertions', () => { it('reject event names containing line breaks', () => { for (const lineBreak of ['\n', '\r', '\r\n']) { - expect(() => assertEventStreamMessageName(`hi${lineBreak}`)).toThrow( + expect(() => assertEventStreamMessageEvent(`hi${lineBreak}`)).toThrow( "Event's event must not contain a carriage return or newline character", ) } diff --git a/packages/core/src/event-stream/encoder.ts b/packages/core/src/event-stream/encoder.ts index 3447a3b..fe92066 100644 --- a/packages/core/src/event-stream/encoder.ts +++ b/packages/core/src/event-stream/encoder.ts @@ -31,7 +31,7 @@ export function assertEventStreamMessageId(id: string): void { } } -export function assertEventStreamMessageName(event: string): void { +export function assertEventStreamMessageEvent(event: string): void { if (containsEventStreamLineBreak(event)) { throw new EventStreamEncoderError( "Event's event must not contain a carriage return or newline character", @@ -81,7 +81,7 @@ export function encodeEventStreamMessage(message: EventStreamMessage): string { // 'message' is the default for a message with data. Without data, keep it // so the message is not mistaken for a comment. if (message.event !== undefined && (message.event !== 'message' || message.data === undefined)) { - assertEventStreamMessageName(message.event) + assertEventStreamMessageEvent(message.event) output += `event: ${message.event}\n` } diff --git a/packages/core/src/event-stream/error.test.ts b/packages/core/src/event-stream/error.test.ts index 3a11a08..43fa793 100644 --- a/packages/core/src/event-stream/error.test.ts +++ b/packages/core/src/event-stream/error.test.ts @@ -1,12 +1,14 @@ -import { ErrorEvent } from './error.ts' +import { EventStreamErrorEvent } from './error.ts' it('errorEvent', () => { - const errorEvent = new ErrorEvent('data', { cause: 'cause' }) + const errorEvent = new EventStreamErrorEvent('data', { cause: 'cause' }) expect(errorEvent.message).toBe('Error Event') expect(errorEvent.cause).toBe('cause') expect(errorEvent.data).toBe('data') - const errorEventWithCustomMessage = new ErrorEvent('data', { message: 'custom message' }) + const errorEventWithCustomMessage = new EventStreamErrorEvent('data', { + message: 'custom message', + }) expect(errorEventWithCustomMessage.message).toBe('custom message') }) diff --git a/packages/core/src/event-stream/error.ts b/packages/core/src/event-stream/error.ts index 27acb3d..65684a0 100644 --- a/packages/core/src/event-stream/error.ts +++ b/packages/core/src/event-stream/error.ts @@ -1,15 +1,15 @@ export class EventStreamEncoderError extends TypeError {} export class EventStreamDecoderError extends TypeError {} -export interface ErrorEventOptions extends ErrorOptions { - message?: string +export interface EventStreamErrorEventOptions extends ErrorOptions { + message?: string | undefined } -export class ErrorEvent extends Error { +export class EventStreamErrorEvent extends Error { constructor( public data: unknown, - options: ErrorEventOptions = {}, + options: EventStreamErrorEventOptions = {}, ) { - super(options?.message ?? 'Error Event', options) + super(options.message ?? 'Error Event', options) } } diff --git a/packages/core/src/event-stream/meta.ts b/packages/core/src/event-stream/meta.ts index d8b142f..2231807 100644 --- a/packages/core/src/event-stream/meta.ts +++ b/packages/core/src/event-stream/meta.ts @@ -1,4 +1,4 @@ -import { getOrBind, isTypescriptObject } from '@standard-server/shared' +import { getOrBind, isNonPrimitive } from '@standard-server/shared' import { assertEventStreamMessageComment, @@ -59,7 +59,7 @@ export function withEventMeta(container: T, meta: EventMeta): * Unwraps an iterator event value and extracts its associated metadata. */ export function unwrapEvent(container: T): [data: T, meta: EventMeta | undefined] { - if (!isTypescriptObject(container)) { + if (!isNonPrimitive(container)) { return [container, undefined] } @@ -74,7 +74,7 @@ export function unwrapEvent(container: T): [data: T, meta: EventMeta | undefi * Retrieves metadata attached to a single iterator event value. */ export function getEventMeta(container: unknown): EventMeta | undefined { - if (!isTypescriptObject(container)) { + if (!isNonPrimitive(container)) { return undefined } diff --git a/packages/core/src/validators.ts b/packages/core/src/validators.ts index db6714e..1fd9a11 100644 --- a/packages/core/src/validators.ts +++ b/packages/core/src/validators.ts @@ -1,4 +1,4 @@ -import { isTypescriptObject } from '@standard-server/shared' +import { isNonPrimitive } from '@standard-server/shared' import type { StandardHeaders, @@ -17,7 +17,7 @@ export function isStandardUrl(maybe: unknown): maybe is StandardUrl { } export function isStandardHeaders(maybe: unknown): maybe is StandardHeaders { - if (!isTypescriptObject(maybe)) { + if (!isNonPrimitive(maybe)) { return false } @@ -34,7 +34,7 @@ export function isStandardStatus(maybe: unknown): maybe is number { } export function isStandardRequest(maybe: unknown): maybe is StandardRequest { - if (!isTypescriptObject(maybe)) { + if (!isNonPrimitive(maybe)) { return false } @@ -48,7 +48,7 @@ export function isStandardRequest(maybe: unknown): maybe is StandardRequest { } export function isStandardResponse(maybe: unknown): maybe is StandardResponse { - if (!isTypescriptObject(maybe)) { + if (!isNonPrimitive(maybe)) { return false } diff --git a/packages/fastify/src/response.test.ts b/packages/fastify/src/response.test.ts index ba1d587..019940d 100644 --- a/packages/fastify/src/response.test.ts +++ b/packages/fastify/src/response.test.ts @@ -22,7 +22,7 @@ describe('sendStandardResponse', () => { const fastify = Fastify() onTestFinished(() => fastify.close()) - const options = { eventStream: { keepAlive: { enabled: true } } } + const options = { eventStream: { keepAlive: true } } fastify.get('/', async (req, reply) => { sendSpy = vi.spyOn(reply, 'send') @@ -67,7 +67,7 @@ describe('sendStandardResponse', () => { const fastify = Fastify() onTestFinished(() => fastify.close()) - const options = { eventStream: { keepAlive: { enabled: true } } } + const options = { eventStream: { keepAlive: true } } fastify.get('/', async (req, reply) => { sendSpy = vi.spyOn(reply, 'send') @@ -140,7 +140,7 @@ describe('sendStandardResponse', () => { const fastify = Fastify() onTestFinished(() => fastify.close()) - const options = { eventStream: { keepAlive: { enabled: true } } } + const options = { eventStream: { keepAlive: true } } fastify.get('/', async (req, reply) => { sendSpy = vi.spyOn(reply, 'send') @@ -198,7 +198,7 @@ describe('sendStandardResponse', () => { const fastify = Fastify() onTestFinished(() => fastify.close()) - const options = { eventStream: { keepAlive: { enabled: true } } } + const options = { eventStream: { keepAlive: true } } fastify.get('/', async (req, reply) => { sendSpy = vi.spyOn(reply, 'send') diff --git a/packages/fastify/src/response.ts b/packages/fastify/src/response.ts index 72ed7df..c784ecc 100644 --- a/packages/fastify/src/response.ts +++ b/packages/fastify/src/response.ts @@ -1,10 +1,10 @@ import type { StandardResponse } from '@standard-server/core' import type { ToNodeHttpBodyOptions } from '@standard-server/node' import { - canWriteToNodeResponse, + canWriteNodeHttpResponse, destroyNodeHttpBody, - getNodeResponseError, - isNodeResponseDisconnectError, + getNodeHttpResponseError, + isNodeHttpResponseDisconnectError, toNodeHttpBody, } from '@standard-server/node' @@ -24,12 +24,12 @@ export async function sendStandardResponse( ) return new Promise((resolve, reject) => { - if (!canWriteToNodeResponse(reply.raw)) { - const error = getNodeResponseError(reply.raw) + if (!canWriteNodeHttpResponse(reply.raw)) { + const error = getNodeHttpResponseError(reply.raw) destroyNodeHttpBody(resBody, error, reject) - if (error && !isNodeResponseDisconnectError(error)) { + if (error && !isNodeHttpResponseDisconnectError(error)) { reject(error) } else { resolve() @@ -41,7 +41,7 @@ export async function sendStandardResponse( const connection = 'stream' in reply.raw ? reply.raw.stream : reply.raw connection.once('error', (error) => { - if (!isNodeResponseDisconnectError(error)) { + if (!isNodeHttpResponseDisconnectError(error)) { reject(error) } }) diff --git a/packages/fetch/src/body.test.ts b/packages/fetch/src/body.test.ts index cc30e97..e7e8cfd 100644 --- a/packages/fetch/src/body.test.ts +++ b/packages/fetch/src/body.test.ts @@ -502,7 +502,7 @@ describe('toFetchBody', () => { yield 123 return 456 } - const options = { eventStream: { keepAlive: { enabled: false } } } + const options = { eventStream: { keepAlive: false } } const [body, headers] = toFetchBody(gen(), baseHeaders, options) expect(toEventStreamSpy).toHaveBeenCalledWith(gen(), options.eventStream) diff --git a/packages/fetch/src/body.ts b/packages/fetch/src/body.ts index 7dbeb70..ff1b70a 100644 --- a/packages/fetch/src/body.ts +++ b/packages/fetch/src/body.ts @@ -22,10 +22,10 @@ export interface ToStandardBodyOptions { */ export async function toStandardBody( re: Request | Response, - options?: ToStandardBodyOptions, + options: ToStandardBodyOptions = {}, ): Promise { const hint = - options?.hint ?? + options.hint ?? resolveStandardBodyHint({ 'standard-server': re.headers.get('standard-server') ?? undefined, 'content-type': re.headers.get('content-type') ?? undefined, @@ -87,7 +87,7 @@ export interface ToFetchBodyOptions { /** * Options for the event stream, like keep-alive settings, initial comment, etc. */ - eventStream?: ToEventStreamOptions + eventStream?: ToEventStreamOptions | undefined } /** diff --git a/packages/fetch/src/event-stream.test.ts b/packages/fetch/src/event-stream.test.ts index 4e6a4b3..6701ca0 100644 --- a/packages/fetch/src/event-stream.test.ts +++ b/packages/fetch/src/event-stream.test.ts @@ -1,5 +1,5 @@ import { - ErrorEvent, + EventStreamErrorEvent, EventStreamDecoderError, getEventMeta, withEventMeta, @@ -209,7 +209,7 @@ describe('toAsyncIteratorObject', () => { }) await expect(generator.next()).rejects.toSatisfy((error: any) => { - expect(error).toBeInstanceOf(ErrorEvent) + expect(error).toBeInstanceOf(EventStreamErrorEvent) expect(error.data).toEqual({ order: 3 }) expect(getEventMeta(error)).toEqual({ id: 'id-3', retry: 30000 }) @@ -320,7 +320,7 @@ describe('toEventStream', () => { expect(await reader.read()).toEqual({ done: true }) }) - it('with non-ErrorEvent error', async () => { + it('with non-EventStreamErrorEvent error', async () => { async function* gen() { yield withEventMeta({ order: 1 }, { id: 'id-1' }) yield withEventMeta({ order: 2 }, { retry: 20000 }) @@ -337,12 +337,12 @@ describe('toEventStream', () => { await expect(reader.read()).rejects.toThrow('order-4') }) - it('with ErrorEvent error', async () => { + it('with EventStreamErrorEvent error', async () => { async function* gen() { yield withEventMeta({ order: 1 }, { id: 'id-1' }) yield withEventMeta({ order: 2 }, { retry: 20000 }) yield undefined - throw withEventMeta(new ErrorEvent({ order: 4 }), { id: 'id-4', retry: 40000 }) + throw withEventMeta(new EventStreamErrorEvent({ order: 4 }), { id: 'id-4', retry: 40000 }) } const reader = toEventStream(gen()).pipeThrough(new TextDecoderStream()).getReader() @@ -515,8 +515,8 @@ describe('toEventStream', () => { } const stream = toEventStream(gen(), { - initialComment: { enabled: false }, - keepAlive: { enabled: true, interval: 40, comment: 'ping' }, + initialComment: false, + keepAlive: { interval: 40, comment: 'ping' }, }) const reader = stream.pipeThrough(new TextDecoderStream()).getReader() @@ -564,8 +564,8 @@ describe('toEventStream', () => { } const stream = toEventStream(gen(), { - initialComment: { enabled: false }, - keepAlive: { enabled: false, interval: 40, comment: 'ping' }, + initialComment: false, + keepAlive: false, }) const reader = stream.pipeThrough(new TextDecoderStream()).getReader() @@ -585,6 +585,24 @@ describe('toEventStream', () => { }) }) + it('true uses default initial comment and keep-alive', async () => { + async function* gen() { + await sleep(15000) + yield 'hello' + } + + const stream = toEventStream(gen(), { initialComment: true, keepAlive: true }) + + const reader = stream.pipeThrough(new TextDecoderStream()).getReader() + + await expect(reader.read()).resolves.toEqual({ done: false, value: ': \n\n' }) + + await Promise.all([ + expect(reader.read()).resolves.toEqual({ done: false, value: ': \n\n' }), + vi.advanceTimersByTimeAsync(15000), + ]) + }) + describe('initial comment', () => { it('enabled', async () => { async function* gen() { @@ -593,8 +611,8 @@ describe('toEventStream', () => { } const stream = toEventStream(gen(), { - initialComment: { enabled: true, comment: 'stream-started' }, - keepAlive: { enabled: false }, + initialComment: { comment: 'stream-started' }, + keepAlive: false, }) const reader = stream.pipeThrough(new TextDecoderStream()).getReader() @@ -618,8 +636,8 @@ describe('toEventStream', () => { } const stream = toEventStream(gen(), { - initialComment: { enabled: false }, - keepAlive: { enabled: false }, + initialComment: false, + keepAlive: false, }) const reader = stream.pipeThrough(new TextDecoderStream()).getReader() @@ -646,7 +664,7 @@ it.each([[[1, 2, 3, 4, 5, 6]], [[{ a: 1 }, { b: 2 }, { c: 3 }, { d: 4 }, { e: 5 yield value } })(), - { keepAlive: { enabled: true, interval: 10 } }, + { keepAlive: { interval: 10 } }, ), ) diff --git a/packages/fetch/src/event-stream.ts b/packages/fetch/src/event-stream.ts index 8e64511..4e7ad53 100644 --- a/packages/fetch/src/event-stream.ts +++ b/packages/fetch/src/event-stream.ts @@ -1,6 +1,6 @@ import { encodeEventStreamMessage, - ErrorEvent, + EventStreamErrorEvent, EventStreamDecoderError, EventStreamDecoderStream, getEventMeta, @@ -9,7 +9,7 @@ import { } from '@standard-server/core' import { AsyncIteratorClass, - isTypescriptObject, + isNonPrimitive, parseEmptyableJSON, stringifyJSON, } from '@standard-server/shared' @@ -44,7 +44,7 @@ export function toAsyncIteratorObject( case 'message': { let message = parseEmptyableJSON(value.data) - if (isTypescriptObject(message)) { + if (isNonPrimitive(message)) { message = withEventMeta(message, value) } @@ -52,7 +52,7 @@ export function toAsyncIteratorObject( } case 'error': { - let error = new ErrorEvent(parseEmptyableJSON(value.data)) + let error = new EventStreamErrorEvent(parseEmptyableJSON(value.data)) error = withEventMeta(error, value) @@ -62,7 +62,7 @@ export function toAsyncIteratorObject( case 'close': { let close = parseEmptyableJSON(value.data) - if (isTypescriptObject(close)) { + if (isNonPrimitive(close)) { close = withEventMeta(close, value) } @@ -79,70 +79,62 @@ export function toAsyncIteratorObject( export interface ToEventStreamOptions { /** - * If enabled, an initial comment is sent immediately upon stream start to flush headers. - * This allows the receiving side to establish the connection without waiting for the first event. + * Initial comment sent immediately upon stream start to flush headers. This allows the + * receiving side to establish the connection without waiting for the first event. * - * @default { enabled: true } + * `false` disables it, {@link ToEventStreamInitialCommentOptions} customizes it. + * + * @default true */ - initialComment?: - | undefined - | { - /** - * If true, an initial comment is sent immediately upon stream start to flush headers. - * This allows the receiving side to establish the connection without waiting for the first event. - * - * @default true - */ - enabled?: boolean + initialComment?: boolean | ToEventStreamInitialCommentOptions | undefined - /** - * The content of the initial comment sent upon stream start. Must not include newline characters. - * - * @default '' - */ - comment?: string - } + /** + * Ping comment sent periodically to keep the connection alive. + * + * `false` disables it, {@link ToEventStreamKeepAliveOptions} customizes it. + * + * @default true + */ + keepAlive?: boolean | ToEventStreamKeepAliveOptions | undefined +} +export interface ToEventStreamInitialCommentOptions { /** - * If enabled, a ping comment is sent periodically to keep the connection alive. + * The content of the initial comment sent upon stream start. Must not include newline characters. * - * @default { enabled: true } + * @default '' */ - keepAlive?: - | undefined - | { - /** - * If true, a ping comment is sent periodically to keep the connection alive. - * - * @default true - */ - enabled: boolean + comment?: string | undefined +} - /** - * Interval (in milliseconds) between ping comments sent after the last event. - * - * @default 15000 - */ - interval?: number +export interface ToEventStreamKeepAliveOptions { + /** + * Interval (in milliseconds) between ping comments sent after the last event. + * + * @default 15000 + */ + interval?: number | undefined - /** - * The content of the ping comment. Must not include newline characters. - * - * @default '' - */ - comment?: string - } + /** + * The content of the ping comment. Must not include newline characters. + * + * @default '' + */ + comment?: string | undefined } export function toEventStream( iterator: AsyncIterator, options: ToEventStreamOptions = {}, ): ReadableStream> { - const keepAliveEnabled = options.keepAlive?.enabled ?? true - const keepAliveInterval = options.keepAlive?.interval ?? 15000 - const keepAliveComment = options.keepAlive?.comment ?? '' - const initialCommentEnabled = options.initialComment?.enabled ?? true - const initialComment = options.initialComment?.comment ?? '' + const keepAlive = typeof options.keepAlive === 'object' ? options.keepAlive : {} + const initialComment = typeof options.initialComment === 'object' ? options.initialComment : {} + + const keepAliveEnabled = options.keepAlive !== false + const keepAliveInterval = keepAlive.interval ?? 15000 + const keepAliveComment = keepAlive.comment ?? '' + const initialCommentEnabled = options.initialComment !== false + const initialCommentContent = initialComment.comment ?? '' let cancelled = false let timeout: ReturnType | undefined @@ -152,7 +144,7 @@ export function toEventStream( if (initialCommentEnabled) { controller.enqueue( encodeEventStreamMessage({ - comments: [initialComment], + comments: [initialCommentContent], }), ) } @@ -179,7 +171,7 @@ export function toEventStream( return } - if (err instanceof ErrorEvent) { + if (err instanceof EventStreamErrorEvent) { controller.enqueue( encodeEventStreamMessage({ ...getEventMeta(err), @@ -190,7 +182,7 @@ export function toEventStream( controller.close() } else { /** - * Should treat a non-ErrorEvent as an error. + * Should treat a non-EventStreamErrorEvent as an error. */ controller.error(err) } diff --git a/packages/fetch/src/response.test.ts b/packages/fetch/src/response.test.ts index b05a0f0..6f7a893 100644 --- a/packages/fetch/src/response.test.ts +++ b/packages/fetch/src/response.test.ts @@ -24,7 +24,7 @@ describe('toFetchResponse', () => { status: 206, } - const options = { eventStream: { keepAlive: { enabled: true } } } + const options = { eventStream: { keepAlive: true } } const fetchResponse = toFetchResponse(standardResponse, options) expect(fetchResponse.status).toBe(206) diff --git a/packages/node/src/body.test.ts b/packages/node/src/body.test.ts index d043bb7..93fbaa8 100644 --- a/packages/node/src/body.test.ts +++ b/packages/node/src/body.test.ts @@ -821,7 +821,7 @@ describe('toNodeHttpBody', () => { yield 123 return 456 } - const options = { eventStream: { keepAlive: { enabled: true } } } + const options = { eventStream: { keepAlive: true } } const iterator = gen() const [body, headers] = toNodeHttpBody(iterator, baseHeaders, options) diff --git a/packages/node/src/body.ts b/packages/node/src/body.ts index 94dbddd..4c95610 100644 --- a/packages/node/src/body.ts +++ b/packages/node/src/body.ts @@ -39,7 +39,7 @@ export async function toStandardBody( } const hint = - options?.hint ?? + options.hint ?? resolveStandardBodyHint({ 'standard-server': req.headers['standard-server'], 'content-type': req.headers['content-type'], @@ -97,7 +97,7 @@ export interface ToNodeHttpBodyOptions { /** * Options for the event stream, like keep-alive settings, initial comment, etc. */ - eventStream?: ToEventStreamOptions + eventStream?: ToEventStreamOptions | undefined } /** diff --git a/packages/node/src/event-stream.ts b/packages/node/src/event-stream.ts index f0bdc77..50a402d 100644 --- a/packages/node/src/event-stream.ts +++ b/packages/node/src/event-stream.ts @@ -13,6 +13,11 @@ export function toAsyncIteratorObject(stream: Readable): AsyncIteratorClass { it('buffered (empty)', async () => { let endSpy: any - const options = { eventStream: { keepAlive: { enabled: true } } } + const options = { eventStream: { keepAlive: true } } const res = await request(async (req: IncomingMessage, res: ServerResponse) => { endSpy = vi.spyOn(res, 'end') @@ -62,7 +62,7 @@ describe('sendStandardResponse', () => { it('buffered', async () => { let endSpy: any - const options = { eventStream: { keepAlive: { enabled: true } } } + const options = { eventStream: { keepAlive: true } } const res = await request(async (req: IncomingMessage, res: ServerResponse) => { endSpy = vi.spyOn(res, 'end') @@ -104,7 +104,7 @@ describe('sendStandardResponse', () => { const blob = new Blob(['foo'], { type: 'text/plain' }) let endSpy: any - const options = { eventStream: { keepAlive: { enabled: true } } } + const options = { eventStream: { keepAlive: true } } const res = await request(async (req: IncomingMessage, res: ServerResponse) => { endSpy = vi.spyOn(res, 'end') @@ -155,7 +155,7 @@ describe('sendStandardResponse', () => { let endSpy: any - const options = { eventStream: { keepAlive: { enabled: true } } } + const options = { eventStream: { keepAlive: true } } const res = await request(async (req: IncomingMessage, res: ServerResponse) => { endSpy = vi.spyOn(res, 'end') @@ -206,7 +206,7 @@ describe('sendStandardResponse', () => { let endSpy: any - const options = { eventStream: { keepAlive: { enabled: true } } } + const options = { eventStream: { keepAlive: true } } const res = await request(async (req: IncomingMessage, res: ServerResponse) => { endSpy = vi.spyOn(res, 'end') diff --git a/packages/node/src/response.ts b/packages/node/src/response.ts index e5f545d..a5087f3 100644 --- a/packages/node/src/response.ts +++ b/packages/node/src/response.ts @@ -4,10 +4,10 @@ import type { ToNodeHttpBodyOptions } from './body.ts' import { toNodeHttpBody } from './body.ts' import type { NodeHttpResponse } from './types.ts' import { - canWriteToNodeResponse, + canWriteNodeHttpResponse, destroyNodeHttpBody, - getNodeResponseError, - isNodeResponseDisconnectError, + getNodeHttpResponseError, + isNodeHttpResponseDisconnectError, } from './utils.ts' export interface SendStandardResponseOptions extends ToNodeHttpBodyOptions {} @@ -24,12 +24,12 @@ export async function sendStandardResponse( ) return new Promise((resolve, reject) => { - if (!canWriteToNodeResponse(res)) { - const error = getNodeResponseError(res) + if (!canWriteNodeHttpResponse(res)) { + const error = getNodeHttpResponseError(res) destroyNodeHttpBody(resBody, error, reject) - if (error && !isNodeResponseDisconnectError(error)) { + if (error && !isNodeHttpResponseDisconnectError(error)) { reject(error) } else { resolve() @@ -41,7 +41,7 @@ export async function sendStandardResponse( const connection = 'stream' in res ? res.stream : res connection.once('error', (error) => { - if (!isNodeResponseDisconnectError(error)) { + if (!isNodeHttpResponseDisconnectError(error)) { reject(error) } }) @@ -66,7 +66,7 @@ export async function sendStandardResponse( } else { connection.once('close', () => { if (!resBody.closed) { - resBody.destroy(getNodeResponseError(res) ?? undefined) + resBody.destroy(getNodeHttpResponseError(res) ?? undefined) } }) diff --git a/packages/node/src/signal.ts b/packages/node/src/signal.ts index 2c6666c..d55744e 100644 --- a/packages/node/src/signal.ts +++ b/packages/node/src/signal.ts @@ -3,7 +3,7 @@ import type Stream from 'node:stream' import { AbortError } from '@standard-server/shared' import type { NodeHttpResponse } from './types.ts' -import { getNodeResponseError } from './utils.ts' +import { getNodeHttpResponseError } from './utils.ts' export function toAbortSignal(res: Stream.Writable | NodeHttpResponse): AbortSignal { const controller = new AbortController() @@ -13,7 +13,7 @@ export function toAbortSignal(res: Stream.Writable | NodeHttpResponse): AbortSig const onClose = () => { if (!res.writableEnded) { controller.abort( - getNodeResponseError(res) ?? + getNodeHttpResponseError(res) ?? new AbortError('Writable stream closed before it finished writing'), ) } diff --git a/packages/node/src/utils.test.ts b/packages/node/src/utils.test.ts index dc9f2d6..be27092 100644 --- a/packages/node/src/utils.test.ts +++ b/packages/node/src/utils.test.ts @@ -10,14 +10,14 @@ import { Readable } from 'node:stream' import { text } from 'node:stream/consumers' import { - canWriteToNodeResponse, + canWriteNodeHttpResponse, destroyNodeHttpBody, - getNodeResponseError, - isNodeResponseDisconnectError, + getNodeHttpResponseError, + isNodeHttpResponseDisconnectError, toWebReadableStream, } from './utils.ts' -describe('canWriteToNodeResponse', () => { +describe('canWriteNodeHttpResponse', () => { it('on http1 response aborted by client', async ({ onTestFinished }) => { const server = http.createServer() onTestFinished(() => new Promise((r) => server.close(r))) @@ -25,11 +25,11 @@ describe('canWriteToNodeResponse', () => { const handled = new Promise((resolve, reject) => { server.on('request', async (req, res) => { try { - expect(canWriteToNodeResponse(res)).toBe(true) + expect(canWriteNodeHttpResponse(res)).toBe(true) await new Promise((r) => res.once('close', () => r())) - expect(canWriteToNodeResponse(res)).toBe(false) + expect(canWriteNodeHttpResponse(res)).toBe(false) resolve() } catch (error) { @@ -59,12 +59,12 @@ describe('canWriteToNodeResponse', () => { const handled = new Promise((resolve, reject) => { server.on('request', async (req, res) => { try { - expect(canWriteToNodeResponse(res)).toBe(true) + expect(canWriteNodeHttpResponse(res)).toBe(true) res.end('ok') await vi.waitFor(() => { - expect(canWriteToNodeResponse(res)).toBe(false) + expect(canWriteNodeHttpResponse(res)).toBe(false) }) resolve() @@ -89,12 +89,12 @@ describe('canWriteToNodeResponse', () => { const handled = new Promise((resolve, reject) => { server.on('request', async (req, res) => { try { - expect(canWriteToNodeResponse(res)).toBe(true) + expect(canWriteNodeHttpResponse(res)).toBe(true) res.flushHeaders() expect(res.headersSent).toBe(true) - expect(canWriteToNodeResponse(res)).toBe(false) + expect(canWriteNodeHttpResponse(res)).toBe(false) res.end() @@ -120,11 +120,11 @@ describe('canWriteToNodeResponse', () => { const handled = new Promise((resolve, reject) => { server.on('request', async (req, res) => { try { - expect(canWriteToNodeResponse(res)).toBe(true) + expect(canWriteNodeHttpResponse(res)).toBe(true) await new Promise((r) => res.stream.once('close', () => r())) - expect(canWriteToNodeResponse(res)).toBe(false) + expect(canWriteNodeHttpResponse(res)).toBe(false) resolve() } catch (error) { @@ -155,12 +155,12 @@ describe('canWriteToNodeResponse', () => { const handled = new Promise((resolve, reject) => { server.on('request', async (req, res) => { try { - expect(canWriteToNodeResponse(res)).toBe(true) + expect(canWriteNodeHttpResponse(res)).toBe(true) res.end('ok') await vi.waitFor(() => { - expect(canWriteToNodeResponse(res)).toBe(false) + expect(canWriteNodeHttpResponse(res)).toBe(false) }) resolve() @@ -189,11 +189,11 @@ describe('canWriteToNodeResponse', () => { server.on('request', async (req, res) => { try { expect(res.stream.writableEnded).toBe(true) - expect(canWriteToNodeResponse(res)).toBe(true) + expect(canWriteNodeHttpResponse(res)).toBe(true) res.end() - expect(canWriteToNodeResponse(res)).toBe(false) + expect(canWriteNodeHttpResponse(res)).toBe(false) resolve() } catch (error) { @@ -214,7 +214,7 @@ describe('canWriteToNodeResponse', () => { }) }) -describe('getNodeResponseError', () => { +describe('getNodeHttpResponseError', () => { it('on http1 response destroyed with an error', async ({ onTestFinished }) => { const server = http.createServer() onTestFinished(() => new Promise((r) => server.close(r))) @@ -224,7 +224,7 @@ describe('getNodeResponseError', () => { const handled = new Promise((resolve, reject) => { server.on('request', async (req, res) => { try { - expect(getNodeResponseError(res)).toBe(null) + expect(getNodeHttpResponseError(res)).toBe(null) // catch error res.once('error', () => {}) @@ -232,7 +232,7 @@ describe('getNodeResponseError', () => { await new Promise((r) => res.once('close', () => r())) - expect(getNodeResponseError(res)).toBe(error) + expect(getNodeHttpResponseError(res)).toBe(error) resolve() } catch (error) { @@ -260,7 +260,7 @@ describe('getNodeResponseError', () => { await new Promise((r) => res.once('close', () => r())) - expect(getNodeResponseError(res)).toBe(null) + expect(getNodeHttpResponseError(res)).toBe(null) resolve() } catch (error) { @@ -286,7 +286,7 @@ describe('getNodeResponseError', () => { const handled = new Promise((resolve, reject) => { server.on('request', async (req, res) => { try { - expect(getNodeResponseError(res)).toBe(null) + expect(getNodeHttpResponseError(res)).toBe(null) // catch error res.stream.once('error', () => {}) @@ -294,7 +294,7 @@ describe('getNodeResponseError', () => { await new Promise((r) => res.stream.once('close', () => r())) - expect(getNodeResponseError(res)).toBe(error) + expect(getNodeHttpResponseError(res)).toBe(error) // `Http2ServerResponse` extends `Stream`, not `Writable`, so the error is only // reachable through the underlying `Http2Stream` - reading it off the response @@ -329,7 +329,7 @@ describe('getNodeResponseError', () => { await new Promise((r) => res.stream.once('close', () => r())) - expect(getNodeResponseError(res)).toBe(null) + expect(getNodeHttpResponseError(res)).toBe(null) resolve() } catch (error) { @@ -350,11 +350,13 @@ describe('getNodeResponseError', () => { }) }) -describe('isNodeResponseDisconnectError', () => { +describe('isNodeHttpResponseDisconnectError', () => { it.each(['ERR_HTTP2_STREAM_ERROR', 'ERR_HTTP2_SESSION_ERROR', 'ECONNRESET', 'EPIPE'])( 'on an error with code %s', (code) => { - expect(isNodeResponseDisconnectError(Object.assign(new Error('test'), { code }))).toBe(true) + expect(isNodeHttpResponseDisconnectError(Object.assign(new Error('test'), { code }))).toBe( + true, + ) }, ) @@ -365,7 +367,7 @@ describe('isNodeResponseDisconnectError', () => { Object.assign(new Error('test'), { code: 'ERR_STREAM_WRITE_AFTER_END' }), ], ])('on %s', (_, error) => { - expect(isNodeResponseDisconnectError(error)).toBe(false) + expect(isNodeHttpResponseDisconnectError(error)).toBe(false) }) }) diff --git a/packages/node/src/utils.ts b/packages/node/src/utils.ts index c24f2a3..19e3c70 100644 --- a/packages/node/src/utils.ts +++ b/packages/node/src/utils.ts @@ -78,7 +78,7 @@ async function _drainIterator(iterator: AsyncIterator): Promise { /** * Check the response can still be sent. */ -export function canWriteToNodeResponse(res: Stream.Writable | NodeHttpResponse): boolean { +export function canWriteNodeHttpResponse(res: Stream.Writable | NodeHttpResponse): boolean { return ( !('headersSent' in res && res.headersSent) && !res.writableEnded && @@ -89,7 +89,7 @@ export function canWriteToNodeResponse(res: Stream.Writable | NodeHttpResponse): /** * Get the error of the response, preferring its underlying stream's (http2). */ -export function getNodeResponseError(res: Stream.Writable | NodeHttpResponse): Error | null { +export function getNodeHttpResponseError(res: Stream.Writable | NodeHttpResponse): Error | null { if ('stream' in res) { return res.stream.errored ?? res.errored ?? null } @@ -97,7 +97,7 @@ export function getNodeResponseError(res: Stream.Writable | NodeHttpResponse): E return res.errored } -const NODE_RESPONSE_DISCONNECT_ERROR_CODES = new Set([ +const NODE_HTTP_RESPONSE_DISCONNECT_ERROR_CODES = new Set([ 'ERR_HTTP2_STREAM_ERROR', 'ERR_HTTP2_SESSION_ERROR', 'ECONNRESET', @@ -110,8 +110,8 @@ const NODE_RESPONSE_DISCONNECT_ERROR_CODES = new Set([ * A client disconnect only closes an http1 response, while on http2 it usually errors * the response too, so these errors should be handled like a plain close. */ -export function isNodeResponseDisconnectError(error: Error): boolean { - return NODE_RESPONSE_DISCONNECT_ERROR_CODES.has((error as { code?: unknown }).code) +export function isNodeHttpResponseDisconnectError(error: Error): boolean { + return NODE_HTTP_RESPONSE_DISCONNECT_ERROR_CODES.has((error as { code?: unknown }).code) } /** diff --git a/packages/peer/src/client.test.ts b/packages/peer/src/client.test.ts index 4757b48..6e711a0 100644 --- a/packages/peer/src/client.test.ts +++ b/packages/peer/src/client.test.ts @@ -1,5 +1,5 @@ import type { StandardLazyResponse, StandardRequest } from '@standard-server/core' -import { ErrorEvent } from '@standard-server/core' +import { EventStreamErrorEvent } from '@standard-server/core' import { AbortError, AsyncIteratorClass, @@ -855,8 +855,8 @@ describe('clientPeer', () => { const iter = (await response.resolveBody()) as AsyncIterator await expect(iter.next()).resolves.toEqual({ done: false, value: 'evt1' }) - await expect(iter.next()).rejects.toSatisfy((error: ErrorEvent) => { - expect(error).toBeInstanceOf(ErrorEvent) + await expect(iter.next()).rejects.toSatisfy((error: EventStreamErrorEvent) => { + expect(error).toBeInstanceOf(EventStreamErrorEvent) expect(error.data).toEqual({ code: 'BOOM' }) return true }) diff --git a/packages/peer/src/codec.ts b/packages/peer/src/codec.ts index 22b1d20..7cd4bf0 100644 --- a/packages/peer/src/codec.ts +++ b/packages/peer/src/codec.ts @@ -19,7 +19,7 @@ export interface EncodePeerMessageOptions { * Optional string prepended to the encoded message. * Used to distinguish messages when multiple protocols share the same peer. */ - prefix?: string + prefix?: string | undefined } /** @@ -66,7 +66,7 @@ export interface DecodePeerMessageOptions { * If present and the message does not start with this prefix, * the decoder returns `matched: false`. */ - prefix?: string + prefix?: string | undefined } /** diff --git a/packages/peer/src/event-stream.test.ts b/packages/peer/src/event-stream.test.ts index abec775..4059dc9 100644 --- a/packages/peer/src/event-stream.test.ts +++ b/packages/peer/src/event-stream.test.ts @@ -1,4 +1,4 @@ -import { ErrorEvent, unwrapEvent, withEventMeta } from '@standard-server/core' +import { EventStreamErrorEvent, unwrapEvent, withEventMeta } from '@standard-server/core' import { AsyncIteratorClass, Queue, sleep } from '@standard-server/shared' import { EventStreamTransmitter, toAsyncIteratorObject } from './event-stream.ts' @@ -57,7 +57,7 @@ describe('toAsyncIteratorObject', () => { expect(r1.value).toBe('primitive') }) - it('throws ErrorEvent on error events', async () => { + it('throws EventStreamErrorEvent on error events', async () => { const queue = new Queue() const cleanup = vi.fn() const iter = toAsyncIteratorObject(queue, cleanup) @@ -68,8 +68,8 @@ describe('toAsyncIteratorObject', () => { json: { event: 'error', data: { code: 500 }, comments: ['oops'] }, }) - const assertError = (err: ErrorEvent) => { - expect(err).toBeInstanceOf(ErrorEvent) + const assertError = (err: EventStreamErrorEvent) => { + expect(err).toBeInstanceOf(EventStreamErrorEvent) expect(err.data).toEqual({ code: 500 }) const [, meta] = unwrapEvent(err) expect(meta).toEqual({ comments: ['oops'] }) @@ -191,11 +191,11 @@ describe('eventStreamTransmitter', () => { }) }) - it('sends error event for ErrorEvent', async () => { + it('sends error event for EventStreamErrorEvent', async () => { const send = vi.fn(async () => {}) const iter = new AsyncIteratorClass( async () => { - throw new ErrorEvent({ reason: 'fail' }) + throw new EventStreamErrorEvent({ reason: 'fail' }) }, async () => {}, ) @@ -261,14 +261,17 @@ describe('eventStreamTransmitter', () => { const cleanup = vi.fn() const iter = new AsyncIteratorClass(async () => { - throw new ErrorEvent({ hi: true }) + throw new EventStreamErrorEvent({ hi: true }) }, cleanup) const transmitter = new EventStreamTransmitter(iter, 'msg-1', send) await expect(transmitter.transmit()).rejects.toThrow('send failed') expect(cleanup).toHaveBeenCalledTimes(1) - expect(cleanup).toHaveBeenCalledWith({ kind: 'error', error: expect.any(ErrorEvent) }) + expect(cleanup).toHaveBeenCalledWith({ + kind: 'error', + error: expect.any(EventStreamErrorEvent), + }) }) it('does not send success-event after cancel', async () => { @@ -300,7 +303,7 @@ describe('eventStreamTransmitter', () => { async () => { // simulate slow iterator so cancel happens mid-stream await sleep(50) - throw new ErrorEvent({ reason: 'fail' }) + throw new EventStreamErrorEvent({ reason: 'fail' }) }, async () => {}, ) diff --git a/packages/peer/src/event-stream.ts b/packages/peer/src/event-stream.ts index 760a3fd..bf80796 100644 --- a/packages/peer/src/event-stream.ts +++ b/packages/peer/src/event-stream.ts @@ -1,6 +1,6 @@ -import { ErrorEvent, unwrapEvent, withEventMeta } from '@standard-server/core' +import { EventStreamErrorEvent, unwrapEvent, withEventMeta } from '@standard-server/core' import type { AsyncCleanupFn, Queue } from '@standard-server/shared' -import { AsyncIteratorClass, isTypescriptObject } from '@standard-server/shared' +import { AsyncIteratorClass, isNonPrimitive } from '@standard-server/shared' import type { PeerEventStreamMessage } from './types.ts' @@ -17,7 +17,7 @@ export function toAsyncIteratorObject( case 'message': { let data = json.data - if (isTypescriptObject(data)) { + if (isNonPrimitive(data)) { data = withEventMeta(data, json) } @@ -26,13 +26,13 @@ export function toAsyncIteratorObject( case 'error': { // Error events are surfaced by throwing a special error type - throw withEventMeta(new ErrorEvent(json.data), json) + throw withEventMeta(new EventStreamErrorEvent(json.data), json) } case 'close': { let data = json.data - if (isTypescriptObject(data)) { + if (isNonPrimitive(data)) { data = withEventMeta(data, json) } @@ -88,8 +88,8 @@ export class EventStreamTransmitter { return } } catch (error) { - // ErrorEvent is part of event-stream protocol - if (error instanceof ErrorEvent) { + // EventStreamErrorEvent is part of event-stream protocol + if (error instanceof EventStreamErrorEvent) { if (!this.isDone) { this.isDone = true diff --git a/packages/peer/src/validators.ts b/packages/peer/src/validators.ts index 57ba735..7547981 100644 --- a/packages/peer/src/validators.ts +++ b/packages/peer/src/validators.ts @@ -7,7 +7,7 @@ import { isStandardStatus, isStandardUrl, } from '@standard-server/core' -import { isTypescriptObject } from '@standard-server/shared' +import { isNonPrimitive } from '@standard-server/shared' import type { ClientPeerSendMessage, @@ -22,7 +22,7 @@ import type { } from './types.ts' export function isPeerMessage(maybe: unknown): maybe is PeerMessage { - if (!isTypescriptObject(maybe)) { + if (!isNonPrimitive(maybe)) { return false } @@ -50,7 +50,7 @@ export function isPeerRequestMessage(maybe: PeerMessage): maybe is PeerRequestMe return false } - if (!isTypescriptObject(maybe.json)) { + if (!isNonPrimitive(maybe.json)) { return false } @@ -70,7 +70,7 @@ export function isPeerResponseMessage(maybe: PeerMessage): maybe is PeerResponse return false } - if (!isTypescriptObject(maybe.json)) { + if (!isNonPrimitive(maybe.json)) { return false } @@ -94,7 +94,7 @@ export function isPeerEventStreamMessage(maybe: PeerMessage): maybe is PeerEvent return false } - if (!isTypescriptObject(maybe.json)) { + if (!isNonPrimitive(maybe.json)) { return false } @@ -128,7 +128,7 @@ export function isPeerEventStreamMessage(maybe: PeerMessage): maybe is PeerEvent export function isPeerOctetStreamMessage(maybe: PeerMessage): maybe is PeerOctetStreamMessage { return ( maybe.kind === 'octet-stream' && - isTypescriptObject(maybe.json) && + isNonPrimitive(maybe.json) && (typeof maybe.json.close === 'undefined' || typeof maybe.json.close === 'boolean') ) } diff --git a/packages/peer/tests/peer.test.ts b/packages/peer/tests/peer.test.ts index 23f0927..390c9f3 100644 --- a/packages/peer/tests/peer.test.ts +++ b/packages/peer/tests/peer.test.ts @@ -1,7 +1,12 @@ import { getEventListeners } from 'node:events' import type { StandardLazyRequest, StandardResponse } from '@standard-server/core' -import { cancelStandardBody, ErrorEvent, getEventMeta, withEventMeta } from '@standard-server/core' +import { + cancelStandardBody, + EventStreamErrorEvent, + getEventMeta, + withEventMeta, +} from '@standard-server/core' import { promiseWithResolvers, sleep } from '@standard-server/shared' import type { ClientPeer, ServerPeer } from '../src/index.ts' @@ -206,13 +211,13 @@ describe('peer integration (client <-> server over encoded wire)', () => { expect(last.value).toEqual({ total: 2 }) }) - it('propagates ErrorEvent from server stream to client iterator', async () => { + it('propagates EventStreamErrorEvent from server stream to client iterator', async () => { const { client } = connect(async () => ({ status: 200, headers: {}, body: (async function* () { yield 'ok' - throw new ErrorEvent({ code: 'BOOM' }) + throw new EventStreamErrorEvent({ code: 'BOOM' }) })(), })) @@ -220,8 +225,8 @@ describe('peer integration (client <-> server over encoded wire)', () => { const iterator = (await response.resolveBody()) as AsyncIterator await expect(iterator.next()).resolves.toEqual({ done: false, value: 'ok' }) - await expect(iterator.next()).rejects.toSatisfy((error: ErrorEvent) => { - expect(error).toBeInstanceOf(ErrorEvent) + await expect(iterator.next()).rejects.toSatisfy((error: EventStreamErrorEvent) => { + expect(error).toBeInstanceOf(EventStreamErrorEvent) expect(error.data).toEqual({ code: 'BOOM' }) return true }) diff --git a/packages/shared/src/object.test.ts b/packages/shared/src/object.test.ts index 50d3622..08a8cca 100644 --- a/packages/shared/src/object.test.ts +++ b/packages/shared/src/object.test.ts @@ -1,4 +1,4 @@ -import { hasAnyDefinedValue, isTypescriptObject } from './object.ts' +import { hasAnyDefinedValue, isNonPrimitive } from './object.ts' it('hasAnyDefinedValue', () => { expect(hasAnyDefinedValue({})).toBe(false) @@ -10,13 +10,13 @@ it('hasAnyDefinedValue', () => { expect(hasAnyDefinedValue({ 'set-cookie': ['a=1'] })).toBe(true) }) -it('isTypescriptObject', () => { - expect(isTypescriptObject(null)).toBe(false) - expect(isTypescriptObject(undefined)).toBe(false) - expect(isTypescriptObject(123)).toBe(false) - expect(isTypescriptObject('string')).toBe(false) +it('isNonPrimitive', () => { + expect(isNonPrimitive(null)).toBe(false) + expect(isNonPrimitive(undefined)).toBe(false) + expect(isNonPrimitive(123)).toBe(false) + expect(isNonPrimitive('string')).toBe(false) - expect(isTypescriptObject({})).toBe(true) - expect(isTypescriptObject(new Date())).toBe(true) - expect(isTypescriptObject(() => {})).toBe(true) + expect(isNonPrimitive({})).toBe(true) + expect(isNonPrimitive(new Date())).toBe(true) + expect(isNonPrimitive(() => {})).toBe(true) }) diff --git a/packages/shared/src/object.ts b/packages/shared/src/object.ts index 30a3635..0653983 100644 --- a/packages/shared/src/object.ts +++ b/packages/shared/src/object.ts @@ -14,9 +14,9 @@ export function hasAnyDefinedValue(object: Record): boolean { } /** - * Checks whether the provided container is a typescript object (object or function). + * Checks whether the value is non-primitive: a non-null object or a function. */ -export function isTypescriptObject( +export function isNonPrimitive( maybeObject: unknown, ): maybeObject is object & Record { if (!maybeObject) { diff --git a/packages/shared/src/proxy.ts b/packages/shared/src/proxy.ts index a57b408..ac50a8f 100644 --- a/packages/shared/src/proxy.ts +++ b/packages/shared/src/proxy.ts @@ -6,7 +6,7 @@ export interface GetOrBindOptions { * * @default true */ - bind?: boolean + bind?: boolean | undefined } export function getOrBind( diff --git a/tests/bun/tests/data-transfer.test.ts b/tests/bun/tests/data-transfer.test.ts index 3d337d9..b91285a 100644 --- a/tests/bun/tests/data-transfer.test.ts +++ b/tests/bun/tests/data-transfer.test.ts @@ -1,7 +1,7 @@ import { afterAll, beforeEach, describe, expect, it } from 'bun:test' import type { StandardHeaders } from '@standard-server/core' -import { ErrorEvent, unwrapEvent, withEventMeta } from '@standard-server/core' +import { EventStreamErrorEvent, unwrapEvent, withEventMeta } from '@standard-server/core' import { isAsyncIteratorObject, sleep } from '@standard-server/shared' import { createBunFetchClientServerTest } from './client-server.bun-fetch.ts' @@ -309,7 +309,7 @@ for (const [adapter, createClientServer] of ADAPTERS) { yield 'order1' await sleep(CHUNK_DELAY) yield withEventMeta({ order: 2 }, { id: 'id-2' }) - throw withEventMeta(new ErrorEvent({ order: 3 }), { comments: ['order3'] }) + throw withEventMeta(new EventStreamErrorEvent({ order: 3 }), { comments: ['order3'] }) })(), }) @@ -340,8 +340,8 @@ for (const [adapter, createClientServer] of ADAPTERS) { error = e } - expect(error).toBeInstanceOf(ErrorEvent) - const [err, errMeta] = unwrapEvent(error as ErrorEvent) + expect(error).toBeInstanceOf(EventStreamErrorEvent) + const [err, errMeta] = unwrapEvent(error as EventStreamErrorEvent) expect(err.data).toEqual({ order: 3 }) expect(errMeta).toEqual({ comments: ['order3'] }) expect(Date.now() - start).toBeLessThan(PARALLEL_THRESHOLD) diff --git a/tests/bun/tests/event-source.test.ts b/tests/bun/tests/event-source.test.ts index f54eea4..79d6e60 100644 --- a/tests/bun/tests/event-source.test.ts +++ b/tests/bun/tests/event-source.test.ts @@ -1,6 +1,6 @@ import { afterAll, describe, expect, it } from 'bun:test' -import { ErrorEvent } from '@standard-server/core' +import { EventStreamErrorEvent } from '@standard-server/core' import { toFetchResponse } from '@standard-server/fetch' let createBody: () => AsyncGenerator @@ -58,7 +58,7 @@ describe.skipIf(typeof EventSource === 'undefined')('eventSource', () => { it('receives message and error events', async () => { createBody = async function* () { yield 'hello' - throw new ErrorEvent({ code: 'BOOM' }) + throw new EventStreamErrorEvent({ code: 'BOOM' }) } expect(await receive()).toEqual([ diff --git a/tests/data-transfer.test.ts b/tests/data-transfer.test.ts index ca516ee..5fc6326 100644 --- a/tests/data-transfer.test.ts +++ b/tests/data-transfer.test.ts @@ -1,4 +1,4 @@ -import { ErrorEvent, unwrapEvent, withEventMeta } from '@standard-server/core' +import { EventStreamErrorEvent, unwrapEvent, withEventMeta } from '@standard-server/core' import { toFetchHeaders } from '@standard-server/fetch' import { isAsyncIteratorObject, sleep } from '@standard-server/shared' @@ -366,7 +366,7 @@ describe.each([ yield 'order1' await sleep(CHUNK_DELAY) yield withEventMeta({ order: 2 }, { id: 'id-2' }) - throw withEventMeta(new ErrorEvent({ order: 3 }), { comments: ['order3'] }) + throw withEventMeta(new EventStreamErrorEvent({ order: 3 }), { comments: ['order3'] }) })(), }) @@ -394,8 +394,8 @@ describe.each([ expect(Date.now() - start).toBeLessThan(PARALLEL_THRESHOLD) start = Date.now() - await expect(body.next()).rejects.toSatisfy((error: ErrorEvent) => { - expect(error).toBeInstanceOf(ErrorEvent) + await expect(body.next()).rejects.toSatisfy((error: EventStreamErrorEvent) => { + expect(error).toBeInstanceOf(EventStreamErrorEvent) const [err, errMeta] = unwrapEvent(error) expect(err.data).toEqual({ order: 3 }) expect(errMeta).toEqual({ comments: ['order3'] }) diff --git a/tests/event-source.test.ts b/tests/event-source.test.ts index 62a33eb..c75e581 100644 --- a/tests/event-source.test.ts +++ b/tests/event-source.test.ts @@ -1,7 +1,7 @@ import * as http from 'node:http' import type { AddressInfo } from 'node:net' -import { ErrorEvent } from '@standard-server/core' +import { EventStreamErrorEvent } from '@standard-server/core' import { sendStandardResponse } from '@standard-server/node' let createBody: () => AsyncGenerator @@ -60,7 +60,7 @@ describe.skipIf(typeof EventSource === 'undefined')('eventSource', () => { it('receives message and error events', async () => { createBody = async function* () { yield 'hello' - throw new ErrorEvent({ code: 'BOOM' }) + throw new EventStreamErrorEvent({ code: 'BOOM' }) } expect(await receive()).toEqual([