Skip to content

Commit

Permalink
feat(types): improve streaming payload types
Browse files Browse the repository at this point in the history
  • Loading branch information
kuhe committed Jul 20, 2023
1 parent 10ed5f2 commit a4c9552
Show file tree
Hide file tree
Showing 16 changed files with 583 additions and 20 deletions.
5 changes: 5 additions & 0 deletions .changeset/loud-students-attend.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@smithy/types": minor
---

improve streaming payload typings
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ smithy-typescript-integ-tests/yarn.lock

# Issue https://github.com/awslabs/smithy-typescript/issues/425
smithy-typescript-codegen/bin/
smithy-typescript-ssdk-codegen-test-utils/bin/

**/node_modules/
**/*.tsbuildinfo
Expand Down
35 changes: 35 additions & 0 deletions packages/types/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,38 @@

[![NPM version](https://img.shields.io/npm/v/@smithy/types/latest.svg)](https://www.npmjs.com/package/@smithy/types)
[![NPM downloads](https://img.shields.io/npm/dm/@smithy/types.svg)](https://www.npmjs.com/package/@smithy/types)

## Usage

This package is mostly used internally by generated clients.
Some public components have independent applications.

### Scenario: Narrowing a smithy-typescript generated client's output payload blob types

----

This is mostly relevant to operations with streaming bodies such as within
the S3Client in the AWS SDK for JavaScript v3.

Because blob payload types are platform dependent, you may wish to indicate in your application that a client is running in a specific
environment. This narrows the blob payload types.

```typescript
import { GetObjectCommand, S3Client } from "@aws-sdk/client-s3";
import type { NodeJsClient, SdkStream, StreamingBlobPayloadOutputTypes } from "@smithy/types";
import type { IncomingMessage } from "node:http";

// default client init.
const s3Default = new S3Client({});

// client init with type narrowing.
const s3NarrowType = new S3Client({}) as NodeJsClient<S3Client>;

// The default type of blob payloads is a wide union type including multiple possible
// request handlers.
const body1: StreamingBlobPayloadOutputTypes = (await s3Default.send(new GetObjectCommand({ Key: "", Bucket: "" }))).Body!;

// This is of the narrower type SdkStream<IncomingMessage> representing
// blob payload responses using specifically the node:http request handler.
const body2: SdkStream<IncomingMessage> = (await s3NarrowType.send(new GetObjectCommand({ Key: "", Bucket: "" }))).Body!;
```
48 changes: 48 additions & 0 deletions packages/types/src/blob/blob-payload-input-types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { Readable } from "stream";

/**
* @public
*
* A union of types that can be used as inputs for the service model
* "blob" type when it represents the request's entire payload or body.
*
* For example, in Lambda::invoke, the payload is modeled as a blob type
* and this union applies to it.
* In contrast, in Lambda::createFunction the Zip file option is a blob type,
* but is not the (entire) payload and this union does not apply.
*
* Note: not all types are signable by the standard SignatureV4 signer when
* used as the request body. For example, in Node.js a Readable stream
* is not signable by the default signer.
* They are included in the union because it may work in some cases,
* but the expected types are primarily string and Uint8Array.
*
* Additional details may be found in the internal
* function "getPayloadHash" in the SignatureV4 module.
*/
export type BlobPayloadInputTypes =
| string
| ArrayBuffer
| ArrayBufferView
| Uint8Array
| NodeJsRuntimeBlobTypes
| BrowserRuntimeBlobTypes;

/**
* @public
*
* Additional blob types for the Node.js environment.
*/
export type NodeJsRuntimeBlobTypes = Readable | Buffer;

/**
* @public
*
* Additional blob types for the browser environment.
*/
export type BrowserRuntimeBlobTypes = Blob | ReadableStream;

/**
* @deprecated renamed to BlobPayloadInputTypes.
*/
export type BlobTypes = BlobPayloadInputTypes;
33 changes: 29 additions & 4 deletions packages/types/src/client.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Command } from "./command";
import { HttpHandlerOptions } from "./http";
import { MiddlewareStack } from "./middleware";
import { MetadataBearer } from "./response";

