diff --git a/packages/connect/src/protocol-connect/end-stream.ts b/packages/connect/src/protocol-connect/end-stream.ts index 7ad054df6..d6ec07e8d 100644 --- a/packages/connect/src/protocol-connect/end-stream.ts +++ b/packages/connect/src/protocol-connect/end-stream.ts @@ -34,7 +34,7 @@ export const endStreamFlag = 0b00000010; */ export interface EndStreamResponse { metadata: Headers; - error?: ConnectError; + error?: ConnectError | undefined; } /** diff --git a/packages/connect/src/protocol/async-iterable.ts b/packages/connect/src/protocol/async-iterable.ts index 40e19ea91..4d05056cf 100644 --- a/packages/connect/src/protocol/async-iterable.ts +++ b/packages/connect/src/protocol/async-iterable.ts @@ -1069,6 +1069,29 @@ interface Abortable { type AbortState = "rethrown" | "completed" | "caught"; +function assertHasThrow( + t: unknown, + message: string +): asserts t is { + throw: CallableFunction; +} { + if ( + typeof t === "object" && + t !== null && + "throw" in t && + typeof t.throw === "function" + ) { + return; + } + throw new Error(message); +} + +const hasReturn = (t: unknown): t is { return: CallableFunction } => + typeof t === "object" && + t !== null && + "return" in t && + typeof t.return === "function"; + /** * Wrap the given iterable and return an iterable with an abort() method. * @@ -1101,11 +1124,8 @@ type AbortState = "rethrown" | "completed" | "caught"; export function makeIterableAbortable( iterable: AsyncIterable ): AsyncIterable & Abortable { - const innerCandidate = iterable[Symbol.asyncIterator](); - if (innerCandidate.throw === undefined) { - throw new Error("AsyncIterable does not implement throw"); - } - const inner = innerCandidate as Required>; + const inner = iterable[Symbol.asyncIterator](); + assertHasThrow(inner, "AsyncIterable does not implement throw"); let aborted: { reason: unknown; state: Promise } | undefined; let resultPromise: Promise> | undefined; let it: AsyncIterator = { @@ -1119,7 +1139,7 @@ export function makeIterableAbortable( return inner.throw(e); }, }; - if (innerCandidate.return === undefined) { + if (hasReturn(inner)) { it = { ...it, return(value?: unknown): Promise> { diff --git a/packages/connect/src/protocol/compression.ts b/packages/connect/src/protocol/compression.ts index d4aefd188..9da0595c7 100644 --- a/packages/connect/src/protocol/compression.ts +++ b/packages/connect/src/protocol/compression.ts @@ -61,7 +61,7 @@ export function compressionNegotiate( ): { request: Compression | null; response: Compression | null; - error?: ConnectError; + error?: ConnectError | undefined; } { let request = null; let response = null; diff --git a/packages/connect/src/protocol/universal-handler.ts b/packages/connect/src/protocol/universal-handler.ts index c8bbe3c31..aa8bfadad 100644 --- a/packages/connect/src/protocol/universal-handler.ts +++ b/packages/connect/src/protocol/universal-handler.ts @@ -77,12 +77,12 @@ export interface UniversalHandlerOptions { /** * Options for the JSON format. */ - jsonOptions?: Partial; + jsonOptions?: Partial | undefined; /** * Options for the binary wire format. */ - binaryOptions?: Partial; + binaryOptions?: Partial | undefined; maxDeadlineDurationMs: number; // TODO TCN-785 shutdownSignal: AbortSignal; // TODO TCN-919 diff --git a/packages/example/tsconfig.json b/packages/example/tsconfig.json index f6c1c2f6a..daefc1c50 100644 --- a/packages/example/tsconfig.json +++ b/packages/example/tsconfig.json @@ -6,6 +6,7 @@ "lib": ["ESNext", "DOM"], "moduleResolution": "Node", "strict": true, + "exactOptionalPropertyTypes": true, "sourceMap": true, "resolveJsonModule": true, "isolatedModules": true, diff --git a/tsconfig.base.json b/tsconfig.base.json index 405093060..0edc686c8 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -17,6 +17,7 @@ "strictFunctionTypes": true, "strictBindCallApply": true, "strictPropertyInitialization": true, + "exactOptionalPropertyTypes": true, "noImplicitThis": true, "useUnknownInCatchVariables": true, "noUnusedLocals": true,