@bufbuild/connect-web
Advanced tools
Comparing version 0.0.9 to 0.0.10
@@ -50,3 +50,3 @@ "use strict"; | ||
}, (reason) => { | ||
const err = (0, connect_error_js_1.connectErrorFromReason)(reason); | ||
const err = (0, connect_error_js_1.connectErrorFromReason)(reason, code_js_1.Code.Internal); | ||
if (err.code === code_js_1.Code.Canceled && abort.signal.aborted) { | ||
@@ -78,8 +78,11 @@ // As documented, discard Canceled errors if canceled by the user. | ||
run().catch((reason) => { | ||
const err = (0, connect_error_js_1.connectErrorFromReason)(reason); | ||
const err = (0, connect_error_js_1.connectErrorFromReason)(reason, code_js_1.Code.Internal); | ||
if (err.code === code_js_1.Code.Canceled && abort.signal.aborted) { | ||
// As documented, discard Canceled errors if canceled by the user. | ||
return; | ||
// As documented, discard Canceled errors if canceled by the user, | ||
// but do invoke the close-callback. | ||
onClose(undefined); | ||
} | ||
onClose(err); | ||
else { | ||
onClose(err); | ||
} | ||
}); | ||
@@ -92,4 +95,7 @@ return () => abort.abort(); | ||
options.signal.addEventListener("abort", () => abort.abort()); | ||
if (options.signal.aborted) { | ||
abort.abort(); | ||
} | ||
} | ||
return Object.assign(Object.assign({}, options), { signal: abort.signal }); | ||
} |
@@ -16,3 +16,3 @@ "use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.newParseError = exports.connectErrorFromReason = exports.connectErrorFromJson = exports.ConnectError = void 0; | ||
exports.newParseError = exports.connectErrorFromReason = exports.connectErrorFromJson = exports.connectErrorDetails = exports.ConnectError = void 0; | ||
const code_js_1 = require("./code.js"); | ||
@@ -27,11 +27,12 @@ const protobuf_1 = require("@bufbuild/protobuf"); | ||
* Because developer tools typically show just the error message, we prefix | ||
* it with the status code. | ||
* it with the status code, so that the most important information is always | ||
* visible immediately. | ||
* | ||
* Error details are wrapped with google.protobuf.Any on the wire, so that | ||
* a server or middleware can attach arbitrary data to an error. We | ||
* automatically unwrap the details for you. | ||
* a server or middleware can attach arbitrary data to an error. Use the | ||
* function connectErrorDetails() to retrieve the details. | ||
*/ | ||
class ConnectError extends Error { | ||
constructor(message, code = code_js_1.Code.Unknown, details, metadata) { | ||
super(syntheticMessage(code, message)); | ||
super(createMessage(message, code)); | ||
this.name = "ConnectError"; | ||
@@ -42,3 +43,2 @@ // see https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-2.html#example | ||
this.code = code; | ||
this.details = details !== null && details !== void 0 ? details : []; | ||
this.metadata = new Headers(metadata); | ||
@@ -49,3 +49,29 @@ this.rawDetails = []; | ||
exports.ConnectError = ConnectError; | ||
function syntheticMessage(code, message) { | ||
function connectErrorDetails(error, typeOrRegistry, ...moreTypes) { | ||
const typeRegistry = "typeName" in typeOrRegistry | ||
? protobuf_2.TypeRegistry.from(typeOrRegistry, ...moreTypes) | ||
: typeOrRegistry; | ||
const details = []; | ||
for (const raw of error.rawDetails) { | ||
try { | ||
const any = "@type" in raw ? protobuf_2.Any.fromJson(raw, { typeRegistry }) : raw; | ||
const name = any.typeUrl.substring(any.typeUrl.lastIndexOf("/") + 1); | ||
const type = typeRegistry.findMessage(name); | ||
if (type) { | ||
const message = new type(); | ||
any.unpackTo(message); | ||
details.push(message); | ||
} | ||
} | ||
catch (_) { | ||
// | ||
} | ||
} | ||
return details; | ||
} | ||
exports.connectErrorDetails = connectErrorDetails; | ||
/** | ||
* Create an error message, prefixing the given code. | ||
*/ | ||
function createMessage(message, code) { | ||
return message.length | ||
@@ -56,6 +82,6 @@ ? `[${(0, code_js_1.codeToString)(code)}] ${message}` | ||
/** | ||
* Parse an error from a JSON value. | ||
* Parse a Connect error from a JSON value. | ||
* Will return a ConnectError, but throw one in case the JSON is malformed. | ||
*/ | ||
function connectErrorFromJson(jsonValue, options) { | ||
function connectErrorFromJson(jsonValue, metadata) { | ||
if (typeof jsonValue !== "object" || | ||
@@ -76,30 +102,5 @@ jsonValue == null || | ||
} | ||
const error = new ConnectError(message !== null && message !== void 0 ? message : "", code, undefined, options === null || options === void 0 ? void 0 : options.metadata); | ||
const error = new ConnectError(message !== null && message !== void 0 ? message : "", code, undefined, metadata); | ||
if ("details" in jsonValue && Array.isArray(jsonValue.details)) { | ||
for (const raw of jsonValue.details) { | ||
let any; | ||
try { | ||
any = protobuf_2.Any.fromJson(raw, options); | ||
} | ||
catch (e) { | ||
// We consider error details to be supplementary information. | ||
// Parsing error details must not hide elementary information | ||
// like code and message, so we deliberately ignore parsing | ||
// errors here. | ||
error.rawDetails.push(raw); | ||
continue; | ||
} | ||
const typeName = any.typeUrl.substring(any.typeUrl.lastIndexOf("/") + 1); | ||
if (!(options === null || options === void 0 ? void 0 : options.typeRegistry)) { | ||
error.rawDetails.push(raw); | ||
continue; | ||
} | ||
const messageType = options.typeRegistry.findMessage(typeName); | ||
if (messageType) { | ||
const message = new messageType(); | ||
if (any.unpackTo(message)) { | ||
error.details.push(message); | ||
} | ||
} | ||
} | ||
error.rawDetails.push(...jsonValue.details); | ||
} | ||
@@ -110,8 +111,12 @@ return error; | ||
/** | ||
* Convert any value - typically a caught error into a ConnectError. | ||
* If the value is already a ConnectError, return it as is. | ||
* If the value is an AbortError from the fetch API, return code Canceled. | ||
* For other values, return code Internal. | ||
* Convert any value - typically a caught error into a ConnectError, | ||
* following these rules: | ||
* - If the value is already a ConnectError, return it as is. | ||
* - If the value is an AbortError from the fetch API, return the message | ||
* of the AbortError with code Canceled. | ||
* - For other Errors, return the Errors message with code Unknown by default. | ||
* - For other values, return the values String representation as a message, | ||
* with the code Unknown by default. | ||
*/ | ||
function connectErrorFromReason(reason) { | ||
function connectErrorFromReason(reason, code = code_js_1.Code.Unknown) { | ||
if (reason instanceof ConnectError) { | ||
@@ -129,3 +134,3 @@ return reason; | ||
} | ||
return new ConnectError(String(reason), code_js_1.Code.Internal); | ||
return new ConnectError(String(reason), code); | ||
} | ||
@@ -132,0 +137,0 @@ exports.connectErrorFromReason = connectErrorFromReason; |
@@ -23,2 +23,7 @@ "use strict"; | ||
const http_headers_js_1 = require("./http-headers.js"); | ||
/** | ||
* Create a Transport for the Connect protocol, which makes unary and | ||
* server-streaming methods available to web browsers. It uses the fetch | ||
* API to make HTTP requests. | ||
*/ | ||
function createConnectTransport(options) { | ||
@@ -51,6 +56,3 @@ var _a; | ||
if (responseType == "application/json") { | ||
throw (0, connect_error_js_1.connectErrorFromJson)((await response.json()), { | ||
typeRegistry: options.errorDetailRegistry, | ||
metadata: (0, http_headers_js_1.mergeHeaders)(...demuxHeaderTrailers(response.headers)), | ||
}); | ||
throw (0, connect_error_js_1.connectErrorFromJson)((await response.json()), (0, http_headers_js_1.mergeHeaders)(...demuxHeaderTrailers(response.headers))); | ||
} | ||
@@ -74,3 +76,3 @@ throw new connect_error_js_1.ConnectError(`HTTP ${response.status} ${response.statusText}`, (0, code_js_1.codeFromConnectHttpStatus)(response.status)); | ||
catch (e) { | ||
throw (0, connect_error_js_1.connectErrorFromReason)(e); | ||
throw (0, connect_error_js_1.connectErrorFromReason)(e, code_js_1.Code.Internal); | ||
} | ||
@@ -101,6 +103,3 @@ }, | ||
if (responseType == "application/json") { | ||
throw (0, connect_error_js_1.connectErrorFromJson)((await response.json()), { | ||
typeRegistry: options.errorDetailRegistry, | ||
metadata: (0, http_headers_js_1.mergeHeaders)(...demuxHeaderTrailers(response.headers)), | ||
}); | ||
throw (0, connect_error_js_1.connectErrorFromJson)((await response.json()), (0, http_headers_js_1.mergeHeaders)(...demuxHeaderTrailers(response.headers))); | ||
} | ||
@@ -135,5 +134,3 @@ throw new connect_error_js_1.ConnectError(`HTTP ${response.status} ${response.statusText}`, (0, code_js_1.codeFromConnectHttpStatus)(response.status)); | ||
endStreamReceived = true; | ||
const endStream = EndStream.fromJsonString(new TextDecoder().decode(result.value.data), { | ||
typeRegistry: options.errorDetailRegistry, | ||
}); | ||
const endStream = endStreamFromJson(result.value.data); | ||
endStream.metadata.forEach((value, key) => this.trailer.append(key, value)); | ||
@@ -159,3 +156,3 @@ if (endStream.error) { | ||
catch (e) { | ||
throw (0, connect_error_js_1.connectErrorFromReason)(e); | ||
throw (0, connect_error_js_1.connectErrorFromReason)(e, code_js_1.Code.Internal); | ||
} | ||
@@ -166,2 +163,7 @@ }, | ||
exports.createConnectTransport = createConnectTransport; | ||
/** | ||
* Creates a body for a Connect request. Supports only requests with a single | ||
* message because of browser API limitations, but applies the enveloping | ||
* required for server-streaming requests. | ||
*/ | ||
function createConnectRequestBody(message, methodKind, useBinaryFormat, jsonOptions) { | ||
@@ -183,2 +185,5 @@ const encoded = useBinaryFormat | ||
} | ||
/** | ||
* Creates headers for a Connect request. | ||
*/ | ||
function createConnectRequestHeaders(headers, timeoutMs, methodKind, useBinaryFormat) { | ||
@@ -197,2 +202,6 @@ const result = new Headers(headers); | ||
} | ||
/** | ||
* Asserts a valid Connect Content-Type response header. Raises a ConnectError | ||
* otherwise. | ||
*/ | ||
function expectContentType(contentType, stream, binaryFormat) { | ||
@@ -226,69 +235,52 @@ const match = contentType === null || contentType === void 0 ? void 0 : contentType.match(/^application\/(connect\+)?(json|proto)$/); | ||
/** | ||
* Represents the EndStreamResponse of the Connect protocol. | ||
* Parse an EndStreamResponse of the Connect protocol. | ||
*/ | ||
class EndStream { | ||
constructor(metadata, error) { | ||
this.metadata = metadata; | ||
this.error = error; | ||
function endStreamFromJson(data) { | ||
let jsonValue; | ||
try { | ||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment | ||
jsonValue = JSON.parse(new TextDecoder().decode(data)); | ||
} | ||
/** | ||
* Parse an EndStreamResponse from a JSON string. | ||
* Will throw a ConnectError in case the JSON is malformed. | ||
*/ | ||
static fromJsonString(jsonString, options) { | ||
let json; | ||
try { | ||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment | ||
json = JSON.parse(jsonString); | ||
} | ||
catch (e) { | ||
throw (0, connect_error_js_1.newParseError)(e, "", false); | ||
} | ||
return this.fromJson(json, options); | ||
catch (e) { | ||
throw (0, connect_error_js_1.newParseError)(e, "", false); | ||
} | ||
/** | ||
* Parse an EndStreamResponse from a JSON value. | ||
* Will return a ConnectError, but throw one in case the JSON is malformed. | ||
*/ | ||
static fromJson(jsonValue, options) { | ||
if (typeof jsonValue != "object" || | ||
jsonValue == null || | ||
Array.isArray(jsonValue)) { | ||
throw (0, connect_error_js_1.newParseError)(jsonValue); | ||
if (typeof jsonValue != "object" || | ||
jsonValue == null || | ||
Array.isArray(jsonValue)) { | ||
throw (0, connect_error_js_1.newParseError)(jsonValue); | ||
} | ||
const metadata = new Headers(); | ||
if ("metadata" in jsonValue) { | ||
if (typeof jsonValue.metadata != "object" || | ||
jsonValue.metadata == null || | ||
Array.isArray(jsonValue.metadata)) { | ||
throw (0, connect_error_js_1.newParseError)(jsonValue, ".metadata"); | ||
} | ||
const metadata = new Headers(); | ||
if ("metadata" in jsonValue) { | ||
if (typeof jsonValue.metadata != "object" || | ||
jsonValue.metadata == null || | ||
Array.isArray(jsonValue.metadata)) { | ||
throw (0, connect_error_js_1.newParseError)(jsonValue, ".metadata"); | ||
for (const [key, values] of Object.entries(jsonValue.metadata)) { | ||
if (!Array.isArray(values) || | ||
values.some((value) => typeof value != "string")) { | ||
throw (0, connect_error_js_1.newParseError)(values, `.metadata["${key}"]`); | ||
} | ||
for (const [key, values] of Object.entries(jsonValue.metadata)) { | ||
if (!Array.isArray(values) || | ||
values.some((value) => typeof value != "string")) { | ||
throw (0, connect_error_js_1.newParseError)(values, `.metadata["${key}"]`); | ||
} | ||
for (const value of values) { | ||
metadata.append(key, value); | ||
} | ||
for (const value of values) { | ||
metadata.append(key, value); | ||
} | ||
} | ||
let error; | ||
if ("error" in jsonValue) { | ||
if (typeof jsonValue.error != "object" || | ||
jsonValue.error == null || | ||
Array.isArray(jsonValue.error)) { | ||
throw (0, connect_error_js_1.newParseError)(jsonValue, ".error"); | ||
} | ||
let error; | ||
if ("error" in jsonValue) { | ||
if (typeof jsonValue.error != "object" || | ||
jsonValue.error == null || | ||
Array.isArray(jsonValue.error)) { | ||
throw (0, connect_error_js_1.newParseError)(jsonValue, ".error"); | ||
} | ||
if (Object.keys(jsonValue.error).length > 0) { | ||
try { | ||
error = (0, connect_error_js_1.connectErrorFromJson)(jsonValue.error, metadata); | ||
} | ||
if (Object.keys(jsonValue.error).length > 0) { | ||
try { | ||
error = (0, connect_error_js_1.connectErrorFromJson)(jsonValue.error, Object.assign(Object.assign({}, options), { metadata })); | ||
} | ||
catch (e) { | ||
throw (0, connect_error_js_1.newParseError)(e, ".error", false); | ||
} | ||
catch (e) { | ||
throw (0, connect_error_js_1.newParseError)(e, ".error", false); | ||
} | ||
} | ||
return new EndStream(metadata, error); | ||
} | ||
return { metadata, error }; | ||
} |
@@ -16,25 +16,5 @@ "use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.createEnvelopeReadableStream = exports.encodeEnvelopes = void 0; | ||
exports.encodeEnvelopes = exports.createEnvelopeReadableStream = void 0; | ||
const connect_error_js_1 = require("./connect-error.js"); | ||
const code_js_1 = require("./code.js"); | ||
function encodeEnvelopes(...envelopes) { | ||
const target = new ArrayBuffer(envelopes.reduce((previousValue, currentValue) => previousValue + currentValue.data.length + 5, 0)); | ||
let offset = 0; | ||
for (const m of envelopes) { | ||
offset += encodeEnvelope(m, target, offset); | ||
} | ||
return new Uint8Array(target); | ||
} | ||
exports.encodeEnvelopes = encodeEnvelopes; | ||
function encodeEnvelope(envelope, target, byteOffset) { | ||
const len = envelope.data.length + 5; | ||
const bytes = new Uint8Array(target, byteOffset, len); | ||
bytes[0] = envelope.flags; // first byte is flags | ||
for (let l = envelope.data.length, i = 4; i > 0; i--) { | ||
bytes[i] = l % 256; // 4 bytes message length | ||
l >>>= 8; | ||
} | ||
bytes.set(envelope.data, 5); | ||
return len; | ||
} | ||
/** | ||
@@ -96,1 +76,21 @@ * Create a ReadableStream of enveloped messages from a ReadableStream of bytes. | ||
exports.createEnvelopeReadableStream = createEnvelopeReadableStream; | ||
function encodeEnvelopes(...envelopes) { | ||
const target = new ArrayBuffer(envelopes.reduce((previousValue, currentValue) => previousValue + currentValue.data.length + 5, 0)); | ||
let offset = 0; | ||
for (const m of envelopes) { | ||
offset += encodeEnvelope(m, target, offset); | ||
} | ||
return new Uint8Array(target); | ||
} | ||
exports.encodeEnvelopes = encodeEnvelopes; | ||
function encodeEnvelope(envelope, target, byteOffset) { | ||
const len = envelope.data.length + 5; | ||
const bytes = new Uint8Array(target, byteOffset, len); | ||
bytes[0] = envelope.flags; // first byte is flags | ||
for (let l = envelope.data.length, i = 4; i > 0; i--) { | ||
bytes[i] = l % 256; // 4 bytes message length | ||
l >>>= 8; | ||
} | ||
bytes.set(envelope.data, 5); | ||
return len; | ||
} |
@@ -23,2 +23,12 @@ "use strict"; | ||
const envelope_js_1 = require("./envelope.js"); | ||
/** | ||
* Create a Transport for the gRPC-web protocol. The protocol encodes | ||
* trailers in the response body and makes unary and server-streaming | ||
* methods available to web browsers. It uses the fetch API to make | ||
* HTTP requests. | ||
* | ||
* Note that this transport does not implement the grpc-web-text format, | ||
* which applies base64 encoding to the request and response bodies to | ||
* support reading streaming responses from an XMLHttpRequest. | ||
*/ | ||
function createGrpcWebTransport(options) { | ||
@@ -47,3 +57,3 @@ const transportOptions = options; | ||
const response = await fetch(unaryRequest.url, Object.assign(Object.assign({}, unaryRequest.init), { headers: unaryRequest.header, signal: unaryRequest.signal, body: createGrpcWebRequestBody(unaryRequest.message, options.binaryOptions) })); | ||
const headError = (_c = (_b = (_a = extractHttpStatusError(response)) !== null && _a !== void 0 ? _a : extractContentTypeError(response.headers)) !== null && _b !== void 0 ? _b : extractDetailsError(response.headers, transportOptions.errorDetailRegistry)) !== null && _c !== void 0 ? _c : extractHeadersError(response.headers); | ||
const headError = (_c = (_b = (_a = extractHttpStatusError(response)) !== null && _a !== void 0 ? _a : extractContentTypeError(response.headers)) !== null && _b !== void 0 ? _b : extractDetailsError(response.headers)) !== null && _c !== void 0 ? _c : extractHeadersError(response.headers); | ||
if (headError) { | ||
@@ -64,3 +74,3 @@ throw headError; | ||
// that only contains error trailers. | ||
parseGrpcWebTrailerAndExtractError(messageOrTrailerResult.value.data, transportOptions.errorDetailRegistry); | ||
parseGrpcWebTrailerAndExtractError(messageOrTrailerResult.value.data); | ||
// At this point, we received trailers only, but the trailers did | ||
@@ -78,3 +88,3 @@ // not have an error status code. | ||
} | ||
const trailer = parseGrpcWebTrailerAndExtractError(trailerResult.value.data, transportOptions.errorDetailRegistry); | ||
const trailer = parseGrpcWebTrailerAndExtractError(trailerResult.value.data); | ||
const eofResult = await reader.read(); | ||
@@ -93,3 +103,3 @@ if (!eofResult.done) { | ||
catch (e) { | ||
throw (0, connect_error_js_1.connectErrorFromReason)(e); | ||
throw (0, connect_error_js_1.connectErrorFromReason)(e, code_js_1.Code.Internal); | ||
} | ||
@@ -117,3 +127,3 @@ }, | ||
const response = await fetch(unaryRequest.url, Object.assign(Object.assign({}, unaryRequest.init), { headers: unaryRequest.header, signal: unaryRequest.signal, body: createGrpcWebRequestBody(unaryRequest.message, options.binaryOptions) })); | ||
const err = (_c = (_b = (_a = extractHttpStatusError(response)) !== null && _a !== void 0 ? _a : extractContentTypeError(response.headers)) !== null && _b !== void 0 ? _b : extractDetailsError(response.headers, transportOptions.errorDetailRegistry)) !== null && _c !== void 0 ? _c : extractHeadersError(response.headers); | ||
const err = (_c = (_b = (_a = extractHttpStatusError(response)) !== null && _a !== void 0 ? _a : extractContentTypeError(response.headers)) !== null && _b !== void 0 ? _b : extractDetailsError(response.headers)) !== null && _c !== void 0 ? _c : extractHeadersError(response.headers); | ||
if (err) { | ||
@@ -147,3 +157,3 @@ throw err; | ||
endStreamReceived = true; | ||
const trailer = parseGrpcWebTrailerAndExtractError(result.value.data, transportOptions.errorDetailRegistry); | ||
const trailer = parseGrpcWebTrailerAndExtractError(result.value.data); | ||
trailer.forEach((value, key) => this.trailer.append(key, value)); | ||
@@ -165,3 +175,3 @@ return { | ||
catch (e) { | ||
throw (0, connect_error_js_1.connectErrorFromReason)(e); | ||
throw (0, connect_error_js_1.connectErrorFromReason)(e, code_js_1.Code.Internal); | ||
} | ||
@@ -237,3 +247,3 @@ }, | ||
} | ||
function extractDetailsError(header, typeRegistry) { | ||
function extractDetailsError(header) { | ||
const grpcStatusDetailsBin = header.get("grpc-status-details-bin"); | ||
@@ -250,21 +260,7 @@ if (grpcStatusDetailsBin == null) { | ||
const error = new connect_error_js_1.ConnectError(status.message, status.code, undefined, header); | ||
// TODO deduplicate with similar logic in ConnectError | ||
for (const any of status.details) { | ||
const typeName = any.typeUrl.substring(any.typeUrl.lastIndexOf("/") + 1); | ||
if (!typeRegistry) { | ||
error.rawDetails.push(any); | ||
continue; | ||
} | ||
const messageType = typeRegistry.findMessage(typeName); | ||
if (messageType) { | ||
const message = new messageType(); | ||
if (any.unpackTo(message)) { | ||
error.details.push(message); | ||
} | ||
} | ||
} | ||
error.rawDetails.push(...status.details); | ||
return error; | ||
} | ||
catch (e) { | ||
const ce = (0, connect_error_js_1.connectErrorFromReason)(e); | ||
const ce = (0, connect_error_js_1.connectErrorFromReason)(e, code_js_1.Code.Internal); | ||
header.forEach((value, key) => ce.metadata.append(key, value)); | ||
@@ -274,6 +270,6 @@ return ce; | ||
} | ||
function parseGrpcWebTrailerAndExtractError(data, errorDetailRegistry) { | ||
function parseGrpcWebTrailerAndExtractError(data) { | ||
var _a; | ||
const trailer = parseGrpcWebTrailer(data); | ||
const err = (_a = extractDetailsError(trailer, errorDetailRegistry)) !== null && _a !== void 0 ? _a : extractHeadersError(trailer); | ||
const err = (_a = extractDetailsError(trailer)) !== null && _a !== void 0 ? _a : extractHeadersError(trailer); | ||
if (err) { | ||
@@ -280,0 +276,0 @@ throw err; |
@@ -21,7 +21,9 @@ "use strict"; | ||
/** | ||
* Encode a single binary header value according to the gRPC | ||
* specification. | ||
* Encode a single binary header value according to the Connect | ||
* and gRPC specifications. | ||
* | ||
* Binary headers names end with `-bin`, and can contain arbitrary | ||
* base64-encoded binary data. | ||
* This function accepts raw binary data from a buffer, a string | ||
* with UTF-8 text, or a protobuf message. It encodes the input | ||
* with base64 and returns a string that can be used for a header | ||
* whose name ends with `-bin`. | ||
*/ | ||
@@ -33,2 +35,5 @@ function encodeBinaryHeader(value) { | ||
} | ||
else if (typeof value == "string") { | ||
bytes = new TextEncoder().encode(value); | ||
} | ||
else { | ||
@@ -35,0 +40,0 @@ bytes = value instanceof Uint8Array ? value : new Uint8Array(value); |
@@ -16,3 +16,3 @@ "use strict"; | ||
Object.defineProperty(exports, "__esModule", { value: true }); | ||
exports.encodeEnvelopes = exports.createEnvelopeReadableStream = exports.mergeHeaders = exports.encodeBinaryHeader = exports.decodeBinaryHeader = exports.createGrpcWebTransport = exports.createConnectTransport = exports.codeToString = exports.codeFromString = exports.Code = exports.connectErrorFromJson = exports.ConnectError = exports.runServerStream = exports.runUnary = exports.makeAnyClient = exports.createPromiseClient = exports.createCallbackClient = void 0; | ||
exports.createEnvelopeReadableStream = exports.mergeHeaders = exports.decodeBinaryHeader = exports.encodeBinaryHeader = exports.codeToString = exports.codeFromString = exports.Code = exports.connectErrorFromJson = exports.connectErrorFromReason = exports.connectErrorDetails = exports.ConnectError = exports.runServerStream = exports.runUnary = exports.createGrpcWebTransport = exports.createConnectTransport = exports.makeAnyClient = exports.createPromiseClient = exports.createCallbackClient = void 0; | ||
var callback_client_js_1 = require("./callback-client.js"); | ||
@@ -24,8 +24,13 @@ Object.defineProperty(exports, "createCallbackClient", { enumerable: true, get: function () { return callback_client_js_1.createCallbackClient; } }); | ||
Object.defineProperty(exports, "makeAnyClient", { enumerable: true, get: function () { return any_client_js_1.makeAnyClient; } }); | ||
var connect_transport_js_1 = require("./connect-transport.js"); | ||
Object.defineProperty(exports, "createConnectTransport", { enumerable: true, get: function () { return connect_transport_js_1.createConnectTransport; } }); | ||
var grpc_web_transport_js_1 = require("./grpc-web-transport.js"); | ||
Object.defineProperty(exports, "createGrpcWebTransport", { enumerable: true, get: function () { return grpc_web_transport_js_1.createGrpcWebTransport; } }); | ||
var interceptor_js_1 = require("./interceptor.js"); | ||
Object.defineProperty(exports, "runUnary", { enumerable: true, get: function () { return interceptor_js_1.runUnary; } }); | ||
Object.defineProperty(exports, "runServerStream", { enumerable: true, get: function () { return interceptor_js_1.runServerStream; } }); | ||
var transport_js_1 = require("./transport.js"); | ||
var connect_error_js_1 = require("./connect-error.js"); | ||
Object.defineProperty(exports, "ConnectError", { enumerable: true, get: function () { return connect_error_js_1.ConnectError; } }); | ||
Object.defineProperty(exports, "connectErrorDetails", { enumerable: true, get: function () { return connect_error_js_1.connectErrorDetails; } }); | ||
Object.defineProperty(exports, "connectErrorFromReason", { enumerable: true, get: function () { return connect_error_js_1.connectErrorFromReason; } }); | ||
Object.defineProperty(exports, "connectErrorFromJson", { enumerable: true, get: function () { return connect_error_js_1.connectErrorFromJson; } }); | ||
@@ -36,12 +41,7 @@ var code_js_1 = require("./code.js"); | ||
Object.defineProperty(exports, "codeToString", { enumerable: true, get: function () { return code_js_1.codeToString; } }); | ||
var connect_transport_js_1 = require("./connect-transport.js"); | ||
Object.defineProperty(exports, "createConnectTransport", { enumerable: true, get: function () { return connect_transport_js_1.createConnectTransport; } }); | ||
var grpc_web_transport_js_1 = require("./grpc-web-transport.js"); | ||
Object.defineProperty(exports, "createGrpcWebTransport", { enumerable: true, get: function () { return grpc_web_transport_js_1.createGrpcWebTransport; } }); | ||
var http_headers_js_1 = require("./http-headers.js"); | ||
Object.defineProperty(exports, "encodeBinaryHeader", { enumerable: true, get: function () { return http_headers_js_1.encodeBinaryHeader; } }); | ||
Object.defineProperty(exports, "decodeBinaryHeader", { enumerable: true, get: function () { return http_headers_js_1.decodeBinaryHeader; } }); | ||
Object.defineProperty(exports, "encodeBinaryHeader", { enumerable: true, get: function () { return http_headers_js_1.encodeBinaryHeader; } }); | ||
Object.defineProperty(exports, "mergeHeaders", { enumerable: true, get: function () { return http_headers_js_1.mergeHeaders; } }); | ||
var envelope_js_1 = require("./envelope.js"); | ||
Object.defineProperty(exports, "createEnvelopeReadableStream", { enumerable: true, get: function () { return envelope_js_1.createEnvelopeReadableStream; } }); | ||
Object.defineProperty(exports, "encodeEnvelopes", { enumerable: true, get: function () { return envelope_js_1.encodeEnvelopes; } }); |
@@ -17,3 +17,17 @@ "use strict"; | ||
exports.runServerStream = exports.runUnary = void 0; | ||
// TODO document | ||
/** | ||
* applyInterceptors takes the given UnaryFn or ServerStreamingFn, and wraps | ||
* it with each of the given interceptors, returning a new UnaryFn or | ||
* ServerStreamingFn. | ||
*/ | ||
function applyInterceptors(next, interceptors) { | ||
return interceptors | ||
.concat() | ||
.reverse() | ||
.reduce((n, i) => i(n), next); | ||
} | ||
/** | ||
* Runs a unary method with the given interceptors. Note that this function | ||
* is only used when implementing a Transport. | ||
*/ | ||
function runUnary(req, next, interceptors) { | ||
@@ -26,3 +40,6 @@ if (interceptors) { | ||
exports.runUnary = runUnary; | ||
// TODO document | ||
/** | ||
* Runs a server-streaming method with the given interceptors. Note that this | ||
* function is only used when implementing a Transport. | ||
*/ | ||
function runServerStream(req, next, interceptors) { | ||
@@ -35,7 +52,1 @@ if (interceptors) { | ||
exports.runServerStream = runServerStream; | ||
function applyInterceptors(next, interceptors) { | ||
return interceptors | ||
.concat() | ||
.reverse() | ||
.reduce((n, i) => i(n), next); | ||
} |
@@ -46,3 +46,3 @@ // Copyright 2021-2022 Buf Technologies, Inc. | ||
}, (reason) => { | ||
const err = connectErrorFromReason(reason); | ||
const err = connectErrorFromReason(reason, Code.Internal); | ||
if (err.code === Code.Canceled && abort.signal.aborted) { | ||
@@ -74,8 +74,11 @@ // As documented, discard Canceled errors if canceled by the user. | ||
run().catch((reason) => { | ||
const err = connectErrorFromReason(reason); | ||
const err = connectErrorFromReason(reason, Code.Internal); | ||
if (err.code === Code.Canceled && abort.signal.aborted) { | ||
// As documented, discard Canceled errors if canceled by the user. | ||
return; | ||
// As documented, discard Canceled errors if canceled by the user, | ||
// but do invoke the close-callback. | ||
onClose(undefined); | ||
} | ||
onClose(err); | ||
else { | ||
onClose(err); | ||
} | ||
}); | ||
@@ -88,4 +91,7 @@ return () => abort.abort(); | ||
options.signal.addEventListener("abort", () => abort.abort()); | ||
if (options.signal.aborted) { | ||
abort.abort(); | ||
} | ||
} | ||
return Object.assign(Object.assign({}, options), { signal: abort.signal }); | ||
} |
@@ -16,3 +16,3 @@ // Copyright 2021-2022 Buf Technologies, Inc. | ||
import { proto3, } from "@bufbuild/protobuf"; | ||
import { Any } from "@bufbuild/protobuf"; | ||
import { Any, TypeRegistry } from "@bufbuild/protobuf"; | ||
/** | ||
@@ -24,11 +24,12 @@ * ConnectError captures three pieces of information: a Code, an error | ||
* Because developer tools typically show just the error message, we prefix | ||
* it with the status code. | ||
* it with the status code, so that the most important information is always | ||
* visible immediately. | ||
* | ||
* Error details are wrapped with google.protobuf.Any on the wire, so that | ||
* a server or middleware can attach arbitrary data to an error. We | ||
* automatically unwrap the details for you. | ||
* a server or middleware can attach arbitrary data to an error. Use the | ||
* function connectErrorDetails() to retrieve the details. | ||
*/ | ||
export class ConnectError extends Error { | ||
constructor(message, code = Code.Unknown, details, metadata) { | ||
super(syntheticMessage(code, message)); | ||
super(createMessage(message, code)); | ||
this.name = "ConnectError"; | ||
@@ -39,3 +40,2 @@ // see https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-2.html#example | ||
this.code = code; | ||
this.details = details !== null && details !== void 0 ? details : []; | ||
this.metadata = new Headers(metadata); | ||
@@ -45,3 +45,28 @@ this.rawDetails = []; | ||
} | ||
function syntheticMessage(code, message) { | ||
export function connectErrorDetails(error, typeOrRegistry, ...moreTypes) { | ||
const typeRegistry = "typeName" in typeOrRegistry | ||
? TypeRegistry.from(typeOrRegistry, ...moreTypes) | ||
: typeOrRegistry; | ||
const details = []; | ||
for (const raw of error.rawDetails) { | ||
try { | ||
const any = "@type" in raw ? Any.fromJson(raw, { typeRegistry }) : raw; | ||
const name = any.typeUrl.substring(any.typeUrl.lastIndexOf("/") + 1); | ||
const type = typeRegistry.findMessage(name); | ||
if (type) { | ||
const message = new type(); | ||
any.unpackTo(message); | ||
details.push(message); | ||
} | ||
} | ||
catch (_) { | ||
// | ||
} | ||
} | ||
return details; | ||
} | ||
/** | ||
* Create an error message, prefixing the given code. | ||
*/ | ||
function createMessage(message, code) { | ||
return message.length | ||
@@ -52,6 +77,6 @@ ? `[${codeToString(code)}] ${message}` | ||
/** | ||
* Parse an error from a JSON value. | ||
* Parse a Connect error from a JSON value. | ||
* Will return a ConnectError, but throw one in case the JSON is malformed. | ||
*/ | ||
export function connectErrorFromJson(jsonValue, options) { | ||
export function connectErrorFromJson(jsonValue, metadata) { | ||
if (typeof jsonValue !== "object" || | ||
@@ -72,30 +97,5 @@ jsonValue == null || | ||
} | ||
const error = new ConnectError(message !== null && message !== void 0 ? message : "", code, undefined, options === null || options === void 0 ? void 0 : options.metadata); | ||
const error = new ConnectError(message !== null && message !== void 0 ? message : "", code, undefined, metadata); | ||
if ("details" in jsonValue && Array.isArray(jsonValue.details)) { | ||
for (const raw of jsonValue.details) { | ||
let any; | ||
try { | ||
any = Any.fromJson(raw, options); | ||
} | ||
catch (e) { | ||
// We consider error details to be supplementary information. | ||
// Parsing error details must not hide elementary information | ||
// like code and message, so we deliberately ignore parsing | ||
// errors here. | ||
error.rawDetails.push(raw); | ||
continue; | ||
} | ||
const typeName = any.typeUrl.substring(any.typeUrl.lastIndexOf("/") + 1); | ||
if (!(options === null || options === void 0 ? void 0 : options.typeRegistry)) { | ||
error.rawDetails.push(raw); | ||
continue; | ||
} | ||
const messageType = options.typeRegistry.findMessage(typeName); | ||
if (messageType) { | ||
const message = new messageType(); | ||
if (any.unpackTo(message)) { | ||
error.details.push(message); | ||
} | ||
} | ||
} | ||
error.rawDetails.push(...jsonValue.details); | ||
} | ||
@@ -105,8 +105,12 @@ return error; | ||
/** | ||
* Convert any value - typically a caught error into a ConnectError. | ||
* If the value is already a ConnectError, return it as is. | ||
* If the value is an AbortError from the fetch API, return code Canceled. | ||
* For other values, return code Internal. | ||
* Convert any value - typically a caught error into a ConnectError, | ||
* following these rules: | ||
* - If the value is already a ConnectError, return it as is. | ||
* - If the value is an AbortError from the fetch API, return the message | ||
* of the AbortError with code Canceled. | ||
* - For other Errors, return the Errors message with code Unknown by default. | ||
* - For other values, return the values String representation as a message, | ||
* with the code Unknown by default. | ||
*/ | ||
export function connectErrorFromReason(reason) { | ||
export function connectErrorFromReason(reason, code = Code.Unknown) { | ||
if (reason instanceof ConnectError) { | ||
@@ -124,3 +128,3 @@ return reason; | ||
} | ||
return new ConnectError(String(reason), Code.Internal); | ||
return new ConnectError(String(reason), code); | ||
} | ||
@@ -127,0 +131,0 @@ export function newParseError(valueOrError, property, json) { |
@@ -20,2 +20,7 @@ // Copyright 2021-2022 Buf Technologies, Inc. | ||
import { mergeHeaders } from "./http-headers.js"; | ||
/** | ||
* Create a Transport for the Connect protocol, which makes unary and | ||
* server-streaming methods available to web browsers. It uses the fetch | ||
* API to make HTTP requests. | ||
*/ | ||
export function createConnectTransport(options) { | ||
@@ -48,6 +53,3 @@ var _a; | ||
if (responseType == "application/json") { | ||
throw connectErrorFromJson((await response.json()), { | ||
typeRegistry: options.errorDetailRegistry, | ||
metadata: mergeHeaders(...demuxHeaderTrailers(response.headers)), | ||
}); | ||
throw connectErrorFromJson((await response.json()), mergeHeaders(...demuxHeaderTrailers(response.headers))); | ||
} | ||
@@ -71,3 +73,3 @@ throw new ConnectError(`HTTP ${response.status} ${response.statusText}`, codeFromConnectHttpStatus(response.status)); | ||
catch (e) { | ||
throw connectErrorFromReason(e); | ||
throw connectErrorFromReason(e, Code.Internal); | ||
} | ||
@@ -98,6 +100,3 @@ }, | ||
if (responseType == "application/json") { | ||
throw connectErrorFromJson((await response.json()), { | ||
typeRegistry: options.errorDetailRegistry, | ||
metadata: mergeHeaders(...demuxHeaderTrailers(response.headers)), | ||
}); | ||
throw connectErrorFromJson((await response.json()), mergeHeaders(...demuxHeaderTrailers(response.headers))); | ||
} | ||
@@ -132,5 +131,3 @@ throw new ConnectError(`HTTP ${response.status} ${response.statusText}`, codeFromConnectHttpStatus(response.status)); | ||
endStreamReceived = true; | ||
const endStream = EndStream.fromJsonString(new TextDecoder().decode(result.value.data), { | ||
typeRegistry: options.errorDetailRegistry, | ||
}); | ||
const endStream = endStreamFromJson(result.value.data); | ||
endStream.metadata.forEach((value, key) => this.trailer.append(key, value)); | ||
@@ -156,3 +153,3 @@ if (endStream.error) { | ||
catch (e) { | ||
throw connectErrorFromReason(e); | ||
throw connectErrorFromReason(e, Code.Internal); | ||
} | ||
@@ -162,2 +159,7 @@ }, | ||
} | ||
/** | ||
* Creates a body for a Connect request. Supports only requests with a single | ||
* message because of browser API limitations, but applies the enveloping | ||
* required for server-streaming requests. | ||
*/ | ||
function createConnectRequestBody(message, methodKind, useBinaryFormat, jsonOptions) { | ||
@@ -179,2 +181,5 @@ const encoded = useBinaryFormat | ||
} | ||
/** | ||
* Creates headers for a Connect request. | ||
*/ | ||
function createConnectRequestHeaders(headers, timeoutMs, methodKind, useBinaryFormat) { | ||
@@ -193,2 +198,6 @@ const result = new Headers(headers); | ||
} | ||
/** | ||
* Asserts a valid Connect Content-Type response header. Raises a ConnectError | ||
* otherwise. | ||
*/ | ||
function expectContentType(contentType, stream, binaryFormat) { | ||
@@ -222,69 +231,52 @@ const match = contentType === null || contentType === void 0 ? void 0 : contentType.match(/^application\/(connect\+)?(json|proto)$/); | ||
/** | ||
* Represents the EndStreamResponse of the Connect protocol. | ||
* Parse an EndStreamResponse of the Connect protocol. | ||
*/ | ||
class EndStream { | ||
constructor(metadata, error) { | ||
this.metadata = metadata; | ||
this.error = error; | ||
function endStreamFromJson(data) { | ||
let jsonValue; | ||
try { | ||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment | ||
jsonValue = JSON.parse(new TextDecoder().decode(data)); | ||
} | ||
/** | ||
* Parse an EndStreamResponse from a JSON string. | ||
* Will throw a ConnectError in case the JSON is malformed. | ||
*/ | ||
static fromJsonString(jsonString, options) { | ||
let json; | ||
try { | ||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment | ||
json = JSON.parse(jsonString); | ||
} | ||
catch (e) { | ||
throw newParseError(e, "", false); | ||
} | ||
return this.fromJson(json, options); | ||
catch (e) { | ||
throw newParseError(e, "", false); | ||
} | ||
/** | ||
* Parse an EndStreamResponse from a JSON value. | ||
* Will return a ConnectError, but throw one in case the JSON is malformed. | ||
*/ | ||
static fromJson(jsonValue, options) { | ||
if (typeof jsonValue != "object" || | ||
jsonValue == null || | ||
Array.isArray(jsonValue)) { | ||
throw newParseError(jsonValue); | ||
if (typeof jsonValue != "object" || | ||
jsonValue == null || | ||
Array.isArray(jsonValue)) { | ||
throw newParseError(jsonValue); | ||
} | ||
const metadata = new Headers(); | ||
if ("metadata" in jsonValue) { | ||
if (typeof jsonValue.metadata != "object" || | ||
jsonValue.metadata == null || | ||
Array.isArray(jsonValue.metadata)) { | ||
throw newParseError(jsonValue, ".metadata"); | ||
} | ||
const metadata = new Headers(); | ||
if ("metadata" in jsonValue) { | ||
if (typeof jsonValue.metadata != "object" || | ||
jsonValue.metadata == null || | ||
Array.isArray(jsonValue.metadata)) { | ||
throw newParseError(jsonValue, ".metadata"); | ||
for (const [key, values] of Object.entries(jsonValue.metadata)) { | ||
if (!Array.isArray(values) || | ||
values.some((value) => typeof value != "string")) { | ||
throw newParseError(values, `.metadata["${key}"]`); | ||
} | ||
for (const [key, values] of Object.entries(jsonValue.metadata)) { | ||
if (!Array.isArray(values) || | ||
values.some((value) => typeof value != "string")) { | ||
throw newParseError(values, `.metadata["${key}"]`); | ||
} | ||
for (const value of values) { | ||
metadata.append(key, value); | ||
} | ||
for (const value of values) { | ||
metadata.append(key, value); | ||
} | ||
} | ||
let error; | ||
if ("error" in jsonValue) { | ||
if (typeof jsonValue.error != "object" || | ||
jsonValue.error == null || | ||
Array.isArray(jsonValue.error)) { | ||
throw newParseError(jsonValue, ".error"); | ||
} | ||
let error; | ||
if ("error" in jsonValue) { | ||
if (typeof jsonValue.error != "object" || | ||
jsonValue.error == null || | ||
Array.isArray(jsonValue.error)) { | ||
throw newParseError(jsonValue, ".error"); | ||
} | ||
if (Object.keys(jsonValue.error).length > 0) { | ||
try { | ||
error = connectErrorFromJson(jsonValue.error, metadata); | ||
} | ||
if (Object.keys(jsonValue.error).length > 0) { | ||
try { | ||
error = connectErrorFromJson(jsonValue.error, Object.assign(Object.assign({}, options), { metadata })); | ||
} | ||
catch (e) { | ||
throw newParseError(e, ".error", false); | ||
} | ||
catch (e) { | ||
throw newParseError(e, ".error", false); | ||
} | ||
} | ||
return new EndStream(metadata, error); | ||
} | ||
return { metadata, error }; | ||
} |
@@ -16,21 +16,2 @@ // Copyright 2021-2022 Buf Technologies, Inc. | ||
import { Code } from "./code.js"; | ||
export function encodeEnvelopes(...envelopes) { | ||
const target = new ArrayBuffer(envelopes.reduce((previousValue, currentValue) => previousValue + currentValue.data.length + 5, 0)); | ||
let offset = 0; | ||
for (const m of envelopes) { | ||
offset += encodeEnvelope(m, target, offset); | ||
} | ||
return new Uint8Array(target); | ||
} | ||
function encodeEnvelope(envelope, target, byteOffset) { | ||
const len = envelope.data.length + 5; | ||
const bytes = new Uint8Array(target, byteOffset, len); | ||
bytes[0] = envelope.flags; // first byte is flags | ||
for (let l = envelope.data.length, i = 4; i > 0; i--) { | ||
bytes[i] = l % 256; // 4 bytes message length | ||
l >>>= 8; | ||
} | ||
bytes.set(envelope.data, 5); | ||
return len; | ||
} | ||
/** | ||
@@ -91,1 +72,20 @@ * Create a ReadableStream of enveloped messages from a ReadableStream of bytes. | ||
} | ||
export function encodeEnvelopes(...envelopes) { | ||
const target = new ArrayBuffer(envelopes.reduce((previousValue, currentValue) => previousValue + currentValue.data.length + 5, 0)); | ||
let offset = 0; | ||
for (const m of envelopes) { | ||
offset += encodeEnvelope(m, target, offset); | ||
} | ||
return new Uint8Array(target); | ||
} | ||
function encodeEnvelope(envelope, target, byteOffset) { | ||
const len = envelope.data.length + 5; | ||
const bytes = new Uint8Array(target, byteOffset, len); | ||
bytes[0] = envelope.flags; // first byte is flags | ||
for (let l = envelope.data.length, i = 4; i > 0; i--) { | ||
bytes[i] = l % 256; // 4 bytes message length | ||
l >>>= 8; | ||
} | ||
bytes.set(envelope.data, 5); | ||
return len; | ||
} |
@@ -20,2 +20,12 @@ // Copyright 2021-2022 Buf Technologies, Inc. | ||
import { createEnvelopeReadableStream, encodeEnvelopes } from "./envelope.js"; | ||
/** | ||
* Create a Transport for the gRPC-web protocol. The protocol encodes | ||
* trailers in the response body and makes unary and server-streaming | ||
* methods available to web browsers. It uses the fetch API to make | ||
* HTTP requests. | ||
* | ||
* Note that this transport does not implement the grpc-web-text format, | ||
* which applies base64 encoding to the request and response bodies to | ||
* support reading streaming responses from an XMLHttpRequest. | ||
*/ | ||
export function createGrpcWebTransport(options) { | ||
@@ -44,3 +54,3 @@ const transportOptions = options; | ||
const response = await fetch(unaryRequest.url, Object.assign(Object.assign({}, unaryRequest.init), { headers: unaryRequest.header, signal: unaryRequest.signal, body: createGrpcWebRequestBody(unaryRequest.message, options.binaryOptions) })); | ||
const headError = (_c = (_b = (_a = extractHttpStatusError(response)) !== null && _a !== void 0 ? _a : extractContentTypeError(response.headers)) !== null && _b !== void 0 ? _b : extractDetailsError(response.headers, transportOptions.errorDetailRegistry)) !== null && _c !== void 0 ? _c : extractHeadersError(response.headers); | ||
const headError = (_c = (_b = (_a = extractHttpStatusError(response)) !== null && _a !== void 0 ? _a : extractContentTypeError(response.headers)) !== null && _b !== void 0 ? _b : extractDetailsError(response.headers)) !== null && _c !== void 0 ? _c : extractHeadersError(response.headers); | ||
if (headError) { | ||
@@ -61,3 +71,3 @@ throw headError; | ||
// that only contains error trailers. | ||
parseGrpcWebTrailerAndExtractError(messageOrTrailerResult.value.data, transportOptions.errorDetailRegistry); | ||
parseGrpcWebTrailerAndExtractError(messageOrTrailerResult.value.data); | ||
// At this point, we received trailers only, but the trailers did | ||
@@ -75,3 +85,3 @@ // not have an error status code. | ||
} | ||
const trailer = parseGrpcWebTrailerAndExtractError(trailerResult.value.data, transportOptions.errorDetailRegistry); | ||
const trailer = parseGrpcWebTrailerAndExtractError(trailerResult.value.data); | ||
const eofResult = await reader.read(); | ||
@@ -90,3 +100,3 @@ if (!eofResult.done) { | ||
catch (e) { | ||
throw connectErrorFromReason(e); | ||
throw connectErrorFromReason(e, Code.Internal); | ||
} | ||
@@ -114,3 +124,3 @@ }, | ||
const response = await fetch(unaryRequest.url, Object.assign(Object.assign({}, unaryRequest.init), { headers: unaryRequest.header, signal: unaryRequest.signal, body: createGrpcWebRequestBody(unaryRequest.message, options.binaryOptions) })); | ||
const err = (_c = (_b = (_a = extractHttpStatusError(response)) !== null && _a !== void 0 ? _a : extractContentTypeError(response.headers)) !== null && _b !== void 0 ? _b : extractDetailsError(response.headers, transportOptions.errorDetailRegistry)) !== null && _c !== void 0 ? _c : extractHeadersError(response.headers); | ||
const err = (_c = (_b = (_a = extractHttpStatusError(response)) !== null && _a !== void 0 ? _a : extractContentTypeError(response.headers)) !== null && _b !== void 0 ? _b : extractDetailsError(response.headers)) !== null && _c !== void 0 ? _c : extractHeadersError(response.headers); | ||
if (err) { | ||
@@ -144,3 +154,3 @@ throw err; | ||
endStreamReceived = true; | ||
const trailer = parseGrpcWebTrailerAndExtractError(result.value.data, transportOptions.errorDetailRegistry); | ||
const trailer = parseGrpcWebTrailerAndExtractError(result.value.data); | ||
trailer.forEach((value, key) => this.trailer.append(key, value)); | ||
@@ -162,3 +172,3 @@ return { | ||
catch (e) { | ||
throw connectErrorFromReason(e); | ||
throw connectErrorFromReason(e, Code.Internal); | ||
} | ||
@@ -233,3 +243,3 @@ }, | ||
} | ||
function extractDetailsError(header, typeRegistry) { | ||
function extractDetailsError(header) { | ||
const grpcStatusDetailsBin = header.get("grpc-status-details-bin"); | ||
@@ -246,21 +256,7 @@ if (grpcStatusDetailsBin == null) { | ||
const error = new ConnectError(status.message, status.code, undefined, header); | ||
// TODO deduplicate with similar logic in ConnectError | ||
for (const any of status.details) { | ||
const typeName = any.typeUrl.substring(any.typeUrl.lastIndexOf("/") + 1); | ||
if (!typeRegistry) { | ||
error.rawDetails.push(any); | ||
continue; | ||
} | ||
const messageType = typeRegistry.findMessage(typeName); | ||
if (messageType) { | ||
const message = new messageType(); | ||
if (any.unpackTo(message)) { | ||
error.details.push(message); | ||
} | ||
} | ||
} | ||
error.rawDetails.push(...status.details); | ||
return error; | ||
} | ||
catch (e) { | ||
const ce = connectErrorFromReason(e); | ||
const ce = connectErrorFromReason(e, Code.Internal); | ||
header.forEach((value, key) => ce.metadata.append(key, value)); | ||
@@ -270,6 +266,6 @@ return ce; | ||
} | ||
function parseGrpcWebTrailerAndExtractError(data, errorDetailRegistry) { | ||
function parseGrpcWebTrailerAndExtractError(data) { | ||
var _a; | ||
const trailer = parseGrpcWebTrailer(data); | ||
const err = (_a = extractDetailsError(trailer, errorDetailRegistry)) !== null && _a !== void 0 ? _a : extractHeadersError(trailer); | ||
const err = (_a = extractDetailsError(trailer)) !== null && _a !== void 0 ? _a : extractHeadersError(trailer); | ||
if (err) { | ||
@@ -276,0 +272,0 @@ throw err; |
@@ -18,7 +18,9 @@ // Copyright 2021-2022 Buf Technologies, Inc. | ||
/** | ||
* Encode a single binary header value according to the gRPC | ||
* specification. | ||
* Encode a single binary header value according to the Connect | ||
* and gRPC specifications. | ||
* | ||
* Binary headers names end with `-bin`, and can contain arbitrary | ||
* base64-encoded binary data. | ||
* This function accepts raw binary data from a buffer, a string | ||
* with UTF-8 text, or a protobuf message. It encodes the input | ||
* with base64 and returns a string that can be used for a header | ||
* whose name ends with `-bin`. | ||
*/ | ||
@@ -30,2 +32,5 @@ export function encodeBinaryHeader(value) { | ||
} | ||
else if (typeof value == "string") { | ||
bytes = new TextEncoder().encode(value); | ||
} | ||
else { | ||
@@ -32,0 +37,0 @@ bytes = value instanceof Uint8Array ? value : new Uint8Array(value); |
@@ -17,9 +17,8 @@ // Copyright 2021-2022 Buf Technologies, Inc. | ||
export { makeAnyClient } from "./any-client.js"; | ||
export { createConnectTransport, } from "./connect-transport.js"; | ||
export { createGrpcWebTransport, } from "./grpc-web-transport.js"; | ||
export { runUnary, runServerStream, } from "./interceptor.js"; | ||
export {} from "./transport.js"; | ||
export { ConnectError, connectErrorFromJson } from "./connect-error.js"; | ||
export { ConnectError, connectErrorDetails, connectErrorFromReason, connectErrorFromJson, } from "./connect-error.js"; | ||
export { Code, codeFromString, codeToString } from "./code.js"; | ||
export { createConnectTransport } from "./connect-transport.js"; | ||
export { createGrpcWebTransport } from "./grpc-web-transport.js"; | ||
export { decodeBinaryHeader, encodeBinaryHeader, mergeHeaders, } from "./http-headers.js"; | ||
export { createEnvelopeReadableStream, encodeEnvelopes, } from "./envelope.js"; | ||
export { encodeBinaryHeader, decodeBinaryHeader, mergeHeaders, } from "./http-headers.js"; | ||
export { createEnvelopeReadableStream } from "./envelope.js"; |
@@ -14,3 +14,17 @@ // Copyright 2021-2022 Buf Technologies, Inc. | ||
// limitations under the License. | ||
// TODO document | ||
/** | ||
* applyInterceptors takes the given UnaryFn or ServerStreamingFn, and wraps | ||
* it with each of the given interceptors, returning a new UnaryFn or | ||
* ServerStreamingFn. | ||
*/ | ||
function applyInterceptors(next, interceptors) { | ||
return interceptors | ||
.concat() | ||
.reverse() | ||
.reduce((n, i) => i(n), next); | ||
} | ||
/** | ||
* Runs a unary method with the given interceptors. Note that this function | ||
* is only used when implementing a Transport. | ||
*/ | ||
export function runUnary(req, next, interceptors) { | ||
@@ -22,3 +36,6 @@ if (interceptors) { | ||
} | ||
// TODO document | ||
/** | ||
* Runs a server-streaming method with the given interceptors. Note that this | ||
* function is only used when implementing a Transport. | ||
*/ | ||
export function runServerStream(req, next, interceptors) { | ||
@@ -30,7 +47,1 @@ if (interceptors) { | ||
} | ||
function applyInterceptors(next, interceptors) { | ||
return interceptors | ||
.concat() | ||
.reverse() | ||
.reduce((n, i) => i(n), next); | ||
} |
import type { MethodInfoServerStreaming, MethodInfoUnary, PartialMessage, ServiceType } from "@bufbuild/protobuf"; | ||
import type { ConnectError } from "./connect-error.js"; | ||
import type { CallOptions, Transport } from "./transport.js"; | ||
import type { Transport } from "./transport.js"; | ||
import type { CallOptions } from "./call-options.js"; | ||
/** | ||
@@ -5,0 +6,0 @@ * CallbackClient is a simple client that supports unary and server |
import { Code } from "./code.js"; | ||
import { AnyMessage, IMessageTypeRegistry, JsonValue } from "@bufbuild/protobuf"; | ||
import { AnyMessage, IMessageTypeRegistry, JsonValue, Message, MessageType } from "@bufbuild/protobuf"; | ||
import { Any } from "@bufbuild/protobuf"; | ||
@@ -10,7 +10,8 @@ /** | ||
* Because developer tools typically show just the error message, we prefix | ||
* it with the status code. | ||
* it with the status code, so that the most important information is always | ||
* visible immediately. | ||
* | ||
* Error details are wrapped with google.protobuf.Any on the wire, so that | ||
* a server or middleware can attach arbitrary data to an error. We | ||
* automatically unwrap the details for you. | ||
* a server or middleware can attach arbitrary data to an error. Use the | ||
* function connectErrorDetails() to retrieve the details. | ||
*/ | ||
@@ -23,6 +24,2 @@ export declare class ConnectError extends Error { | ||
/** | ||
* Optional collection of arbitrary Protobuf messages. | ||
*/ | ||
readonly details: AnyMessage[]; | ||
/** | ||
* A union of response headers and trailers associated with this error. | ||
@@ -32,8 +29,6 @@ */ | ||
/** | ||
* When an error is parsed from the wire, details are unwrapped | ||
* automatically from google.protobuf.Any, but we can only do so if you | ||
* provide the message types in a type registry. If a message type is not | ||
* given in a type registry, the unwrapped details are available here. | ||
* When an error is parsed from the wire, error details are stored in | ||
* this property. They can be retrieved using connectErrorDetails(). | ||
*/ | ||
readonly rawDetails: RawDetail[]; | ||
readonly rawDetails: RawErrorDetail[]; | ||
/** | ||
@@ -50,7 +45,18 @@ * The error message, but without a status code in front. | ||
/** | ||
* A raw detail is a google.protobuf.Any, or it's JSON representation. | ||
* Retrieve error details from a ConnectError. On the wire, error details are | ||
* wrapped with google.protobuf.Any, so that a server or middleware can attach | ||
* arbitrary data to an error. This function decodes the array of error details | ||
* from the ConnectError object, and returns an array with the decoded | ||
* messages. Any decoding errors are ignored, and the detail will simply be | ||
* omitted from the list. | ||
*/ | ||
export declare function connectErrorDetails<T extends Message<T>>(error: ConnectError, type: MessageType<T>): T[]; | ||
export declare function connectErrorDetails(error: ConnectError, type: MessageType, ...moreTypes: MessageType[]): AnyMessage[]; | ||
export declare function connectErrorDetails(error: ConnectError, registry: IMessageTypeRegistry): AnyMessage[]; | ||
/** | ||
* A raw error detail is a google.protobuf.Any, or it's JSON representation. | ||
* This type is used for error details that we could not unwrap because | ||
* we are missing type information. | ||
*/ | ||
export declare type RawDetail = Any | { | ||
export declare type RawErrorDetail = Any | { | ||
"@type": string; | ||
@@ -60,16 +66,17 @@ [key: string]: JsonValue; | ||
/** | ||
* Parse an error from a JSON value. | ||
* Parse a Connect error from a JSON value. | ||
* Will return a ConnectError, but throw one in case the JSON is malformed. | ||
*/ | ||
export declare function connectErrorFromJson(jsonValue: JsonValue, options?: { | ||
typeRegistry?: IMessageTypeRegistry; | ||
metadata?: HeadersInit; | ||
}): ConnectError; | ||
export declare function connectErrorFromJson(jsonValue: JsonValue, metadata?: HeadersInit): ConnectError; | ||
/** | ||
* Convert any value - typically a caught error into a ConnectError. | ||
* If the value is already a ConnectError, return it as is. | ||
* If the value is an AbortError from the fetch API, return code Canceled. | ||
* For other values, return code Internal. | ||
* Convert any value - typically a caught error into a ConnectError, | ||
* following these rules: | ||
* - If the value is already a ConnectError, return it as is. | ||
* - If the value is an AbortError from the fetch API, return the message | ||
* of the AbortError with code Canceled. | ||
* - For other Errors, return the Errors message with code Unknown by default. | ||
* - For other values, return the values String representation as a message, | ||
* with the code Unknown by default. | ||
*/ | ||
export declare function connectErrorFromReason(reason: unknown): ConnectError; | ||
export declare function connectErrorFromReason(reason: unknown, code?: Code): ConnectError; | ||
/** | ||
@@ -76,0 +83,0 @@ * newParseError() is an internal utility to create a ConnectError while |
@@ -1,5 +0,10 @@ | ||
import { BinaryReadOptions, BinaryWriteOptions, IMessageTypeRegistry, JsonReadOptions, JsonWriteOptions } from "@bufbuild/protobuf"; | ||
import { BinaryReadOptions, BinaryWriteOptions, JsonReadOptions, JsonWriteOptions } from "@bufbuild/protobuf"; | ||
import type { Transport } from "./transport.js"; | ||
import type { Interceptor } from "./interceptor.js"; | ||
interface ConnectTransportOptions { | ||
/** | ||
* Options used to configure the Connect transport. | ||
* | ||
* See createConnectTransport(). | ||
*/ | ||
export interface ConnectTransportOptions { | ||
/** | ||
@@ -26,4 +31,8 @@ * Base URI for all HTTP requests. | ||
credentials?: RequestCredentials; | ||
errorDetailRegistry?: IMessageTypeRegistry; | ||
/** | ||
* Interceptors that should be applied to all calls running through | ||
* this transport. See the Interceptor type for details. | ||
*/ | ||
interceptors?: Interceptor[]; | ||
/** | ||
* Options for the JSON format. | ||
@@ -36,5 +45,8 @@ */ | ||
binaryOptions?: Partial<BinaryReadOptions & BinaryWriteOptions>; | ||
interceptors?: Interceptor[]; | ||
} | ||
/** | ||
* Create a Transport for the Connect protocol, which makes unary and | ||
* server-streaming methods available to web browsers. It uses the fetch | ||
* API to make HTTP requests. | ||
*/ | ||
export declare function createConnectTransport(options: ConnectTransportOptions): Transport; | ||
export {}; |
/** | ||
* Enveloped-Message | ||
* Represents an Enveloped-Message of the Connect protocol. | ||
* https://connect.build/docs/protocol#streaming-rpcs | ||
*/ | ||
@@ -14,3 +15,2 @@ export interface EnvelopedMessage { | ||
} | ||
export declare function encodeEnvelopes(...envelopes: EnvelopedMessage[]): Uint8Array; | ||
/** | ||
@@ -23,1 +23,2 @@ * Create a ReadableStream of enveloped messages from a ReadableStream of bytes. | ||
export declare function createEnvelopeReadableStream(stream: ReadableStream<Uint8Array>): ReadableStream<EnvelopedMessage>; | ||
export declare function encodeEnvelopes(...envelopes: EnvelopedMessage[]): Uint8Array; |
@@ -1,5 +0,10 @@ | ||
import type { BinaryReadOptions, BinaryWriteOptions, IMessageTypeRegistry } from "@bufbuild/protobuf"; | ||
import type { BinaryReadOptions, BinaryWriteOptions } from "@bufbuild/protobuf"; | ||
import type { Transport } from "./transport.js"; | ||
import { Interceptor } from "./interceptor.js"; | ||
interface GrpcWebTransportOptions { | ||
/** | ||
* Options used to configure the gRPC-web transport. | ||
* | ||
* See createGrpcWebTransport(). | ||
*/ | ||
export interface GrpcWebTransportOptions { | ||
/** | ||
@@ -17,2 +22,7 @@ * Base URI for all HTTP requests. | ||
/** | ||
* Interceptors that should be applied to all calls running through | ||
* this transport. See the Interceptor type for details. | ||
*/ | ||
interceptors?: Interceptor[]; | ||
/** | ||
* Controls what the fetch client will do with credentials, such as | ||
@@ -23,3 +33,2 @@ * Cookies. The default value is "same-origin". For reference, see | ||
credentials?: RequestCredentials; | ||
errorDetailRegistry?: IMessageTypeRegistry; | ||
/** | ||
@@ -29,5 +38,13 @@ * Options for the binary wire format. | ||
binaryOptions?: Partial<BinaryReadOptions & BinaryWriteOptions>; | ||
interceptors?: Interceptor[]; | ||
} | ||
/** | ||
* Create a Transport for the gRPC-web protocol. The protocol encodes | ||
* trailers in the response body and makes unary and server-streaming | ||
* methods available to web browsers. It uses the fetch API to make | ||
* HTTP requests. | ||
* | ||
* Note that this transport does not implement the grpc-web-text format, | ||
* which applies base64 encoding to the request and response bodies to | ||
* support reading streaming responses from an XMLHttpRequest. | ||
*/ | ||
export declare function createGrpcWebTransport(options: GrpcWebTransportOptions): Transport; | ||
export {}; |
import type { BinaryReadOptions, MessageType } from "@bufbuild/protobuf"; | ||
import { Message } from "@bufbuild/protobuf"; | ||
/** | ||
* Encode a single binary header value according to the gRPC | ||
* specification. | ||
* Encode a single binary header value according to the Connect | ||
* and gRPC specifications. | ||
* | ||
* Binary headers names end with `-bin`, and can contain arbitrary | ||
* base64-encoded binary data. | ||
* This function accepts raw binary data from a buffer, a string | ||
* with UTF-8 text, or a protobuf message. It encodes the input | ||
* with base64 and returns a string that can be used for a header | ||
* whose name ends with `-bin`. | ||
*/ | ||
export declare function encodeBinaryHeader(value: Uint8Array | ArrayBufferLike | Message): string; | ||
export declare function encodeBinaryHeader(value: Uint8Array | ArrayBufferLike | Message | string): string; | ||
/** | ||
* Decode a single binary header value according to the gRPC | ||
* specification. | ||
* Decode a single binary header value according to the Connect | ||
* and gRPC specifications. | ||
* | ||
* Binary headers names end with `-bin`, and can contain arbitrary | ||
* base64-encoded binary data. | ||
* This function returns the raw binary data from a header whose | ||
* name ends with `-bin`. If given a message type in the second | ||
* argument, it deserializes a protobuf message. To decode a value | ||
* that contains unicode text, pass the raw binary data returned | ||
* from this function through TextDecoder.decode. | ||
* | ||
@@ -23,3 +28,3 @@ * Note that duplicate header names may have their values joined | ||
* If this function detects invalid base-64 encoding, or invalid | ||
* binary message data, it throws a ConnectError with status | ||
* binary message data, it throws a ConnectError with code | ||
* DataLoss. | ||
@@ -26,0 +31,0 @@ */ |
export { createCallbackClient, CallbackClient } from "./callback-client.js"; | ||
export { createPromiseClient, PromiseClient } from "./promise-client.js"; | ||
export { makeAnyClient, AnyClient } from "./any-client.js"; | ||
export type { CallOptions } from "./call-options.js"; | ||
export { createConnectTransport, ConnectTransportOptions, } from "./connect-transport.js"; | ||
export { createGrpcWebTransport, GrpcWebTransportOptions, } from "./grpc-web-transport.js"; | ||
export type { Transport } from "./transport.js"; | ||
export { Interceptor, UnaryRequest, UnaryResponse, StreamResponse, runUnary, runServerStream, } from "./interceptor.js"; | ||
export { Transport, CallOptions } from "./transport.js"; | ||
export { ConnectError, connectErrorFromJson } from "./connect-error.js"; | ||
export { ConnectError, connectErrorDetails, connectErrorFromReason, connectErrorFromJson, } from "./connect-error.js"; | ||
export { Code, codeFromString, codeToString } from "./code.js"; | ||
export { createConnectTransport } from "./connect-transport.js"; | ||
export { createGrpcWebTransport } from "./grpc-web-transport.js"; | ||
export { decodeBinaryHeader, encodeBinaryHeader, mergeHeaders, } from "./http-headers.js"; | ||
export { createEnvelopeReadableStream, EnvelopedMessage, encodeEnvelopes, } from "./envelope.js"; | ||
export { encodeBinaryHeader, decodeBinaryHeader, mergeHeaders, } from "./http-headers.js"; | ||
export { createEnvelopeReadableStream } from "./envelope.js"; |
import type { AnyMessage, Message, MethodInfo, ServiceType } from "@bufbuild/protobuf"; | ||
/** | ||
* An interceptor can add logic to clients, similar to the decorators | ||
* or middleware you may have seen in other libraries. Interceptors may | ||
* mutate the request and response, catch errors and retry/recover, emit | ||
* logs, or do nearly everything else. | ||
* | ||
* For a simple example, the following interceptor logs all requests: | ||
* | ||
* ```ts | ||
* const logger: Interceptor = (next) => async (req) => { | ||
* console.log(`sending message to ${req.url}`); | ||
* return await next(req); | ||
* }; | ||
* ``` | ||
* | ||
* You can think of interceptors like a layered onion. A request initiated | ||
* by a client goes through the outermost layer first. In the center, the | ||
* actual HTTP request is run by the transport. The response then comes back | ||
* through all layers and is returned to the client. | ||
* | ||
* To implement that layering, Interceptors are functions that wrap a call | ||
* invocation. In an array of interceptors, the interceptor at the end of | ||
* the array is applied first. | ||
*/ | ||
export declare type Interceptor = (next: AnyFn) => AnyFn; | ||
/** | ||
* Interceptors apply to both UnaryFn and ServerStreamFn. In order to handle | ||
* both in a single interceptor, interceptors operate on a AnyFn. Note that | ||
* you can inspect the `stream` property of request and response to identify | ||
* whether you are working with a stream. | ||
*/ | ||
declare type AnyFn = (req: UnaryRequest) => Promise<UnaryResponse | StreamResponse>; | ||
/** | ||
* UnaryFn represents the client-side invocation of a unary RPC - a method | ||
* that takes a single input message, and responds with a single output | ||
* message. | ||
* A Transport implements such a function, and makes it available to | ||
* interceptors. | ||
*/ | ||
declare type UnaryFn<I extends Message<I> = AnyMessage, O extends Message<O> = AnyMessage> = (req: UnaryRequest<I>) => Promise<UnaryResponse<O>>; | ||
/** | ||
* ServerStreamFn represents the client-side invocation of a server-streaming | ||
* RPC - a method that takes one input message, and responds with zero or | ||
* more output messages. | ||
* A Transport implements such a function, and makes it available to | ||
* interceptors. | ||
*/ | ||
declare type ServerStreamFn<I extends Message<I> = AnyMessage, O extends Message<O> = AnyMessage> = (req: UnaryRequest<I>) => Promise<StreamResponse<O>>; | ||
/** | ||
* UnaryRequest is used in interceptors to represent a request with a | ||
* single input message. | ||
*/ | ||
export interface UnaryRequest<I extends Message<I> = AnyMessage> { | ||
/** | ||
* The `stream` property discriminates between UnaryRequest and | ||
* other request types that may be added to this API in the future. | ||
*/ | ||
readonly stream: false; | ||
/** | ||
* Metadata related to the service that is being called. | ||
*/ | ||
readonly service: ServiceType; | ||
/** | ||
* Metadata related to the service method that is being called. | ||
*/ | ||
readonly method: MethodInfo<I, AnyMessage>; | ||
/** | ||
* The URL the request is going to hit. | ||
*/ | ||
readonly url: string; | ||
/** | ||
* Optional parameters to the fetch API. | ||
*/ | ||
readonly init: Exclude<RequestInit, "body" | "headers" | "signal">; | ||
/** | ||
* The AbortSignal for the current call. | ||
*/ | ||
readonly signal: AbortSignal; | ||
/** | ||
* Headers that will be sent along with the request. | ||
*/ | ||
readonly header: Headers; | ||
/** | ||
* The input message that will be transmitted. | ||
*/ | ||
readonly message: I; | ||
} | ||
/** | ||
* UnaryResponse is used in interceptors to represent a response with | ||
* a single output message. | ||
*/ | ||
export interface UnaryResponse<O extends Message<O> = AnyMessage> { | ||
/** | ||
* The `stream` property discriminates between UnaryResponse and | ||
* StreamResponse. | ||
*/ | ||
readonly stream: false; | ||
/** | ||
* Metadata related to the service that is being called. | ||
*/ | ||
readonly service: ServiceType; | ||
/** | ||
* Metadata related to the service method that is being called. | ||
*/ | ||
readonly method: MethodInfo<AnyMessage, O>; | ||
/** | ||
* Headers received from the response. | ||
*/ | ||
readonly header: Headers; | ||
/** | ||
* The received output message. | ||
*/ | ||
readonly message: O; | ||
/** | ||
* Trailers received from the response. | ||
*/ | ||
readonly trailer: Headers; | ||
} | ||
/** | ||
* StreamResponse is used in interceptors to represent a response with | ||
* zero or more output messages. | ||
*/ | ||
export interface StreamResponse<O extends Message<O> = AnyMessage> { | ||
/** | ||
* The `stream` property discriminates between UnaryResponse and | ||
* StreamResponse. | ||
*/ | ||
readonly stream: true; | ||
/** | ||
* Metadata related to the service that is being called. | ||
*/ | ||
readonly service: ServiceType; | ||
/** | ||
* Metadata related to the service method that is being called. | ||
*/ | ||
readonly method: MethodInfo<AnyMessage, O>; | ||
/** | ||
* Headers received from the response. | ||
*/ | ||
readonly header: Headers; | ||
/** | ||
* Reads a single output message from the response. The return value is a | ||
* promise, which fulfills/rejects with a result depending on the state of | ||
* the stream. | ||
* | ||
* The behavior is as follows: | ||
* 1. If a message was read successfully, the promise is fulfilled with an | ||
* object of the form `{value: O, done: false}`. | ||
* 2. If the end of the stream was reached, the promise is fulfilled with | ||
* `{value: undefined, done: true}`. | ||
* 3. If an error occurred, the response is rejected with this error. | ||
*/ | ||
read(): Promise<ReadableStreamDefaultReadResult<O>>; | ||
/** | ||
* Trailers received from the response. | ||
* Note that trailers are only populated if the entirety of the stream was | ||
* read. | ||
*/ | ||
readonly trailer: Headers; | ||
} | ||
/** | ||
* Runs a unary method with the given interceptors. Note that this function | ||
* is only used when implementing a Transport. | ||
*/ | ||
export declare function runUnary<I extends Message<I>, O extends Message<O>>(req: UnaryRequest<I>, next: UnaryFn<I, O>, interceptors?: Interceptor[]): Promise<UnaryResponse<O>>; | ||
/** | ||
* Runs a server-streaming method with the given interceptors. Note that this | ||
* function is only used when implementing a Transport. | ||
*/ | ||
export declare function runServerStream<I extends Message<I>, O extends Message<O>>(req: UnaryRequest<I>, next: ServerStreamFn<I, O>, interceptors?: Interceptor[]): Promise<StreamResponse<O>>; | ||
declare type UnaryFn<I extends Message<I> = AnyMessage, O extends Message<O> = AnyMessage> = (req: UnaryRequest<I>) => Promise<UnaryResponse<O>>; | ||
declare type ServerStreamFn<I extends Message<I> = AnyMessage, O extends Message<O> = AnyMessage> = (req: UnaryRequest<I>) => Promise<StreamResponse<O>>; | ||
export {}; |
import type { MethodInfoServerStreaming, MethodInfoUnary, PartialMessage, ServiceType } from "@bufbuild/protobuf"; | ||
import type { CallOptions, Transport } from "./transport.js"; | ||
import type { Transport } from "./transport.js"; | ||
import type { CallOptions } from "./call-options.js"; | ||
/** | ||
@@ -4,0 +5,0 @@ * PromiseClient is a simple client that supports unary and server-streaming |
@@ -9,31 +9,12 @@ import type { AnyMessage, Message, MethodInfo, PartialMessage, ServiceType } from "@bufbuild/protobuf"; | ||
export interface Transport { | ||
unary<I extends Message<I> = AnyMessage, O extends Message<O> = AnyMessage>(service: ServiceType, method: MethodInfo<I, O>, signal: AbortSignal | undefined, timeoutMs: number | undefined, header: HeadersInit | undefined, message: PartialMessage<I>): Promise<UnaryResponse<O>>; | ||
serverStream<I extends Message<I> = AnyMessage, O extends Message<O> = AnyMessage>(service: ServiceType, method: MethodInfo<I, O>, signal: AbortSignal | undefined, timeoutMs: number | undefined, header: HeadersInit | undefined, message: PartialMessage<I>): Promise<StreamResponse<O>>; | ||
} | ||
/** | ||
* Options for a call. Every client should accept CallOptions as optional | ||
* argument in its RPC methods. | ||
*/ | ||
export interface CallOptions { | ||
/** | ||
* Timeout in milliseconds. | ||
* Call a unary RPC - a method that takes a single input message, and | ||
* responds with a single output message. | ||
*/ | ||
timeoutMs?: number; | ||
unary<I extends Message<I> = AnyMessage, O extends Message<O> = AnyMessage>(service: ServiceType, method: MethodInfo<I, O>, signal: AbortSignal | undefined, timeoutMs: number | undefined, header: HeadersInit | undefined, message: PartialMessage<I>): Promise<UnaryResponse<O>>; | ||
/** | ||
* Custom headers to send with the request. | ||
* Call a server-streaming RPC - a method that takes a single input message, | ||
* and responds with zero or more output messages. | ||
*/ | ||
headers?: HeadersInit; | ||
/** | ||
* An optional AbortSignal to cancel the call. | ||
* If cancelled, an error with Code.Canceled is raised. | ||
*/ | ||
signal?: AbortSignal; | ||
/** | ||
* Called when response headers are received. | ||
*/ | ||
onHeader?(headers: Headers): void; | ||
/** | ||
* Called when response trailers are received. | ||
*/ | ||
onTrailer?(trailers: Headers): void; | ||
serverStream<I extends Message<I> = AnyMessage, O extends Message<O> = AnyMessage>(service: ServiceType, method: MethodInfo<I, O>, signal: AbortSignal | undefined, timeoutMs: number | undefined, header: HeadersInit | undefined, message: PartialMessage<I>): Promise<StreamResponse<O>>; | ||
} |
{ | ||
"name": "@bufbuild/connect-web", | ||
"version": "0.0.9", | ||
"version": "0.0.10", | ||
"license": "Apache-2.0", | ||
@@ -24,4 +24,5 @@ "repository": { | ||
}, | ||
"main": "./dist/esm/index.js", | ||
"peerDependencies": { | ||
"@bufbuild/protobuf": "^0.0.7" | ||
"@bufbuild/protobuf": "^0.0.9" | ||
}, | ||
@@ -28,0 +29,0 @@ "devDependencies": { |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
No README
QualityPackage does not have a README. This may indicate a failed publish or a low quality package.
Found 1 instance in 1 package
150377
45
3458
0
42