Expand All @@ -7,19 +8,43 @@ import { MetadataBearer } from "./response";
*
* function definition for different overrides of client's 'send' function.
*/
interface InvokeFunction<InputTypes extends object, OutputTypes extends MetadataBearer, ResolvedClientConfiguration> {
export interface InvokeFunction<
InputTypes extends object,
OutputTypes extends MetadataBearer,
ResolvedClientConfiguration
> {
<InputType extends InputTypes, OutputType extends OutputTypes>(
command: Command<InputTypes, InputType, OutputTypes, OutputType, ResolvedClientConfiguration>,
options?: any
options?: HttpHandlerOptions
): Promise<OutputType>;
<InputType extends InputTypes, OutputType extends OutputTypes>(
command: Command<InputTypes, InputType, OutputTypes, OutputType, ResolvedClientConfiguration>,
options: any,
cb: (err: any, data?: OutputType) => void
): void;
<InputType extends InputTypes, OutputType extends OutputTypes>(
command: Command<InputTypes, InputType, OutputTypes, OutputType, ResolvedClientConfiguration>,
options?: any,
options: HttpHandlerOptions,
cb: (err: any, data?: OutputType) => void
): void;
<InputType extends InputTypes, OutputType extends OutputTypes>(
command: Command<InputTypes, InputType, OutputTypes, OutputType, ResolvedClientConfiguration>,
options?: HttpHandlerOptions,
cb?: (err: any, data?: OutputType) => void
): Promise<OutputType> | void;
}

/**
* @internal
*
* Signature that appears on aggregated clients' methods.
*/
export interface InvokeMethod<InputType extends object, OutputType extends MetadataBearer> {
(input: InputType, options?: HttpHandlerOptions): Promise<OutputType>;
(input: InputType, cb: (err: any, data?: OutputType) => void): void;
(input: InputType, options: HttpHandlerOptions, cb: (err: any, data?: OutputType) => void): void;
(
input: InputType,
options?: HttpHandlerOptions,
cb?: (err: any, data?: OutputType) => void
): Promise<OutputType> | void;
}
Expand Down
6 changes: 6 additions & 0 deletions packages/types/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export * from "./abort";
export * from "./auth";
export * from "./blob/blob-payload-input-types";
export * from "./checksum";
export * from "./client";
export * from "./command";
Expand All @@ -21,7 +22,12 @@ export * from "./serde";
export * from "./shapes";
export * from "./signature";
export * from "./stream";
export * from "./streaming-payload/streaming-blob-common-types";
export * from "./streaming-payload/streaming-blob-payload-input-types";
export * from "./streaming-payload/streaming-blob-payload-output-types";
export * from "./transfer";
export * from "./transform/client-payload-blob-type-narrow";
export * from "./transform/type-transform";
export * from "./uri";
export * from "./util";
export * from "./waiter";
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import type { Readable } from "stream";

/**
* @public
*
* This is the union representing the modeled blob type with streaming trait
* in a generic format that does not relate to HTTP input or output payloads.
*
* Note: the non-streaming blob type is represented by Uint8Array, but because
* the streaming blob type is always in the request/response paylod, it has
* historically been handled with different types.
*
* @see https://smithy.io/2.0/spec/simple-types.html#blob
*
* For compatibility with its historical representation, it must contain at least
* Readble (Node.js), Blob (browser), and ReadableStream (browser).
*
* @see StreamingPayloadInputTypes for FAQ about mixing types from multiple environments.
*/
export type StreamingBlobTypes = NodeJsRuntimeStreamingBlobTypes | BrowserRuntimeStreamingBlobTypes;

/**
* @public
*
* Node.js streaming blob type.
*/
export type NodeJsRuntimeStreamingBlobTypes = Readable;

/**
* @public
*
* Browser streaming blob types.
*/
export type BrowserRuntimeStreamingBlobTypes = ReadableStream | Blob;
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import type { Readable } from "stream";

/**
* @public
*
* This union represents a superset of the compatible types you
* can use for streaming payload inputs.
*
* FAQ:
* Why does the type union mix mutually exclusive runtime types, namely
* Node.js and browser types?
*
* There are several reasons:
* 1. For backwards compatibility.
* 2. As a convenient compromise solution so that users in either environment may use the types
* without customization. If any types are to be excluded, you can opt to
* declare a global merged interface.
*
* @example
* ```typescript
* declare global {
* export interface ReadableStream { doNotUse: true }
* export interface Blob { doNotUse: true }
* }
* ```
*
* 3. The SDK does not have static type information about the exact implementation
* of the HTTP RequestHandler being used in your client(s) (e.g. fetch, XHR, node:http, or node:http2),
* given that it is chosen at runtime. There are multiple possible request handlers
* in both the Node.js and browser runtime environments.
*
* Rather than restricting the type to a known common format (Uint8Array, for example)
* which doesn't include a universal streaming format in the currently supported Node.js versions,
* the type declaration is widened to multiple possible formats.
* It is up to the user to ultimately select a compatible format with the
* runtime and HTTP handler implementation they are using.
*
*/
export type StreamingBlobPayloadInputTypes =
| NodeJsRuntimeStreamingBlobPayloadInputTypes
| BrowserRuntimeStreamingBlobPayloadInputTypes;

/**
* @public
*
* Streaming payload input types in the Node.js environment.
* These are derived from the types compatible with the request body used by node:http.
*
* Note: not all types are signable by the standard SignatureV4 signer when
* used as the request body. For example, in Node.js a Readable stream
* is not signable by the default signer.
* They are included in the union because it may be intended in some cases,
* but the expected types are primarily string, Uint8Array, and Buffer.
*
* Additional details may be found in the internal
* function "getPayloadHash" in the SignatureV4 module.
*/
export type NodeJsRuntimeStreamingBlobPayloadInputTypes = string | Uint8Array | Buffer | Readable;

/**
* @public
*
* Streaming payload input types in the browser environment.
* These are derived from the types compatible with fetch's Request.body.
*/
export type BrowserRuntimeStreamingBlobPayloadInputTypes = string | Uint8Array | ReadableStream | Blob;
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import type { IncomingMessage } from "http";
import type { Readable } from "stream";

import type { SdkStream } from "../serde";

/**
* @public
*
* This union represents a superset of the types you may receive
* in streaming payload outputs.
*
* @see StreamingPayloadInputTypes for FAQ about mixing types from multiple environments.
*/
export type StreamingBlobPayloadOutputTypes =
| NodeJsRuntimeStreamingBlobPayloadOutputTypes
| BrowserRuntimeStreamingBlobPayloadOutputTypes;

/**
* @public
*
* Streaming payload output types in the Node.js environment.
*
* This is by default the IncomingMessage type from node:http responses when
* using the default node-http-handler in Node.js environments.
*
* It can be other Readable types like node:http2's ClientHttp2Stream
* such as when using the node-http2-handler.
*
* The SdkStreamMixin adds methods on this type to help transform (collect) it to
* other formats.
*/
export type NodeJsRuntimeStreamingBlobPayloadOutputTypes = SdkStream<IncomingMessage | Readable>;

/**
* @public
*
* Streaming payload output types in the browser environment.
*
* This is by default fetch's Response.body type (ReadableStream) when using
* the default fetch-http-handler in browser-like environments.
*
* It may be a Blob, such as when using the XMLHttpRequest handler
* and receiving an arraybuffer response body.
*
* The SdkStreamMixin adds methods on this type to help transform (collect) it to
* other formats.
*/
export type BrowserRuntimeStreamingBlobPayloadOutputTypes = SdkStream<ReadableStream | Blob>;
59 changes: 59 additions & 0 deletions packages/types/src/transform/client-method-transforms.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import type { Command } from "../command";
import type { HttpHandlerOptions } from "../http";
import type { MetadataBearer } from "../response";
import type { StreamingBlobPayloadOutputTypes } from "../streaming-payload/streaming-blob-payload-output-types";
import type { Transform } from "./type-transform";

/**
* @internal
*
* Narrowed version of InvokeFunction used in Client::send.
*/
export interface NarrowedInvokeFunction<
NarrowType,
InputTypes extends object,
OutputTypes extends MetadataBearer,
ResolvedClientConfiguration
> {
<InputType extends InputTypes, OutputType extends OutputTypes>(
command: Command<InputTypes, InputType, OutputTypes, OutputType, ResolvedClientConfiguration>,
options?: HttpHandlerOptions
): Promise<Transform<OutputType, StreamingBlobPayloadOutputTypes | undefined, NarrowType>>;
<InputType extends InputTypes, OutputType extends OutputTypes>(
command: Command<InputTypes, InputType, OutputTypes, OutputType, ResolvedClientConfiguration>,
cb: (err: unknown, data?: Transform<OutputType, StreamingBlobPayloadOutputTypes | undefined, NarrowType>) => void
): void;
<InputType extends InputTypes, OutputType extends OutputTypes>(
command: Command<InputTypes, InputType, OutputTypes, OutputType, ResolvedClientConfiguration>,
options: HttpHandlerOptions,
cb: (err: unknown, data?: Transform<OutputType, StreamingBlobPayloadOutputTypes | undefined, NarrowType>) => void
): void;
<InputType extends InputTypes, OutputType extends OutputTypes>(
command: Command<InputTypes, InputType, OutputTypes, OutputType, ResolvedClientConfiguration>,
options?: HttpHandlerOptions,
cb?: (err: unknown, data?: Transform<OutputType, StreamingBlobPayloadOutputTypes | undefined, NarrowType>) => void
): Promise<Transform<OutputType, StreamingBlobPayloadOutputTypes | undefined, NarrowType>> | void;
}

/**
* @internal
*
* Narrowed version of InvokeMethod used in aggregated Client methods.
*/
export interface NarrowedInvokeMethod<NarrowType, InputType extends object, OutputType extends MetadataBearer> {
(input: InputType, options?: HttpHandlerOptions): Promise<
Transform<OutputType, StreamingBlobPayloadOutputTypes | undefined, NarrowType>
>;
(
input: InputType,
cb: (err: unknown, data?: Transform<OutputType, StreamingBlobPayloadOutputTypes | undefined, NarrowType>) => void
): void;
(
input: InputType,
options: HttpHandlerOptions,
cb: (err: unknown, data?: Transform<OutputType, StreamingBlobPayloadOutputTypes | undefined, NarrowType>) => void
): void;
(input: InputType, options?: HttpHandlerOptions, cb?: (err: unknown, data?: OutputType) => void): Promise<
Transform<OutputType, StreamingBlobPayloadOutputTypes | undefined, NarrowType>
> | void;
}
Loading

0 comments on commit a4c9552

Please sign in to comment.