initial commit of actions

This commit is contained in:
Dominik Polakovics Polakovics 2026-01-31 18:56:04 +01:00
commit 949ece5785
44660 changed files with 12034344 additions and 0 deletions

View file

@ -0,0 +1,2 @@
import { Gateway } from "../index";
export declare function createGateway(): Gateway;

View file

@ -0,0 +1,47 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.createGateway = void 0;
const index_1 = require("../index");
const path_to_regexp_1 = require("path-to-regexp");
function createGateway() {
return new index_1.Gateway({
post: [
{
packageName: "twirp.example.haberdasher",
methodName: "MakeHat",
serviceName: "Haberdasher",
httpMethod: "post",
matchingPath: "/hat{:query_string(\\?.*)}?",
matcher: path_to_regexp_1.match("/hat{:query_string(\\?.*)}?"),
bodyKey: "*",
responseBodyKey: "",
},
],
get: [
{
packageName: "twirp.example.haberdasher",
methodName: "FindHat",
serviceName: "Haberdasher",
httpMethod: "get",
matchingPath: "/hat/{:hat_id}{:query_string(\\?.*)}?",
matcher: path_to_regexp_1.match("/hat/{:hat_id}{:query_string(\\?.*)}?"),
bodyKey: "",
responseBodyKey: "",
},
{
packageName: "twirp.example.haberdasher",
methodName: "ListHat",
serviceName: "Haberdasher",
httpMethod: "get",
matchingPath: "/hat{:query_string(\\?.*)}?",
matcher: path_to_regexp_1.match("/hat{:query_string(\\?.*)}?"),
bodyKey: "",
responseBodyKey: "",
},
],
put: [],
patch: [],
delete: [],
});
}
exports.createGateway = createGateway;

View file

@ -0,0 +1,147 @@
import { ServiceType } from "@protobuf-ts/runtime-rpc";
import type { BinaryWriteOptions } from "@protobuf-ts/runtime";
import type { IBinaryWriter } from "@protobuf-ts/runtime";
import type { BinaryReadOptions } from "@protobuf-ts/runtime";
import type { IBinaryReader } from "@protobuf-ts/runtime";
import { MessageType } from "@protobuf-ts/runtime";
/**
* @generated from protobuf message twirp.example.haberdasher.FindHatRPC
*/
export interface FindHatRPC {
/**
* @generated from protobuf field: string hat_id = 1;
*/
hatId: string;
}
/**
* @generated from protobuf message twirp.example.haberdasher.ListHatRPC
*/
export interface ListHatRPC {
/**
* @generated from protobuf field: repeated twirp.example.haberdasher.Filter filters = 1;
*/
filters: Filter[];
}
/**
* Size of a Hat, in inches.
*
* @generated from protobuf message twirp.example.haberdasher.Size
*/
export interface Size {
/**
* @generated from protobuf field: int32 inches = 1;
*/
inches: number;
}
/**
* A Hat is a piece of headwear made by a Haberdasher.
*
* @generated from protobuf message twirp.example.haberdasher.Hat
*/
export interface Hat {
/**
* @generated from protobuf field: string id = 1;
*/
id: string;
/**
* @generated from protobuf field: int32 inches = 2;
*/
inches: number;
/**
* @generated from protobuf field: string color = 3;
*/
color: string;
/**
* @generated from protobuf field: string name = 4;
*/
name: string;
/**
* @generated from protobuf field: repeated twirp.example.haberdasher.Hat variants = 5;
*/
variants: Hat[];
}
/**
* @generated from protobuf message twirp.example.haberdasher.Filter
*/
export interface Filter {
/**
* @generated from protobuf field: string order_by = 1;
*/
orderBy: string;
/**
* @generated from protobuf field: twirp.example.haberdasher.Pagination pagination = 2;
*/
pagination?: Pagination;
}
/**
* @generated from protobuf message twirp.example.haberdasher.Pagination
*/
export interface Pagination {
/**
* @generated from protobuf field: int32 limit = 1;
*/
limit: number;
/**
* @generated from protobuf field: int32 offset = 2;
*/
offset: number;
}
declare class FindHatRPC$Type extends MessageType<FindHatRPC> {
constructor();
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: FindHatRPC): FindHatRPC;
internalBinaryWrite(message: FindHatRPC, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter;
}
/**
* @generated MessageType for protobuf message twirp.example.haberdasher.FindHatRPC
*/
export declare const FindHatRPC: FindHatRPC$Type;
declare class ListHatRPC$Type extends MessageType<ListHatRPC> {
constructor();
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: ListHatRPC): ListHatRPC;
internalBinaryWrite(message: ListHatRPC, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter;
}
/**
* @generated MessageType for protobuf message twirp.example.haberdasher.ListHatRPC
*/
export declare const ListHatRPC: ListHatRPC$Type;
declare class Size$Type extends MessageType<Size> {
constructor();
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: Size): Size;
internalBinaryWrite(message: Size, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter;
}
/**
* @generated MessageType for protobuf message twirp.example.haberdasher.Size
*/
export declare const Size: Size$Type;
declare class Hat$Type extends MessageType<Hat> {
constructor();
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: Hat): Hat;
internalBinaryWrite(message: Hat, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter;
}
/**
* @generated MessageType for protobuf message twirp.example.haberdasher.Hat
*/
export declare const Hat: Hat$Type;
declare class Filter$Type extends MessageType<Filter> {
constructor();
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: Filter): Filter;
internalBinaryWrite(message: Filter, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter;
}
/**
* @generated MessageType for protobuf message twirp.example.haberdasher.Filter
*/
export declare const Filter: Filter$Type;
declare class Pagination$Type extends MessageType<Pagination> {
constructor();
internalBinaryRead(reader: IBinaryReader, length: number, options: BinaryReadOptions, target?: Pagination): Pagination;
internalBinaryWrite(message: Pagination, writer: IBinaryWriter, options: BinaryWriteOptions): IBinaryWriter;
}
/**
* @generated MessageType for protobuf message twirp.example.haberdasher.Pagination
*/
export declare const Pagination: Pagination$Type;
/**
* @generated ServiceType for protobuf service twirp.example.haberdasher.Haberdasher
*/
export declare const Haberdasher: ServiceType;
export {};

View file

@ -0,0 +1,300 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Haberdasher = exports.Pagination = exports.Filter = exports.Hat = exports.Size = exports.ListHatRPC = exports.FindHatRPC = void 0;
// @generated by protobuf-ts 2.0.0-alpha.27 with parameters client_none,generate_dependencies
// @generated from protobuf file "service.proto" (package "twirp.example.haberdasher", syntax proto3)
// tslint:disable
const runtime_rpc_1 = require("@protobuf-ts/runtime-rpc");
const runtime_1 = require("@protobuf-ts/runtime");
const runtime_2 = require("@protobuf-ts/runtime");
const runtime_3 = require("@protobuf-ts/runtime");
// @generated message type with reflection information, may provide speed optimized methods
class FindHatRPC$Type extends runtime_3.MessageType {
constructor() {
super("twirp.example.haberdasher.FindHatRPC", [
{ no: 1, name: "hat_id", kind: "scalar", T: 9 /*ScalarType.STRING*/ }
]);
}
internalBinaryRead(reader, length, options, target) {
let message = target !== null && target !== void 0 ? target : this.create(), end = reader.pos + length;
while (reader.pos < end) {
let [fieldNo, wireType] = reader.tag();
switch (fieldNo) {
case /* string hat_id */ 1:
message.hatId = reader.string();
break;
default:
let u = options.readUnknownField;
if (u === "throw")
throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`);
let d = reader.skip(wireType);
if (u !== false)
(u === true ? runtime_2.UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d);
}
}
return message;
}
internalBinaryWrite(message, writer, options) {
/* string hat_id = 1; */
if (message.hatId !== "")
writer.tag(1, runtime_1.WireType.LengthDelimited).string(message.hatId);
let u = options.writeUnknownFields;
if (u !== false)
(u == true ? runtime_2.UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
return writer;
}
}
/**
* @generated MessageType for protobuf message twirp.example.haberdasher.FindHatRPC
*/
exports.FindHatRPC = new FindHatRPC$Type();
// @generated message type with reflection information, may provide speed optimized methods
class ListHatRPC$Type extends runtime_3.MessageType {
constructor() {
super("twirp.example.haberdasher.ListHatRPC", [
{ no: 1, name: "filters", kind: "message", repeat: 1 /*RepeatType.PACKED*/, T: () => exports.Filter }
]);
}
internalBinaryRead(reader, length, options, target) {
let message = target !== null && target !== void 0 ? target : this.create(), end = reader.pos + length;
while (reader.pos < end) {
let [fieldNo, wireType] = reader.tag();
switch (fieldNo) {
case /* repeated twirp.example.haberdasher.Filter filters */ 1:
message.filters.push(exports.Filter.internalBinaryRead(reader, reader.uint32(), options));
break;
default:
let u = options.readUnknownField;
if (u === "throw")
throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`);
let d = reader.skip(wireType);
if (u !== false)
(u === true ? runtime_2.UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d);
}
}
return message;
}
internalBinaryWrite(message, writer, options) {
/* repeated twirp.example.haberdasher.Filter filters = 1; */
for (let i = 0; i < message.filters.length; i++)
exports.Filter.internalBinaryWrite(message.filters[i], writer.tag(1, runtime_1.WireType.LengthDelimited).fork(), options).join();
let u = options.writeUnknownFields;
if (u !== false)
(u == true ? runtime_2.UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
return writer;
}
}
/**
* @generated MessageType for protobuf message twirp.example.haberdasher.ListHatRPC
*/
exports.ListHatRPC = new ListHatRPC$Type();
// @generated message type with reflection information, may provide speed optimized methods
class Size$Type extends runtime_3.MessageType {
constructor() {
super("twirp.example.haberdasher.Size", [
{ no: 1, name: "inches", kind: "scalar", T: 5 /*ScalarType.INT32*/ }
]);
}
internalBinaryRead(reader, length, options, target) {
let message = target !== null && target !== void 0 ? target : this.create(), end = reader.pos + length;
while (reader.pos < end) {
let [fieldNo, wireType] = reader.tag();
switch (fieldNo) {
case /* int32 inches */ 1:
message.inches = reader.int32();
break;
default:
let u = options.readUnknownField;
if (u === "throw")
throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`);
let d = reader.skip(wireType);
if (u !== false)
(u === true ? runtime_2.UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d);
}
}
return message;
}
internalBinaryWrite(message, writer, options) {
/* int32 inches = 1; */
if (message.inches !== 0)
writer.tag(1, runtime_1.WireType.Varint).int32(message.inches);
let u = options.writeUnknownFields;
if (u !== false)
(u == true ? runtime_2.UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
return writer;
}
}
/**
* @generated MessageType for protobuf message twirp.example.haberdasher.Size
*/
exports.Size = new Size$Type();
// @generated message type with reflection information, may provide speed optimized methods
class Hat$Type extends runtime_3.MessageType {
constructor() {
super("twirp.example.haberdasher.Hat", [
{ no: 1, name: "id", kind: "scalar", T: 9 /*ScalarType.STRING*/ },
{ no: 2, name: "inches", kind: "scalar", T: 5 /*ScalarType.INT32*/ },
{ no: 3, name: "color", kind: "scalar", T: 9 /*ScalarType.STRING*/ },
{ no: 4, name: "name", kind: "scalar", T: 9 /*ScalarType.STRING*/ },
{ no: 5, name: "variants", kind: "message", repeat: 1 /*RepeatType.PACKED*/, T: () => exports.Hat }
]);
}
internalBinaryRead(reader, length, options, target) {
let message = target !== null && target !== void 0 ? target : this.create(), end = reader.pos + length;
while (reader.pos < end) {
let [fieldNo, wireType] = reader.tag();
switch (fieldNo) {
case /* string id */ 1:
message.id = reader.string();
break;
case /* int32 inches */ 2:
message.inches = reader.int32();
break;
case /* string color */ 3:
message.color = reader.string();
break;
case /* string name */ 4:
message.name = reader.string();
break;
case /* repeated twirp.example.haberdasher.Hat variants */ 5:
message.variants.push(exports.Hat.internalBinaryRead(reader, reader.uint32(), options));
break;
default:
let u = options.readUnknownField;
if (u === "throw")
throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`);
let d = reader.skip(wireType);
if (u !== false)
(u === true ? runtime_2.UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d);
}
}
return message;
}
internalBinaryWrite(message, writer, options) {
/* string id = 1; */
if (message.id !== "")
writer.tag(1, runtime_1.WireType.LengthDelimited).string(message.id);
/* int32 inches = 2; */
if (message.inches !== 0)
writer.tag(2, runtime_1.WireType.Varint).int32(message.inches);
/* string color = 3; */
if (message.color !== "")
writer.tag(3, runtime_1.WireType.LengthDelimited).string(message.color);
/* string name = 4; */
if (message.name !== "")
writer.tag(4, runtime_1.WireType.LengthDelimited).string(message.name);
/* repeated twirp.example.haberdasher.Hat variants = 5; */
for (let i = 0; i < message.variants.length; i++)
exports.Hat.internalBinaryWrite(message.variants[i], writer.tag(5, runtime_1.WireType.LengthDelimited).fork(), options).join();
let u = options.writeUnknownFields;
if (u !== false)
(u == true ? runtime_2.UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
return writer;
}
}
/**
* @generated MessageType for protobuf message twirp.example.haberdasher.Hat
*/
exports.Hat = new Hat$Type();
// @generated message type with reflection information, may provide speed optimized methods
class Filter$Type extends runtime_3.MessageType {
constructor() {
super("twirp.example.haberdasher.Filter", [
{ no: 1, name: "order_by", kind: "scalar", T: 9 /*ScalarType.STRING*/ },
{ no: 2, name: "pagination", kind: "message", T: () => exports.Pagination }
]);
}
internalBinaryRead(reader, length, options, target) {
let message = target !== null && target !== void 0 ? target : this.create(), end = reader.pos + length;
while (reader.pos < end) {
let [fieldNo, wireType] = reader.tag();
switch (fieldNo) {
case /* string order_by */ 1:
message.orderBy = reader.string();
break;
case /* twirp.example.haberdasher.Pagination pagination */ 2:
message.pagination = exports.Pagination.internalBinaryRead(reader, reader.uint32(), options, message.pagination);
break;
default:
let u = options.readUnknownField;
if (u === "throw")
throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`);
let d = reader.skip(wireType);
if (u !== false)
(u === true ? runtime_2.UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d);
}
}
return message;
}
internalBinaryWrite(message, writer, options) {
/* string order_by = 1; */
if (message.orderBy !== "")
writer.tag(1, runtime_1.WireType.LengthDelimited).string(message.orderBy);
/* twirp.example.haberdasher.Pagination pagination = 2; */
if (message.pagination)
exports.Pagination.internalBinaryWrite(message.pagination, writer.tag(2, runtime_1.WireType.LengthDelimited).fork(), options).join();
let u = options.writeUnknownFields;
if (u !== false)
(u == true ? runtime_2.UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
return writer;
}
}
/**
* @generated MessageType for protobuf message twirp.example.haberdasher.Filter
*/
exports.Filter = new Filter$Type();
// @generated message type with reflection information, may provide speed optimized methods
class Pagination$Type extends runtime_3.MessageType {
constructor() {
super("twirp.example.haberdasher.Pagination", [
{ no: 1, name: "limit", kind: "scalar", T: 5 /*ScalarType.INT32*/ },
{ no: 2, name: "offset", kind: "scalar", T: 5 /*ScalarType.INT32*/ }
]);
}
internalBinaryRead(reader, length, options, target) {
let message = target !== null && target !== void 0 ? target : this.create(), end = reader.pos + length;
while (reader.pos < end) {
let [fieldNo, wireType] = reader.tag();
switch (fieldNo) {
case /* int32 limit */ 1:
message.limit = reader.int32();
break;
case /* int32 offset */ 2:
message.offset = reader.int32();
break;
default:
let u = options.readUnknownField;
if (u === "throw")
throw new globalThis.Error(`Unknown field ${fieldNo} (wire type ${wireType}) for ${this.typeName}`);
let d = reader.skip(wireType);
if (u !== false)
(u === true ? runtime_2.UnknownFieldHandler.onRead : u)(this.typeName, message, fieldNo, wireType, d);
}
}
return message;
}
internalBinaryWrite(message, writer, options) {
/* int32 limit = 1; */
if (message.limit !== 0)
writer.tag(1, runtime_1.WireType.Varint).int32(message.limit);
/* int32 offset = 2; */
if (message.offset !== 0)
writer.tag(2, runtime_1.WireType.Varint).int32(message.offset);
let u = options.writeUnknownFields;
if (u !== false)
(u == true ? runtime_2.UnknownFieldHandler.onWrite : u)(this.typeName, message, writer);
return writer;
}
}
/**
* @generated MessageType for protobuf message twirp.example.haberdasher.Pagination
*/
exports.Pagination = new Pagination$Type();
/**
* @generated ServiceType for protobuf service twirp.example.haberdasher.Haberdasher
*/
exports.Haberdasher = new runtime_rpc_1.ServiceType("twirp.example.haberdasher.Haberdasher", [
{ name: "MakeHat", options: { "google.api.http": { body: "*", post: "/hat" } }, I: exports.Size, O: exports.Hat },
{ name: "FindHat", options: { "google.api.http": { get: "/hat/{hat_id}" } }, I: exports.FindHatRPC, O: exports.FindHatRPC },
{ name: "ListHat", options: { "google.api.http": { get: "/hat" } }, I: exports.ListHatRPC, O: exports.ListHatRPC }
]);

View file

@ -0,0 +1,38 @@
/// <reference types="node" />
import { TwirpContext, TwirpServer } from "../index";
import { Size, Hat, FindHatRPC, ListHatRPC } from "./service";
interface Rpc {
request(service: string, method: string, contentType: "application/json" | "application/protobuf", data: object | Uint8Array): Promise<object | Uint8Array>;
}
export interface HaberdasherClient {
MakeHat(request: Size): Promise<Hat>;
FindHat(request: FindHatRPC): Promise<FindHatRPC>;
ListHat(request: ListHatRPC): Promise<ListHatRPC>;
}
export declare class HaberdasherClientJSON implements HaberdasherClient {
private readonly rpc;
constructor(rpc: Rpc);
MakeHat(request: Size): Promise<Hat>;
FindHat(request: FindHatRPC): Promise<FindHatRPC>;
ListHat(request: ListHatRPC): Promise<ListHatRPC>;
}
export declare class HaberdasherClientProtobuf implements HaberdasherClient {
private readonly rpc;
constructor(rpc: Rpc);
MakeHat(request: Size): Promise<Hat>;
FindHat(request: FindHatRPC): Promise<FindHatRPC>;
ListHat(request: ListHatRPC): Promise<ListHatRPC>;
}
export interface HaberdasherTwirp<T extends TwirpContext = TwirpContext> {
MakeHat(ctx: T, request: Size): Promise<Hat>;
FindHat(ctx: T, request: FindHatRPC): Promise<FindHatRPC>;
ListHat(ctx: T, request: ListHatRPC): Promise<ListHatRPC>;
}
export declare enum HaberdasherMethod {
MakeHat = "MakeHat",
FindHat = "FindHat",
ListHat = "ListHat"
}
export declare const HaberdasherMethodList: HaberdasherMethod[];
export declare function createHaberdasherServer<T extends TwirpContext = TwirpContext>(service: HaberdasherTwirp<T>): TwirpServer<HaberdasherTwirp<TwirpContext<import("http").IncomingMessage, import("http").ServerResponse>>, T>;
export {};

View file

@ -0,0 +1,313 @@
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.createHaberdasherServer = exports.HaberdasherMethodList = exports.HaberdasherMethod = exports.HaberdasherClientProtobuf = exports.HaberdasherClientJSON = void 0;
const index_1 = require("../index");
const service_1 = require("./service");
class HaberdasherClientJSON {
constructor(rpc) {
this.rpc = rpc;
this.MakeHat.bind(this);
this.FindHat.bind(this);
this.ListHat.bind(this);
}
MakeHat(request) {
const data = service_1.Size.toJson(request, {
useProtoFieldName: true,
emitDefaultValues: false,
});
const promise = this.rpc.request("twirp.example.haberdasher.Haberdasher", "MakeHat", "application/json", data);
return promise.then((data) => service_1.Hat.fromJson(data, { ignoreUnknownFields: true }));
}
FindHat(request) {
const data = service_1.FindHatRPC.toJson(request, {
useProtoFieldName: true,
emitDefaultValues: false,
});
const promise = this.rpc.request("twirp.example.haberdasher.Haberdasher", "FindHat", "application/json", data);
return promise.then((data) => service_1.FindHatRPC.fromJson(data, { ignoreUnknownFields: true }));
}
ListHat(request) {
const data = service_1.ListHatRPC.toJson(request, {
useProtoFieldName: true,
emitDefaultValues: false,
});
const promise = this.rpc.request("twirp.example.haberdasher.Haberdasher", "ListHat", "application/json", data);
return promise.then((data) => service_1.ListHatRPC.fromJson(data, { ignoreUnknownFields: true }));
}
}
exports.HaberdasherClientJSON = HaberdasherClientJSON;
class HaberdasherClientProtobuf {
constructor(rpc) {
this.rpc = rpc;
this.MakeHat.bind(this);
this.FindHat.bind(this);
this.ListHat.bind(this);
}
MakeHat(request) {
const data = service_1.Size.toBinary(request);
const promise = this.rpc.request("twirp.example.haberdasher.Haberdasher", "MakeHat", "application/protobuf", data);
return promise.then((data) => service_1.Hat.fromBinary(data));
}
FindHat(request) {
const data = service_1.FindHatRPC.toBinary(request);
const promise = this.rpc.request("twirp.example.haberdasher.Haberdasher", "FindHat", "application/protobuf", data);
return promise.then((data) => service_1.FindHatRPC.fromBinary(data));
}
ListHat(request) {
const data = service_1.ListHatRPC.toBinary(request);
const promise = this.rpc.request("twirp.example.haberdasher.Haberdasher", "ListHat", "application/protobuf", data);
return promise.then((data) => service_1.ListHatRPC.fromBinary(data));
}
}
exports.HaberdasherClientProtobuf = HaberdasherClientProtobuf;
var HaberdasherMethod;
(function (HaberdasherMethod) {
HaberdasherMethod["MakeHat"] = "MakeHat";
HaberdasherMethod["FindHat"] = "FindHat";
HaberdasherMethod["ListHat"] = "ListHat";
})(HaberdasherMethod = exports.HaberdasherMethod || (exports.HaberdasherMethod = {}));
exports.HaberdasherMethodList = [
HaberdasherMethod.MakeHat,
HaberdasherMethod.FindHat,
HaberdasherMethod.ListHat,
];
function createHaberdasherServer(service) {
return new index_1.TwirpServer({
service,
packageName: "twirp.example.haberdasher",
serviceName: "Haberdasher",
methodList: exports.HaberdasherMethodList,
matchRoute: matchHaberdasherRoute,
});
}
exports.createHaberdasherServer = createHaberdasherServer;
function matchHaberdasherRoute(method, events) {
switch (method) {
case "MakeHat":
return (ctx, service, data, interceptors) => __awaiter(this, void 0, void 0, function* () {
ctx = Object.assign(Object.assign({}, ctx), { methodName: "MakeHat" });
yield events.onMatch(ctx);
return handleHaberdasherMakeHatRequest(ctx, service, data, interceptors);
});
case "FindHat":
return (ctx, service, data, interceptors) => __awaiter(this, void 0, void 0, function* () {
ctx = Object.assign(Object.assign({}, ctx), { methodName: "FindHat" });
yield events.onMatch(ctx);
return handleHaberdasherFindHatRequest(ctx, service, data, interceptors);
});
case "ListHat":
return (ctx, service, data, interceptors) => __awaiter(this, void 0, void 0, function* () {
ctx = Object.assign(Object.assign({}, ctx), { methodName: "ListHat" });
yield events.onMatch(ctx);
return handleHaberdasherListHatRequest(ctx, service, data, interceptors);
});
default:
events.onNotFound();
const msg = `no handler found`;
throw new index_1.TwirpError(index_1.TwirpErrorCode.BadRoute, msg);
}
}
function handleHaberdasherMakeHatRequest(ctx, service, data, interceptors) {
switch (ctx.contentType) {
case index_1.TwirpContentType.JSON:
return handleHaberdasherMakeHatJSON(ctx, service, data, interceptors);
case index_1.TwirpContentType.Protobuf:
return handleHaberdasherMakeHatProtobuf(ctx, service, data, interceptors);
default:
const msg = "unexpected Content-Type";
throw new index_1.TwirpError(index_1.TwirpErrorCode.BadRoute, msg);
}
}
function handleHaberdasherFindHatRequest(ctx, service, data, interceptors) {
switch (ctx.contentType) {
case index_1.TwirpContentType.JSON:
return handleHaberdasherFindHatJSON(ctx, service, data, interceptors);
case index_1.TwirpContentType.Protobuf:
return handleHaberdasherFindHatProtobuf(ctx, service, data, interceptors);
default:
const msg = "unexpected Content-Type";
throw new index_1.TwirpError(index_1.TwirpErrorCode.BadRoute, msg);
}
}
function handleHaberdasherListHatRequest(ctx, service, data, interceptors) {
switch (ctx.contentType) {
case index_1.TwirpContentType.JSON:
return handleHaberdasherListHatJSON(ctx, service, data, interceptors);
case index_1.TwirpContentType.Protobuf:
return handleHaberdasherListHatProtobuf(ctx, service, data, interceptors);
default:
const msg = "unexpected Content-Type";
throw new index_1.TwirpError(index_1.TwirpErrorCode.BadRoute, msg);
}
}
function handleHaberdasherMakeHatJSON(ctx, service, data, interceptors) {
return __awaiter(this, void 0, void 0, function* () {
let request;
let response;
try {
const body = JSON.parse(data.toString() || "{}");
request = service_1.Size.fromJson(body, { ignoreUnknownFields: true });
}
catch (e) {
if (e instanceof Error) {
const msg = "the json request could not be decoded";
throw new index_1.TwirpError(index_1.TwirpErrorCode.Malformed, msg).withCause(e, true);
}
}
if (interceptors && interceptors.length > 0) {
const interceptor = index_1.chainInterceptors(...interceptors);
response = yield interceptor(ctx, request, (ctx, inputReq) => {
return service.MakeHat(ctx, inputReq);
});
}
else {
response = yield service.MakeHat(ctx, request);
}
return JSON.stringify(service_1.Hat.toJson(response, {
useProtoFieldName: true,
emitDefaultValues: false,
}));
});
}
function handleHaberdasherFindHatJSON(ctx, service, data, interceptors) {
return __awaiter(this, void 0, void 0, function* () {
let request;
let response;
try {
const body = JSON.parse(data.toString() || "{}");
request = service_1.FindHatRPC.fromJson(body, { ignoreUnknownFields: true });
}
catch (e) {
if (e instanceof Error) {
const msg = "the json request could not be decoded";
throw new index_1.TwirpError(index_1.TwirpErrorCode.Malformed, msg).withCause(e, true);
}
}
if (interceptors && interceptors.length > 0) {
const interceptor = index_1.chainInterceptors(...interceptors);
response = yield interceptor(ctx, request, (ctx, inputReq) => {
return service.FindHat(ctx, inputReq);
});
}
else {
response = yield service.FindHat(ctx, request);
}
return JSON.stringify(service_1.FindHatRPC.toJson(response, {
useProtoFieldName: true,
emitDefaultValues: false,
}));
});
}
function handleHaberdasherListHatJSON(ctx, service, data, interceptors) {
return __awaiter(this, void 0, void 0, function* () {
let request;
let response;
try {
const body = JSON.parse(data.toString() || "{}");
request = service_1.ListHatRPC.fromJson(body, { ignoreUnknownFields: true });
}
catch (e) {
if (e instanceof Error) {
const msg = "the json request could not be decoded";
throw new index_1.TwirpError(index_1.TwirpErrorCode.Malformed, msg).withCause(e, true);
}
}
if (interceptors && interceptors.length > 0) {
const interceptor = index_1.chainInterceptors(...interceptors);
response = yield interceptor(ctx, request, (ctx, inputReq) => {
return service.ListHat(ctx, inputReq);
});
}
else {
response = yield service.ListHat(ctx, request);
}
return JSON.stringify(service_1.ListHatRPC.toJson(response, {
useProtoFieldName: true,
emitDefaultValues: false,
}));
});
}
function handleHaberdasherMakeHatProtobuf(ctx, service, data, interceptors) {
return __awaiter(this, void 0, void 0, function* () {
let request;
let response;
try {
request = service_1.Size.fromBinary(data);
}
catch (e) {
if (e instanceof Error) {
const msg = "the protobuf request could not be decoded";
throw new index_1.TwirpError(index_1.TwirpErrorCode.Malformed, msg).withCause(e, true);
}
}
if (interceptors && interceptors.length > 0) {
const interceptor = index_1.chainInterceptors(...interceptors);
response = yield interceptor(ctx, request, (ctx, inputReq) => {
return service.MakeHat(ctx, inputReq);
});
}
else {
response = yield service.MakeHat(ctx, request);
}
return Buffer.from(service_1.Hat.toBinary(response));
});
}
function handleHaberdasherFindHatProtobuf(ctx, service, data, interceptors) {
return __awaiter(this, void 0, void 0, function* () {
let request;
let response;
try {
request = service_1.FindHatRPC.fromBinary(data);
}
catch (e) {
if (e instanceof Error) {
const msg = "the protobuf request could not be decoded";
throw new index_1.TwirpError(index_1.TwirpErrorCode.Malformed, msg).withCause(e, true);
}
}
if (interceptors && interceptors.length > 0) {
const interceptor = index_1.chainInterceptors(...interceptors);
response = yield interceptor(ctx, request, (ctx, inputReq) => {
return service.FindHat(ctx, inputReq);
});
}
else {
response = yield service.FindHat(ctx, request);
}
return Buffer.from(service_1.FindHatRPC.toBinary(response));
});
}
function handleHaberdasherListHatProtobuf(ctx, service, data, interceptors) {
return __awaiter(this, void 0, void 0, function* () {
let request;
let response;
try {
request = service_1.ListHatRPC.fromBinary(data);
}
catch (e) {
if (e instanceof Error) {
const msg = "the protobuf request could not be decoded";
throw new index_1.TwirpError(index_1.TwirpErrorCode.Malformed, msg).withCause(e, true);
}
}
if (interceptors && interceptors.length > 0) {
const interceptor = index_1.chainInterceptors(...interceptors);
response = yield interceptor(ctx, request, (ctx, inputReq) => {
return service.ListHat(ctx, inputReq);
});
}
else {
response = yield service.ListHat(ctx, request);
}
return Buffer.from(service_1.ListHatRPC.toBinary(response));
});
}

View file

@ -0,0 +1 @@
export {};

View file

@ -0,0 +1,160 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
const http = __importStar(require("http"));
const http_terminator_1 = require("http-terminator");
const service_twirp_1 = require("../__mocks__/service.twirp");
const service_1 = require("../__mocks__/service");
const http_client_1 = require("../http.client");
const errors_1 = require("../errors");
describe("Twirp Clients", () => {
let httpTerminator;
let server;
beforeEach(() => {
const twirpServer = service_twirp_1.createHaberdasherServer({
MakeHat(ctx, request) {
return __awaiter(this, void 0, void 0, function* () {
return service_1.Hat.create({
id: "1",
name: "cap",
color: "blue",
inches: 100,
variants: [],
});
});
},
FindHat(ctx, request) {
return __awaiter(this, void 0, void 0, function* () {
return request;
});
},
ListHat(ctx, request) {
return __awaiter(this, void 0, void 0, function* () {
return request;
});
}
});
server = http.createServer(twirpServer.httpHandler());
httpTerminator = http_terminator_1.createHttpTerminator({
server,
});
});
it("can call methods using the JSON client", (done) => {
const port = 9999;
server.listen(port, () => __awaiter(void 0, void 0, void 0, function* () {
const client = new service_twirp_1.HaberdasherClientJSON(http_client_1.NodeHttpRPC({
baseUrl: "http://localhost:9999/twirp",
}));
const hat = yield client.MakeHat({
inches: 1,
});
expect(hat).toEqual({
id: "1",
color: "blue",
inches: 100,
name: "cap",
variants: [],
});
yield httpTerminator.terminate();
done();
}));
});
it("can call methods using the Protobuf client", (done) => {
const port = 9999;
server.listen(port, () => __awaiter(void 0, void 0, void 0, function* () {
const client = new service_twirp_1.HaberdasherClientProtobuf(http_client_1.NodeHttpRPC({
baseUrl: "http://localhost:9999/twirp",
}));
const hat = yield client.MakeHat({
inches: 1,
});
expect(hat).toEqual({
id: "1",
color: "blue",
inches: 100,
name: "cap",
variants: [],
});
yield httpTerminator.terminate();
done();
}));
});
it("will return a TwripError when a error occur", (done) => {
const twirpServer = service_twirp_1.createHaberdasherServer({
MakeHat(ctx, request) {
return __awaiter(this, void 0, void 0, function* () {
const error = new errors_1.InternalServerError("error");
error.withMeta("test", "msg");
error.withMeta("test2", "msg2");
throw error;
});
},
FindHat(ctx, request) {
return __awaiter(this, void 0, void 0, function* () {
return request;
});
},
ListHat(ctx, request) {
return __awaiter(this, void 0, void 0, function* () {
return request;
});
}
});
server = http.createServer(twirpServer.httpHandler());
httpTerminator = http_terminator_1.createHttpTerminator({
server,
});
const port = 9999;
server.listen(port, () => __awaiter(void 0, void 0, void 0, function* () {
const client = new service_twirp_1.HaberdasherClientProtobuf(http_client_1.NodeHttpRPC({
baseUrl: "http://localhost:9999/twirp",
}));
let err;
try {
yield client.MakeHat({
inches: 1,
});
}
catch (e) {
err = e;
}
expect(err).toBeInstanceOf(errors_1.TwirpError);
const twirpErr = err;
expect(twirpErr.code).toEqual(errors_1.TwirpErrorCode.Internal);
expect(twirpErr.msg).toEqual("error");
expect(twirpErr.meta).toEqual({
test: "msg",
test2: "msg2"
});
yield httpTerminator.terminate();
done();
}));
});
});

View file

@ -0,0 +1 @@
export {};

View file

@ -0,0 +1,70 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const errors_1 = require("../errors");
describe("Twirp errors", () => {
it("will render a full error", () => {
const innerError = new Error("some error");
const twirpError = new errors_1.TwirpError(errors_1.TwirpErrorCode.NotFound, "not found");
twirpError.withCause(innerError, true);
twirpError.withMeta("meta1", "value1");
twirpError.withMeta("meta2", "value2");
expect(twirpError.toJSON()).toEqual(JSON.stringify({
code: errors_1.TwirpErrorCode.NotFound,
msg: "not found",
meta: {
cause: "some error",
meta1: "value1",
meta2: "value2",
}
}));
});
});
describe("Standard Errors", () => {
it("will render not found error", () => {
const twirpError = new errors_1.NotFoundError("not found");
expect(twirpError.toJSON()).toEqual(JSON.stringify({
code: errors_1.TwirpErrorCode.NotFound,
msg: "not found",
meta: {}
}));
});
it("will render invalid argument error", () => {
const twirpError = new errors_1.InvalidArgumentError("field", "error");
expect(twirpError.toJSON()).toEqual(JSON.stringify({
code: errors_1.TwirpErrorCode.InvalidArgument,
msg: "field error",
meta: {
argument: "field",
}
}));
});
it("will render required error", () => {
const twirpError = new errors_1.RequiredArgumentError("field");
expect(twirpError.toJSON()).toEqual(JSON.stringify({
code: errors_1.TwirpErrorCode.InvalidArgument,
msg: "field is required",
meta: {
argument: "field",
}
}));
});
it("will render internal server error", () => {
const twirpError = new errors_1.InternalServerError("internal");
expect(twirpError.toJSON()).toEqual(JSON.stringify({
code: errors_1.TwirpErrorCode.Internal,
msg: "internal",
meta: {}
}));
});
it("will render internal server error with inner", () => {
const inner = new Error("inner");
const twirpError = new errors_1.InternalServerErrorWith(inner);
expect(twirpError.toJSON()).toEqual(JSON.stringify({
code: errors_1.TwirpErrorCode.Internal,
msg: "inner",
meta: {
cause: "Error"
}
}));
});
});

View file

@ -0,0 +1 @@
export {};

View file

@ -0,0 +1,174 @@
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const http_1 = __importDefault(require("http"));
const service_twirp_1 = require("../__mocks__/service.twirp");
const service_1 = require("../__mocks__/service");
const gateway_twirp_1 = require("../__mocks__/gateway.twirp");
const supertest_1 = __importDefault(require("supertest"));
const http_terminator_1 = require("http-terminator");
describe("Gateway", () => {
let server;
let twirpServer;
let gateway;
beforeEach(() => {
twirpServer = service_twirp_1.createHaberdasherServer({
MakeHat(ctx, request) {
return __awaiter(this, void 0, void 0, function* () {
return service_1.Hat.create({
id: "1",
name: "cap",
color: "blue",
inches: request.inches,
});
});
},
FindHat(ctx, request) {
return __awaiter(this, void 0, void 0, function* () {
return request;
});
},
ListHat(ctx, request) {
return __awaiter(this, void 0, void 0, function* () {
return request;
});
}
});
gateway = gateway_twirp_1.createGateway();
const twirpRewrite = gateway.twirpRewrite();
server = http_1.default.createServer((req, resp) => {
twirpRewrite(req, resp, () => {
twirpServer.httpHandler()(req, resp);
});
});
});
it("call custom POST http endpoint that maps to MakeHat", () => __awaiter(void 0, void 0, void 0, function* () {
const response = yield supertest_1.default(server)
.post('/hat')
.send({
inches: 30,
})
.expect('Content-Type', "application/json")
.expect(200);
expect(response.body).toEqual({
id: "1",
name: "cap",
color: "blue",
inches: 30,
});
}));
it("will map url parameter to request message", () => __awaiter(void 0, void 0, void 0, function* () {
const response = yield supertest_1.default(server)
.get('/hat/12345')
.expect('Content-Type', "application/json")
.expect(200);
expect(response.body).toEqual({
hat_id: "12345",
});
}));
it("will map query string parameters to request message", () => __awaiter(void 0, void 0, void 0, function* () {
const response = yield supertest_1.default(server)
.get('/hat')
.query({
'filters[0].order_by': "desc",
'filters[0].pagination.limit': 10,
'filters[0].pagination.offset': 2,
'filters[1].order_by': "asc",
'filters[1].pagination.limit': 5,
'filters[1].pagination.offset': 6,
})
.expect('Content-Type', "application/json")
.expect(200);
expect(response.body).toEqual({
filters: [
{
order_by: "desc",
pagination: {
limit: 10,
offset: 2,
},
},
{
order_by: "asc",
pagination: {
limit: 5,
offset: 6,
},
}
]
});
}));
it("will do a reverse proxy request to the handler", (done) => {
const server = service_twirp_1.createHaberdasherServer({
MakeHat(ctx, request) {
return __awaiter(this, void 0, void 0, function* () {
return service_1.Hat.create({
id: "1",
name: "cap",
color: "blue",
inches: 100,
variants: [],
});
});
},
FindHat(ctx, request) {
return __awaiter(this, void 0, void 0, function* () {
return request;
});
},
ListHat(ctx, request) {
return __awaiter(this, void 0, void 0, function* () {
return request;
});
}
});
const gateway = gateway_twirp_1.createGateway();
const twirpServerPort = 9999;
const twirpServer = http_1.default.createServer(server.httpHandler());
const httpTerminator1 = http_terminator_1.createHttpTerminator({
server: twirpServer,
});
const gatewayServerPort = 9998;
const gatewayServer = http_1.default.createServer(gateway.reverseProxy({
baseUrl: "http://localhost:9999/twirp",
}));
const httpTerminator2 = http_terminator_1.createHttpTerminator({
server: gatewayServer,
});
// twirp server
twirpServer.listen(twirpServerPort, () => __awaiter(void 0, void 0, void 0, function* () {
// reverse proxy server
gatewayServer.listen(gatewayServerPort, () => __awaiter(void 0, void 0, void 0, function* () {
const response = yield supertest_1.default(gatewayServer)
.post('/hat')
.send({
inches: 30,
})
.expect('Content-Type', "application/json")
.expect(200);
expect(response.body).toEqual({
id: "1",
name: "cap",
color: "blue",
inches: 100,
});
yield Promise.all([
httpTerminator1.terminate(),
httpTerminator2.terminate(),
]);
done();
}));
}));
});
});

View file

@ -0,0 +1 @@
export {};

View file

@ -0,0 +1,76 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const hooks_1 = require("../hooks");
const errors_1 = require("../errors");
const request_1 = require("../request");
describe("Hooks behaviour", () => {
it("can chain multiple hooks together", () => {
const hook1Spy = jest.fn();
const hooks1 = {
requestReceived: (ctx) => {
hook1Spy("received");
},
requestRouted: (ctx) => {
hook1Spy("routed");
},
requestPrepared: (ctx) => {
hook1Spy("prepared");
},
requestSent: (ctx) => {
hook1Spy("sent");
},
responseSent: (ctx) => {
hook1Spy("sent");
},
responsePrepared: (ctx) => {
hook1Spy("prepared");
},
error: (ctx, err) => {
hook1Spy("error");
}
};
const hook2Spy = jest.fn();
const hooks2 = {
requestReceived: (ctx) => {
hook2Spy("received");
},
requestRouted: (ctx) => {
hook2Spy("routed");
},
requestPrepared: (ctx) => {
hook2Spy("prepared");
},
requestSent: (ctx) => {
hook2Spy("sent");
},
responseSent: (ctx) => {
hook2Spy("sent");
},
responsePrepared: (ctx) => {
hook2Spy("prepared");
},
error: (ctx, err) => {
hook2Spy("error");
}
};
const emptyHook = {};
const chainedHook = hooks_1.chainHooks(hooks1, hooks2, emptyHook);
expect(chainedHook).not.toBeNull();
const hookNames = ["requestReceived", "requestRouted", "requestPrepared", "requestSent", "responseSent", "responsePrepared", "error"];
hookNames.map(hookName => {
const ctx = {
req: jest.fn(),
res: jest.fn(),
contentType: request_1.TwirpContentType.Unknown,
packageName: "",
serviceName: "",
methodName: "",
};
const hook = chainedHook[hookName];
if (!hook) {
throw new Error(`hook ${hookName} must be present`);
}
hook(ctx, new errors_1.InternalServerError("test"));
});
});
});

View file

@ -0,0 +1 @@
export {};

View file

@ -0,0 +1,44 @@
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
const interceptors_1 = require("../interceptors");
const request_1 = require("../request");
describe("Interceptor", () => {
it("will chain interceptors", () => __awaiter(void 0, void 0, void 0, function* () {
const spy = jest.fn();
const interceptor0 = (ctx, typedRequest, next) => __awaiter(void 0, void 0, void 0, function* () {
spy();
const response = yield next(ctx, typedRequest);
spy();
return response;
});
const spy1 = jest.fn();
const interceptor1 = (ctx, typedRequest, next) => __awaiter(void 0, void 0, void 0, function* () {
spy1();
return next(ctx, typedRequest);
});
const chain = interceptors_1.chainInterceptors(interceptor0, interceptor1);
const ctx = {
req: jest.fn(),
res: jest.fn(),
contentType: request_1.TwirpContentType.Unknown,
packageName: "",
methodName: "",
serviceName: "",
};
const response = yield chain(ctx, {}, (ctx1, typedRequest) => __awaiter(void 0, void 0, void 0, function* () {
return { test: "test" };
}));
expect(response).toEqual({ test: "test" });
expect(spy).toBeCalledTimes(2);
expect(spy1).toBeCalledTimes(1);
}));
});

View file

@ -0,0 +1 @@
export {};

View file

@ -0,0 +1,272 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const http = __importStar(require("http"));
const supertest_1 = __importDefault(require("supertest"));
const service_twirp_1 = require("../__mocks__/service.twirp");
const service_1 = require("../__mocks__/service");
const errors_1 = require("../errors");
describe("Server twirp specification", () => {
let server;
let twirpServer;
beforeEach(() => {
twirpServer = service_twirp_1.createHaberdasherServer({
MakeHat(ctx, request) {
return __awaiter(this, void 0, void 0, function* () {
return service_1.Hat.create({
name: "cap",
color: "blue",
inches: 3,
});
});
},
FindHat(ctx, request) {
return __awaiter(this, void 0, void 0, function* () {
return request;
});
},
ListHat(ctx, request) {
return __awaiter(this, void 0, void 0, function* () {
return request;
});
}
});
server = http.createServer(twirpServer.httpHandler());
});
it("support only POST requests", () => __awaiter(void 0, void 0, void 0, function* () {
const unsupportedMethods = ["get", "put", "patch", "delete", "options"];
const tests = unsupportedMethods.map((method) => __awaiter(void 0, void 0, void 0, function* () {
const dynamicSupertest = supertest_1.default(server);
const resp = yield dynamicSupertest[method]("/invalid-url")
.set('Content-Type', 'application/json')
.expect('Content-Type', "application/json")
.expect(404);
expect(resp.body).toEqual({
code: errors_1.TwirpErrorCode.BadRoute,
msg: `unsupported method ${method.toUpperCase()} (only POST is allowed)`,
meta: {
twirp_invalid_route: `${method.toUpperCase()} /invalid-url`,
}
});
}));
yield Promise.all(tests);
yield supertest_1.default(server).post("/twirp/twirp.example.haberdasher.Haberdasher/MakeHat")
.set('Content-Type', 'application/json')
.expect('Content-Type', "application/json")
.expect(200);
}));
it("support only application/json and application/protobuf content-type", () => __awaiter(void 0, void 0, void 0, function* () {
const resp = yield supertest_1.default(server).post("/twirp/twirp.example.haberdasher.Haberdasher/MakeHat")
.set('Content-Type', 'invalid/json')
.expect('Content-Type', "application/json")
.expect(404);
expect(resp.body).toEqual({
code: "bad_route",
meta: {
twirp_invalid_route: "POST /twirp/twirp.example.haberdasher.Haberdasher/MakeHat"
},
msg: "unexpected Content-Type: invalid/json"
});
yield supertest_1.default(server).post("/twirp/twirp.example.haberdasher.Haberdasher/MakeHat")
.set('Content-Type', 'application/json')
.expect('Content-Type', "application/json")
.expect(200);
yield supertest_1.default(server).post("/twirp/twirp.example.haberdasher.Haberdasher/MakeHat")
.set('Content-Type', 'application/protobuf')
.expect('Content-Type', "application/protobuf")
.expect(200);
}));
describe("url must match [<prefix>]/[<package>.]<Service>/<Method>", () => {
it("will error if url is malformed", () => __awaiter(void 0, void 0, void 0, function* () {
const resp = yield supertest_1.default(server).post("/invalid-url-format")
.expect('Content-Type', "application/json")
.expect(404);
expect(resp.body).toEqual({
code: errors_1.TwirpErrorCode.BadRoute,
msg: `no handler for path /invalid-url-format`,
meta: {
twirp_invalid_route: `POST /invalid-url-format`,
}
});
}));
it("succeeds when url is properly constructed", () => __awaiter(void 0, void 0, void 0, function* () {
yield supertest_1.default(server).post("/twirp/twirp.example.haberdasher.Haberdasher/MakeHat")
.set('Content-Type', 'application/json')
.expect('Content-Type', "application/json")
.expect(200);
}));
it("must respect the prefix", () => __awaiter(void 0, void 0, void 0, function* () {
const resp = yield supertest_1.default(server).post("/twirp-not-existing/twirp.example.haberdasher.Haberdasher/MakeHat")
.set('Content-Type', 'application/json')
.expect('Content-Type', "application/json")
.expect(404);
expect(resp.body).toEqual({
code: "bad_route",
meta: {
twirp_invalid_route: "POST /twirp-not-existing/twirp.example.haberdasher.Haberdasher/MakeHat"
},
msg: "invalid path prefix /twirp-not-existing, expected /twirp, on path /twirp-not-existing/twirp.example.haberdasher.Haberdasher/MakeHat"
});
}));
it("must have a specified handler", () => __awaiter(void 0, void 0, void 0, function* () {
const resp = yield supertest_1.default(server).post("/twirp/twirp.example.haberdasher.Haberdasher/MakeHatDoesntExists")
.set('Content-Type', 'application/json')
.expect('Content-Type', "application/json")
.expect(404);
expect(resp.body).toEqual({
code: "bad_route",
meta: {
twirp_invalid_route: "POST /twirp/twirp.example.haberdasher.Haberdasher/MakeHatDoesntExists"
},
msg: "no handler for path /twirp/twirp.example.haberdasher.Haberdasher/MakeHatDoesntExists"
});
}));
it("support rawBody Buffer", () => __awaiter(void 0, void 0, void 0, function* () {
server = http.createServer((req, res) => __awaiter(void 0, void 0, void 0, function* () {
req.rawBody = Buffer.from(JSON.stringify({
hatId: '1234',
}));
yield twirpServer.httpHandler()(req, res);
}));
const response = yield supertest_1.default(server).post("/twirp/twirp.example.haberdasher.Haberdasher/FindHat")
.set('Content-Type', 'application/json')
.expect('Content-Type', "application/json")
.expect(200);
expect(response.body).toEqual({
hat_id: '1234'
});
}));
});
});
describe("Hooks & Interceptors", () => {
let server;
let twirpServer;
beforeEach(() => {
twirpServer = service_twirp_1.createHaberdasherServer({
MakeHat(ctx, request) {
return __awaiter(this, void 0, void 0, function* () {
return service_1.Hat.create({
name: "cap",
color: "blue",
inches: 3,
});
});
},
FindHat(ctx, request) {
return __awaiter(this, void 0, void 0, function* () {
return request;
});
},
ListHat(ctx, request) {
return __awaiter(this, void 0, void 0, function* () {
return request;
});
}
});
server = http.createServer(twirpServer.httpHandler());
});
it("can add interceptors", () => __awaiter(void 0, void 0, void 0, function* () {
const interceptorSpy = jest.fn();
twirpServer.use((ctx, req, next) => __awaiter(void 0, void 0, void 0, function* () {
interceptorSpy();
const resp = yield next(ctx, next);
interceptorSpy();
return resp;
}));
yield supertest_1.default(server).post("/twirp/twirp.example.haberdasher.Haberdasher/MakeHat")
.set('Content-Type', 'application/json')
.expect('Content-Type', "application/json")
.expect(200);
expect(interceptorSpy).toBeCalledTimes(2);
}));
it("can add hooks", () => __awaiter(void 0, void 0, void 0, function* () {
const hookSpy = jest.fn();
twirpServer.use({
requestReceived: (ctx) => {
hookSpy("received");
},
requestRouted: (ctx) => {
hookSpy("routed");
},
requestPrepared: (ctx) => {
hookSpy("prepared");
},
requestSent: (ctx) => {
hookSpy("sent");
},
error: (ctx, err) => {
hookSpy("error"); // will not be called
}
});
yield supertest_1.default(server).post("/twirp/twirp.example.haberdasher.Haberdasher/MakeHat")
.set('Content-Type', 'application/json')
.expect('Content-Type', "application/json")
.expect(200);
expect(hookSpy).toBeCalledTimes(4);
expect(hookSpy).toBeCalledWith("received");
expect(hookSpy).toBeCalledWith("routed");
expect(hookSpy).toBeCalledWith("prepared");
expect(hookSpy).toBeCalledWith("sent");
}));
it("will invoke the error hook when an error occurs", () => __awaiter(void 0, void 0, void 0, function* () {
twirpServer = service_twirp_1.createHaberdasherServer({
MakeHat(ctx, request) {
return __awaiter(this, void 0, void 0, function* () {
throw new errors_1.TwirpError(errors_1.TwirpErrorCode.Internal, "test error");
});
},
FindHat(ctx, request) {
return __awaiter(this, void 0, void 0, function* () {
return request;
});
},
ListHat(ctx, request) {
return __awaiter(this, void 0, void 0, function* () {
return request;
});
}
});
const hookSpy = jest.fn();
twirpServer.use({
error: (ctx, err) => {
hookSpy("error"); // will not be called
}
});
server = http.createServer(twirpServer.httpHandler());
yield supertest_1.default(server).post("/twirp/twirp.example.haberdasher.Haberdasher/MakeHat")
.set('Content-Type', 'application/json')
.expect('Content-Type', "application/json")
.expect(500);
expect(hookSpy).toBeCalledWith("error");
}));
});

View file

@ -0,0 +1,11 @@
/// <reference types="node" />
import * as http from "http";
import { TwirpContentType } from "./request";
export interface TwirpContext<Request = http.IncomingMessage, Response = http.ServerResponse> {
readonly packageName: string;
readonly serviceName: string;
readonly methodName: string;
readonly contentType: TwirpContentType;
readonly req: Request;
readonly res: Response;
}

View file

@ -0,0 +1,2 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });

View file

@ -0,0 +1,104 @@
/**
* Represents a twirp error
*/
export declare class TwirpError extends Error {
readonly msg: string;
readonly code: TwirpErrorCode;
readonly meta: Record<string, string>;
private _originalCause?;
constructor(code: TwirpErrorCode, msg: string);
/**
* Adds a metadata kv to the error
* @param key
* @param value
*/
withMeta(key: string, value: string): this;
/**
* Returns a single metadata value
* return "" if not found
* @param key
*/
getMeta(key: string): string;
/**
* Add the original error cause
* @param err
* @param addMeta
*/
withCause(err: Error, addMeta?: boolean): this;
cause(): Error | undefined;
/**
* Returns the error representation to JSON
*/
toJSON(): string;
/**
* Create a twirp error from an object
* @param obj
*/
static fromObject(obj: Record<string, any>): TwirpError;
}
/**
* NotFoundError constructor for the common NotFound error.
*/
export declare class NotFoundError extends TwirpError {
constructor(msg: string);
}
/**
* InvalidArgumentError constructor for the common InvalidArgument error. Can be
* used when an argument has invalid format, is a number out of range, is a bad
* option, etc).
*/
export declare class InvalidArgumentError extends TwirpError {
constructor(argument: string, validationMsg: string);
}
/**
* RequiredArgumentError is a more specific constructor for InvalidArgument
* error. Should be used when the argument is required (expected to have a
* non-zero value).
*/
export declare class RequiredArgumentError extends InvalidArgumentError {
constructor(argument: string);
}
/**
* InternalError constructor for the common Internal error. Should be used to
* specify that something bad or unexpected happened.
*/
export declare class InternalServerError extends TwirpError {
constructor(msg: string);
}
/**
* InternalErrorWith makes an internal error, wrapping the original error and using it
* for the error message, and with metadata "cause" with the original error type.
* This function is used by Twirp services to wrap non-Twirp errors as internal errors.
* The wrapped error can be extracted later with err.cause()
*/
export declare class InternalServerErrorWith extends InternalServerError {
constructor(err: Error);
}
/**
* A standard BadRoute Error
*/
export declare class BadRouteError extends TwirpError {
constructor(msg: string, method: string, url: string);
}
export declare enum TwirpErrorCode {
Canceled = "canceled",
Unknown = "unknown",
InvalidArgument = "invalid_argument",
Malformed = "malformed",
DeadlineExceeded = "deadline_exceeded",
NotFound = "not_found",
BadRoute = "bad_route",
AlreadyExists = "already_exists",
PermissionDenied = "permission_denied",
Unauthenticated = "unauthenticated",
ResourceExhausted = "resource_exhausted",
FailedPrecondition = "failed_precondition",
Aborted = "aborted",
OutOfRange = "out_of_range",
Unimplemented = "unimplemented",
Internal = "internal",
Unavailable = "unavailable",
DataLoss = "data_loss"
}
export declare function httpStatusFromErrorCode(code: TwirpErrorCode): number;
export declare function isValidErrorCode(code: TwirpErrorCode): boolean;

View file

@ -0,0 +1,271 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.isValidErrorCode = exports.httpStatusFromErrorCode = exports.TwirpErrorCode = exports.BadRouteError = exports.InternalServerErrorWith = exports.InternalServerError = exports.RequiredArgumentError = exports.InvalidArgumentError = exports.NotFoundError = exports.TwirpError = void 0;
/**
* Represents a twirp error
*/
class TwirpError extends Error {
constructor(code, msg) {
super(msg);
this.code = TwirpErrorCode.Internal;
this.meta = {};
this.code = code;
this.msg = msg;
Object.setPrototypeOf(this, TwirpError.prototype);
}
/**
* Adds a metadata kv to the error
* @param key
* @param value
*/
withMeta(key, value) {
this.meta[key] = value;
return this;
}
/**
* Returns a single metadata value
* return "" if not found
* @param key
*/
getMeta(key) {
return this.meta[key] || "";
}
/**
* Add the original error cause
* @param err
* @param addMeta
*/
withCause(err, addMeta = false) {
this._originalCause = err;
if (addMeta) {
this.withMeta("cause", err.message);
}
return this;
}
cause() {
return this._originalCause;
}
/**
* Returns the error representation to JSON
*/
toJSON() {
try {
return JSON.stringify({
code: this.code,
msg: this.msg,
meta: this.meta,
});
}
catch (e) {
return `{"code": "internal", "msg": "There was an error but it could not be serialized into JSON"}`;
}
}
/**
* Create a twirp error from an object
* @param obj
*/
static fromObject(obj) {
const code = obj["code"] || TwirpErrorCode.Unknown;
const msg = obj["msg"] || "unknown";
const error = new TwirpError(code, msg);
if (obj["meta"]) {
Object.keys(obj["meta"]).forEach((key) => {
error.withMeta(key, obj["meta"][key]);
});
}
return error;
}
}
exports.TwirpError = TwirpError;
/**
* NotFoundError constructor for the common NotFound error.
*/
class NotFoundError extends TwirpError {
constructor(msg) {
super(TwirpErrorCode.NotFound, msg);
}
}
exports.NotFoundError = NotFoundError;
/**
* InvalidArgumentError constructor for the common InvalidArgument error. Can be
* used when an argument has invalid format, is a number out of range, is a bad
* option, etc).
*/
class InvalidArgumentError extends TwirpError {
constructor(argument, validationMsg) {
super(TwirpErrorCode.InvalidArgument, argument + " " + validationMsg);
this.withMeta("argument", argument);
}
}
exports.InvalidArgumentError = InvalidArgumentError;
/**
* RequiredArgumentError is a more specific constructor for InvalidArgument
* error. Should be used when the argument is required (expected to have a
* non-zero value).
*/
class RequiredArgumentError extends InvalidArgumentError {
constructor(argument) {
super(argument, "is required");
}
}
exports.RequiredArgumentError = RequiredArgumentError;
/**
* InternalError constructor for the common Internal error. Should be used to
* specify that something bad or unexpected happened.
*/
class InternalServerError extends TwirpError {
constructor(msg) {
super(TwirpErrorCode.Internal, msg);
}
}
exports.InternalServerError = InternalServerError;
/**
* InternalErrorWith makes an internal error, wrapping the original error and using it
* for the error message, and with metadata "cause" with the original error type.
* This function is used by Twirp services to wrap non-Twirp errors as internal errors.
* The wrapped error can be extracted later with err.cause()
*/
class InternalServerErrorWith extends InternalServerError {
constructor(err) {
super(err.message);
this.withMeta("cause", err.name);
this.withCause(err);
}
}
exports.InternalServerErrorWith = InternalServerErrorWith;
/**
* A standard BadRoute Error
*/
class BadRouteError extends TwirpError {
constructor(msg, method, url) {
super(TwirpErrorCode.BadRoute, msg);
this.withMeta("twirp_invalid_route", method + " " + url);
}
}
exports.BadRouteError = BadRouteError;
var TwirpErrorCode;
(function (TwirpErrorCode) {
// Canceled indicates the operation was cancelled (typically by the caller).
TwirpErrorCode["Canceled"] = "canceled";
// Unknown error. For example when handling errors raised by APIs that do not
// return enough error information.
TwirpErrorCode["Unknown"] = "unknown";
// InvalidArgument indicates client specified an invalid argument. It
// indicates arguments that are problematic regardless of the state of the
// system (i.e. a malformed file name, required argument, number out of range,
// etc.).
TwirpErrorCode["InvalidArgument"] = "invalid_argument";
// Malformed indicates an error occurred while decoding the client's request.
// This may mean that the message was encoded improperly, or that there is a
// disagreement in message format between the client and server.
TwirpErrorCode["Malformed"] = "malformed";
// DeadlineExceeded means operation expired before completion. For operations
// that change the state of the system, this error may be returned even if the
// operation has completed successfully (timeout).
TwirpErrorCode["DeadlineExceeded"] = "deadline_exceeded";
// NotFound means some requested entity was not found.
TwirpErrorCode["NotFound"] = "not_found";
// BadRoute means that the requested URL path wasn't routable to a Twirp
// service and method. This is returned by the generated server, and usually
// shouldn't be returned by applications. Instead, applications should use
// NotFound or Unimplemented.
TwirpErrorCode["BadRoute"] = "bad_route";
// AlreadyExists means an attempt to create an entity failed because one
// already exists.
TwirpErrorCode["AlreadyExists"] = "already_exists";
// PermissionDenied indicates the caller does not have permission to execute
// the specified operation. It must not be used if the caller cannot be
// identified (Unauthenticated).
TwirpErrorCode["PermissionDenied"] = "permission_denied";
// Unauthenticated indicates the request does not have valid authentication
// credentials for the operation.
TwirpErrorCode["Unauthenticated"] = "unauthenticated";
// ResourceExhausted indicates some resource has been exhausted, perhaps a
// per-user quota, or perhaps the entire file system is out of space.
TwirpErrorCode["ResourceExhausted"] = "resource_exhausted";
// FailedPrecondition indicates operation was rejected because the system is
// not in a state required for the operation's execution. For example, doing
// an rmdir operation on a directory that is non-empty, or on a non-directory
// object, or when having conflicting read-modify-write on the same resource.
TwirpErrorCode["FailedPrecondition"] = "failed_precondition";
// Aborted indicates the operation was aborted, typically due to a concurrency
// issue like sequencer check failures, transaction aborts, etc.
TwirpErrorCode["Aborted"] = "aborted";
// OutOfRange means operation was attempted past the valid range. For example,
// seeking or reading past end of a paginated collection.
//
// Unlike InvalidArgument, this error indicates a problem that may be fixed if
// the system state changes (i.e. adding more items to the collection).
//
// There is a fair bit of overlap between FailedPrecondition and OutOfRange.
// We recommend using OutOfRange (the more specific error) when it applies so
// that callers who are iterating through a space can easily look for an
// OutOfRange error to detect when they are done.
TwirpErrorCode["OutOfRange"] = "out_of_range";
// Unimplemented indicates operation is not implemented or not
// supported/enabled in this service.
TwirpErrorCode["Unimplemented"] = "unimplemented";
// Internal errors. When some invariants expected by the underlying system
// have been broken. In other words, something bad happened in the library or
// backend service. Do not confuse with HTTP Internal Server Error; an
// Internal error could also happen on the client code, i.e. when parsing a
// server response.
TwirpErrorCode["Internal"] = "internal";
// Unavailable indicates the service is currently unavailable. This is a most
// likely a transient condition and may be corrected by retrying with a
// backoff.
TwirpErrorCode["Unavailable"] = "unavailable";
// DataLoss indicates unrecoverable data loss or corruption.
TwirpErrorCode["DataLoss"] = "data_loss";
})(TwirpErrorCode = exports.TwirpErrorCode || (exports.TwirpErrorCode = {}));
// ServerHTTPStatusFromErrorCode maps a Twirp error type into a similar HTTP
// response status. It is used by the Twirp server handler to set the HTTP
// response status code. Returns 0 if the ErrorCode is invalid.
function httpStatusFromErrorCode(code) {
switch (code) {
case TwirpErrorCode.Canceled:
return 408; // RequestTimeout
case TwirpErrorCode.Unknown:
return 500; // Internal Server Error
case TwirpErrorCode.InvalidArgument:
return 400; // BadRequest
case TwirpErrorCode.Malformed:
return 400; // BadRequest
case TwirpErrorCode.DeadlineExceeded:
return 408; // RequestTimeout
case TwirpErrorCode.NotFound:
return 404; // Not Found
case TwirpErrorCode.BadRoute:
return 404; // Not Found
case TwirpErrorCode.AlreadyExists:
return 409; // Conflict
case TwirpErrorCode.PermissionDenied:
return 403; // Forbidden
case TwirpErrorCode.Unauthenticated:
return 401; // Unauthorized
case TwirpErrorCode.ResourceExhausted:
return 429; // Too Many Requests
case TwirpErrorCode.FailedPrecondition:
return 412; // Precondition Failed
case TwirpErrorCode.Aborted:
return 409; // Conflict
case TwirpErrorCode.OutOfRange:
return 400; // Bad Request
case TwirpErrorCode.Unimplemented:
return 501; // Not Implemented
case TwirpErrorCode.Internal:
return 500; // Internal Server Error
case TwirpErrorCode.Unavailable:
return 503; // Service Unavailable
case TwirpErrorCode.DataLoss:
return 500; // Internal Server Error
default:
return 0; // Invalid!
}
}
exports.httpStatusFromErrorCode = httpStatusFromErrorCode;
// IsValidErrorCode returns true if is one of the valid predefined constants.
function isValidErrorCode(code) {
return httpStatusFromErrorCode(code) != 0;
}
exports.isValidErrorCode = isValidErrorCode;

View file

@ -0,0 +1,72 @@
/// <reference types="node" />
import * as http from "http";
import { MatchFunction, MatchResult } from "path-to-regexp";
import { HttpClientOptions } from "./http.client";
export declare enum Pattern {
POST = "post",
GET = "get",
PATCH = "patch",
PUT = "put",
DELETE = "delete"
}
export interface HttpRoute {
serviceName: string;
methodName: string;
packageName: string;
matchingPath: string;
matcher: MatchFunction;
httpMethod: Pattern;
bodyKey?: string;
responseBodyKey?: string;
additionalBindings?: HttpRoute;
}
declare type RouteRules = {
[key in Pattern]: HttpRoute[];
};
/**
* The Gateway proxies http requests to Twirp Compliant
* handlers
*/
export declare class Gateway {
readonly routes: RouteRules;
constructor(routes: RouteRules);
/**
* Middleware that rewrite the current request
* to a Twirp compliant request
*/
twirpRewrite(prefix?: string): (req: http.IncomingMessage, resp: http.ServerResponse, next: (err?: Error | undefined) => void) => void;
/**
* Rewrite an incoming request to a Twirp compliant request
* @param req
* @param resp
* @param prefix
*/
rewrite(req: http.IncomingMessage, resp: http.ServerResponse, prefix?: string): Promise<void>;
/**
* Create a reverse proxy handler to
* proxy http requests to Twirp Compliant handlers
* @param httpClientOption
*/
reverseProxy(httpClientOption: HttpClientOptions): (req: http.IncomingMessage, res: http.ServerResponse) => Promise<void>;
/**
* Prepares twirp body requests using http.google.annotions
* compliant spec
*
* @param req
* @param match
* @param route
* @protected
*/
protected prepareTwirpBody(req: http.IncomingMessage, match: MatchResult, route: HttpRoute): Promise<Record<string, any>>;
/**
* Matches a route
* @param req
*/
matchRoute(req: http.IncomingMessage): [MatchResult, HttpRoute];
/**
* Parse query string
* @param queryString
*/
parseQueryString(queryString: string): object;
}
export {};

View file

@ -0,0 +1,207 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __rest = (this && this.__rest) || function (s, e) {
var t = {};
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0)
t[p] = s[p];
if (s != null && typeof Object.getOwnPropertySymbols === "function")
for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) {
if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i]))
t[p[i]] = s[p[i]];
}
return t;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Gateway = exports.Pattern = void 0;
const querystring_1 = require("querystring");
const dotObject = __importStar(require("dot-object"));
const request_1 = require("./request");
const errors_1 = require("./errors");
const http_client_1 = require("./http.client");
const server_1 = require("./server");
var Pattern;
(function (Pattern) {
Pattern["POST"] = "post";
Pattern["GET"] = "get";
Pattern["PATCH"] = "patch";
Pattern["PUT"] = "put";
Pattern["DELETE"] = "delete";
})(Pattern = exports.Pattern || (exports.Pattern = {}));
/**
* The Gateway proxies http requests to Twirp Compliant
* handlers
*/
class Gateway {
constructor(routes) {
this.routes = routes;
}
/**
* Middleware that rewrite the current request
* to a Twirp compliant request
*/
twirpRewrite(prefix = "/twirp") {
return (req, resp, next) => {
this.rewrite(req, resp, prefix)
.then(() => next())
.catch((e) => {
if (e instanceof errors_1.TwirpError) {
if (e.code !== errors_1.TwirpErrorCode.NotFound) {
server_1.writeError(resp, e);
}
else {
next();
}
}
});
};
}
/**
* Rewrite an incoming request to a Twirp compliant request
* @param req
* @param resp
* @param prefix
*/
rewrite(req, resp, prefix = "/twirp") {
return __awaiter(this, void 0, void 0, function* () {
const [match, route] = this.matchRoute(req);
const body = yield this.prepareTwirpBody(req, match, route);
const twirpUrl = `${prefix}/${route.packageName}.${route.serviceName}/${route.methodName}`;
req.url = twirpUrl;
req.originalUrl = twirpUrl;
req.method = "POST";
req.headers["content-type"] = "application/json";
req.rawBody = Buffer.from(JSON.stringify(body));
if (route.responseBodyKey) {
const endFn = resp.end.bind(resp);
resp.end = function (chunk) {
if (resp.statusCode === 200) {
endFn(`{ "${route.responseBodyKey}": ${chunk} }`);
}
else {
endFn(chunk);
}
};
}
});
}
/**
* Create a reverse proxy handler to
* proxy http requests to Twirp Compliant handlers
* @param httpClientOption
*/
reverseProxy(httpClientOption) {
const client = http_client_1.NodeHttpRPC(httpClientOption);
return (req, res) => __awaiter(this, void 0, void 0, function* () {
try {
const [match, route] = this.matchRoute(req);
const body = yield this.prepareTwirpBody(req, match, route);
const response = yield client.request(`${route.packageName}.${route.serviceName}`, route.methodName, "application/json", body);
res.statusCode = 200;
res.setHeader("content-type", "application/json");
let jsonResponse;
if (route.responseBodyKey) {
jsonResponse = JSON.stringify({ [route.responseBodyKey]: response });
}
else {
jsonResponse = JSON.stringify(response);
}
res.end(jsonResponse);
}
catch (e) {
server_1.writeError(res, e);
}
});
}
/**
* Prepares twirp body requests using http.google.annotions
* compliant spec
*
* @param req
* @param match
* @param route
* @protected
*/
prepareTwirpBody(req, match, route) {
return __awaiter(this, void 0, void 0, function* () {
const _a = match.params, { query_string } = _a, params = __rest(_a, ["query_string"]);
let requestBody = Object.assign({}, params);
if (query_string && route.bodyKey !== "*") {
const queryParams = this.parseQueryString(query_string);
requestBody = Object.assign(Object.assign({}, queryParams), requestBody);
}
let body = {};
if (route.bodyKey) {
const data = yield request_1.getRequestData(req);
try {
const jsonBody = JSON.parse(data.toString() || "{}");
if (route.bodyKey === "*") {
body = jsonBody;
}
else {
body[route.bodyKey] = jsonBody;
}
}
catch (e) {
const msg = "the json request could not be decoded";
throw new errors_1.TwirpError(errors_1.TwirpErrorCode.Malformed, msg).withCause(e, true);
}
}
return Object.assign(Object.assign({}, body), requestBody);
});
}
/**
* Matches a route
* @param req
*/
matchRoute(req) {
var _a;
const httpMethod = (_a = req.method) === null || _a === void 0 ? void 0 : _a.toLowerCase();
if (!httpMethod) {
throw new errors_1.BadRouteError(`method not allowed`, req.method || "", req.url || "");
}
const routes = this.routes[httpMethod];
for (const route of routes) {
const match = route.matcher(req.url || "/");
if (match) {
return [match, route];
}
}
throw new errors_1.NotFoundError(`url ${req.url} not found`);
}
/**
* Parse query string
* @param queryString
*/
parseQueryString(queryString) {
const queryParams = querystring_1.parse(queryString.replace("?", ""));
return dotObject.object(queryParams);
}
}
exports.Gateway = Gateway;

View file

@ -0,0 +1,15 @@
import { TwirpContext } from "./context";
import { TwirpError } from "./errors";
export interface ServerHooks<T extends TwirpContext = TwirpContext> {
requestReceived?: (ctx: T) => void | Promise<void>;
requestRouted?: (ctx: T) => void | Promise<void>;
/**@deprecated Use responsePrepared instead*/
requestPrepared?: (ctx: T) => void | Promise<void>;
responsePrepared?: (ctx: T) => void | Promise<void>;
/**@deprecated Use responseSent instead*/
requestSent?: (ctx: T) => void | Promise<void>;
responseSent?: (ctx: T) => void | Promise<void>;
error?: (ctx: T, err: TwirpError) => void | Promise<void>;
}
export declare function chainHooks<T extends TwirpContext = TwirpContext>(...hooks: ServerHooks<T>[]): ServerHooks<T> | null;
export declare function isHook<T extends TwirpContext = TwirpContext>(object: any): object is ServerHooks<T>;

View file

@ -0,0 +1,114 @@
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.isHook = exports.chainHooks = void 0;
// ChainHooks creates a new ServerHook which chains the callbacks in
// each of the constituent hooks passed in. Each hook function will be
// called in the order of the ServerHooks values passed in.
//
// For the erroring hooks, RequestReceived and RequestRouted, any returned
// errors prevent processing by later hooks.
function chainHooks(...hooks) {
if (hooks.length === 0) {
return null;
}
if (hooks.length === 1) {
return hooks[0];
}
const serverHook = {
requestReceived(ctx) {
return __awaiter(this, void 0, void 0, function* () {
for (const hook of hooks) {
if (!hook.requestReceived) {
continue;
}
yield hook.requestReceived(ctx);
}
});
},
requestPrepared(ctx) {
return __awaiter(this, void 0, void 0, function* () {
for (const hook of hooks) {
if (!hook.requestPrepared) {
continue;
}
console.warn("hook requestPrepared is deprecated and will be removed in the next release. " +
"Please use responsePrepared instead.");
yield hook.requestPrepared(ctx);
}
});
},
responsePrepared(ctx) {
return __awaiter(this, void 0, void 0, function* () {
for (const hook of hooks) {
if (!hook.responsePrepared) {
continue;
}
yield hook.responsePrepared(ctx);
}
});
},
requestSent(ctx) {
return __awaiter(this, void 0, void 0, function* () {
for (const hook of hooks) {
if (!hook.requestSent) {
continue;
}
console.warn("hook requestSent is deprecated and will be removed in the next release. " +
"Please use responseSent instead.");
yield hook.requestSent(ctx);
}
});
},
responseSent(ctx) {
return __awaiter(this, void 0, void 0, function* () {
for (const hook of hooks) {
if (!hook.responseSent) {
continue;
}
yield hook.responseSent(ctx);
}
});
},
requestRouted(ctx) {
return __awaiter(this, void 0, void 0, function* () {
for (const hook of hooks) {
if (!hook.requestRouted) {
continue;
}
yield hook.requestRouted(ctx);
}
});
},
error(ctx, err) {
return __awaiter(this, void 0, void 0, function* () {
for (const hook of hooks) {
if (!hook.error) {
continue;
}
yield hook.error(ctx, err);
}
});
},
};
return serverHook;
}
exports.chainHooks = chainHooks;
function isHook(object) {
return ("requestReceived" in object ||
"requestPrepared" in object ||
"requestSent" in object ||
"requestRouted" in object ||
"responsePrepared" in object ||
"responseSent" in object ||
"error" in object);
}
exports.isHook = isHook;

View file

@ -0,0 +1,24 @@
/// <reference types="node" />
import * as http from "http";
import * as https from "https";
import { TwirpError } from "./errors";
export interface Rpc {
request(service: string, method: string, contentType: "application/json" | "application/protobuf", data: object | Uint8Array): Promise<object | Uint8Array>;
}
export declare type HttpClientOptions = Omit<http.RequestOptions | https.RequestOptions, "path" | "host" | "port"> & {
baseUrl: string;
};
/**
* a node HTTP RPC implementation
* @param options
* @constructor
*/
export declare const NodeHttpRPC: (options: HttpClientOptions) => Rpc;
export declare function wrapErrorResponseToTwirpError(errorResponse: string): TwirpError;
export declare type FetchRPCOptions = Omit<RequestInit, "body" | "method"> & {
baseUrl: string;
};
/**
* a browser fetch RPC implementation
*/
export declare const FetchRPC: (options: FetchRPCOptions) => Rpc;

View file

@ -0,0 +1,112 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.FetchRPC = exports.wrapErrorResponseToTwirpError = exports.NodeHttpRPC = void 0;
const http = __importStar(require("http"));
const https = __importStar(require("https"));
const url_1 = require("url");
const errors_1 = require("./errors");
/**
* a node HTTP RPC implementation
* @param options
* @constructor
*/
const NodeHttpRPC = (options) => ({
request(service, method, contentType, data) {
let client;
return new Promise((resolve, rejected) => {
const responseChunks = [];
const requestData = contentType === "application/protobuf"
? Buffer.from(data)
: JSON.stringify(data);
const url = new url_1.URL(options.baseUrl);
const isHttps = url.protocol === "https:";
if (isHttps) {
client = https;
}
else {
client = http;
}
const prefix = url.pathname !== "/" ? url.pathname : "";
const req = client
.request(Object.assign(Object.assign({}, (options ? options : {})), { method: "POST", protocol: url.protocol, host: url.hostname, port: url.port ? url.port : isHttps ? 443 : 80, path: `${prefix}/${service}/${method}`, headers: Object.assign(Object.assign({}, (options.headers ? options.headers : {})), { "Content-Type": contentType, "Content-Length": contentType === "application/protobuf"
? Buffer.byteLength(requestData)
: Buffer.from(requestData).byteLength }) }), (res) => {
res.on("data", (chunk) => responseChunks.push(chunk));
res.on("end", () => {
const data = Buffer.concat(responseChunks);
if (res.statusCode != 200) {
rejected(wrapErrorResponseToTwirpError(data.toString()));
}
else {
if (contentType === "application/json") {
resolve(JSON.parse(data.toString()));
}
else {
resolve(data);
}
}
});
res.on("error", (err) => {
rejected(err);
});
})
.on("error", (err) => {
rejected(err);
});
req.end(requestData);
});
},
});
exports.NodeHttpRPC = NodeHttpRPC;
function wrapErrorResponseToTwirpError(errorResponse) {
return errors_1.TwirpError.fromObject(JSON.parse(errorResponse));
}
exports.wrapErrorResponseToTwirpError = wrapErrorResponseToTwirpError;
/**
* a browser fetch RPC implementation
*/
const FetchRPC = (options) => ({
request(service, method, contentType, data) {
return __awaiter(this, void 0, void 0, function* () {
const headers = new Headers(options.headers);
headers.set("content-type", contentType);
const response = yield fetch(`${options.baseUrl}/${service}/${method}`, Object.assign(Object.assign({}, options), { method: "POST", headers, body: data instanceof Uint8Array ? data : JSON.stringify(data) }));
if (response.status === 200) {
if (contentType === "application/json") {
return yield response.json();
}
return new Uint8Array(yield response.arrayBuffer());
}
throw errors_1.TwirpError.fromObject(yield response.json());
});
},
});
exports.FetchRPC = FetchRPC;

View file

@ -0,0 +1,8 @@
export * from "./context";
export * from "./server";
export * from "./interceptors";
export * from "./hooks";
export * from "./errors";
export * from "./gateway";
export * from "./http.client";
export { TwirpContentType, TwirpRequest } from "./request";

View file

@ -0,0 +1,22 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __exportStar = (this && this.__exportStar) || function(m, exports) {
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.TwirpContentType = void 0;
__exportStar(require("./context"), exports);
__exportStar(require("./server"), exports);
__exportStar(require("./interceptors"), exports);
__exportStar(require("./hooks"), exports);
__exportStar(require("./errors"), exports);
__exportStar(require("./gateway"), exports);
__exportStar(require("./http.client"), exports);
var request_1 = require("./request");
Object.defineProperty(exports, "TwirpContentType", { enumerable: true, get: function () { return request_1.TwirpContentType; } });

View file

@ -0,0 +1,4 @@
import { TwirpContext } from "./context";
export declare type Next<Context extends TwirpContext = TwirpContext, Request = any, Response = any> = (ctx: Context, typedRequest: Request) => Promise<Response>;
export declare type Interceptor<Context extends TwirpContext, Request, Response> = (ctx: Context, typedRequest: Request, next: Next<Context, Request, Response>) => Promise<Response>;
export declare function chainInterceptors<Context extends TwirpContext, Request, Response>(...interceptors: Interceptor<Context, Request, Response>[]): Interceptor<Context, Request, Response> | undefined;

View file

@ -0,0 +1,34 @@
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.chainInterceptors = void 0;
// chains multiple Interceptors into a single Interceptor.
// The first interceptor wraps the second one, and so on.
// Returns null if interceptors is empty.
function chainInterceptors(...interceptors) {
if (interceptors.length === 0) {
return;
}
if (interceptors.length === 1) {
return interceptors[0];
}
const first = interceptors[0];
return (ctx, request, handler) => __awaiter(this, void 0, void 0, function* () {
let next = handler;
for (let i = interceptors.length - 1; i > 0; i--) {
next = ((next) => (ctx, typedRequest) => {
return interceptors[i](ctx, typedRequest, next);
})(next);
}
return first(ctx, request, next);
});
}
exports.chainInterceptors = chainInterceptors;

View file

@ -0,0 +1,43 @@
/// <reference types="node" />
import { TwirpContext } from "./context";
import http from "http";
/**
* Supported Twirp Content-Type
*/
export declare enum TwirpContentType {
Protobuf = 0,
JSON = 1,
Unknown = 2
}
/**
* Represent a Twirp request
*/
export interface TwirpRequest {
prefix?: string;
pkgService: string;
method: string;
contentType: TwirpContentType;
mimeContentType: string;
}
/**
* Get supported content-type
* @param mimeType
*/
export declare function getContentType(mimeType: string | undefined): TwirpContentType;
/**
* Validate a twirp request
* @param ctx
* @param request
* @param pathPrefix
*/
export declare function validateRequest(ctx: TwirpContext, request: http.IncomingMessage, pathPrefix: string): TwirpRequest;
/**
* Get request data from the body
* @param req
*/
export declare function getRequestData(req: http.IncomingMessage): Promise<Buffer>;
/**
* Parses twirp url path
* @param path
*/
export declare function parseTwirpPath(path: string): Omit<TwirpRequest, "contentType" | "mimeContentType">;

View file

@ -0,0 +1,117 @@
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.parseTwirpPath = exports.getRequestData = exports.validateRequest = exports.getContentType = exports.TwirpContentType = void 0;
const errors_1 = require("./errors");
/**
* Supported Twirp Content-Type
*/
var TwirpContentType;
(function (TwirpContentType) {
TwirpContentType[TwirpContentType["Protobuf"] = 0] = "Protobuf";
TwirpContentType[TwirpContentType["JSON"] = 1] = "JSON";
TwirpContentType[TwirpContentType["Unknown"] = 2] = "Unknown";
})(TwirpContentType = exports.TwirpContentType || (exports.TwirpContentType = {}));
/**
* Get supported content-type
* @param mimeType
*/
function getContentType(mimeType) {
switch (mimeType) {
case "application/protobuf":
return TwirpContentType.Protobuf;
case "application/json":
return TwirpContentType.JSON;
default:
return TwirpContentType.Unknown;
}
}
exports.getContentType = getContentType;
/**
* Validate a twirp request
* @param ctx
* @param request
* @param pathPrefix
*/
function validateRequest(ctx, request, pathPrefix) {
if (request.method !== "POST") {
const msg = `unsupported method ${request.method} (only POST is allowed)`;
throw new errors_1.BadRouteError(msg, request.method || "", request.url || "");
}
const path = parseTwirpPath(request.url || "");
if (path.pkgService !==
(ctx.packageName ? ctx.packageName + "." : "") + ctx.serviceName) {
const msg = `no handler for path ${request.url}`;
throw new errors_1.BadRouteError(msg, request.method || "", request.url || "");
}
if (path.prefix !== pathPrefix) {
const msg = `invalid path prefix ${path.prefix}, expected ${pathPrefix}, on path ${request.url}`;
throw new errors_1.BadRouteError(msg, request.method || "", request.url || "");
}
const mimeContentType = request.headers["content-type"] || "";
if (ctx.contentType === TwirpContentType.Unknown) {
const msg = `unexpected Content-Type: ${request.headers["content-type"]}`;
throw new errors_1.BadRouteError(msg, request.method || "", request.url || "");
}
return Object.assign(Object.assign({}, path), { mimeContentType, contentType: ctx.contentType });
}
exports.validateRequest = validateRequest;
/**
* Get request data from the body
* @param req
*/
function getRequestData(req) {
return new Promise((resolve, reject) => {
const reqWithRawBody = req;
if (reqWithRawBody.rawBody instanceof Buffer) {
resolve(reqWithRawBody.rawBody);
return;
}
const chunks = [];
req.on("data", (chunk) => chunks.push(chunk));
req.on("end", () => __awaiter(this, void 0, void 0, function* () {
const data = Buffer.concat(chunks);
resolve(data);
}));
req.on("error", (err) => {
if (req.aborted) {
reject(new errors_1.TwirpError(errors_1.TwirpErrorCode.DeadlineExceeded, "failed to read request: deadline exceeded"));
}
else {
reject(new errors_1.TwirpError(errors_1.TwirpErrorCode.Malformed, err.message).withCause(err));
}
});
req.on("close", () => {
reject(new errors_1.TwirpError(errors_1.TwirpErrorCode.Canceled, "failed to read request: context canceled"));
});
});
}
exports.getRequestData = getRequestData;
/**
* Parses twirp url path
* @param path
*/
function parseTwirpPath(path) {
const parts = path.split("/");
if (parts.length < 2) {
return {
pkgService: "",
method: "",
prefix: "",
};
}
return {
method: parts[parts.length - 1],
pkgService: parts[parts.length - 2],
prefix: parts.slice(0, parts.length - 2).join("/"),
};
}
exports.parseTwirpPath = parseTwirpPath;

View file

@ -0,0 +1,104 @@
/// <reference types="node" />
import * as http from "http";
import { TwirpContext } from "./context";
import { ServerHooks } from "./hooks";
import { Interceptor } from "./interceptors";
import { TwirpError } from "./errors";
/**
* Twirp Server options
*/
interface TwirpServerOptions<T extends object, S extends TwirpContext = TwirpContext> {
service: T;
packageName: string;
serviceName: string;
methodList: keys<T>;
matchRoute: (method: string, events: RouterEvents<S>) => TwirpHandler<T, S>;
}
/**
* httpHandler options
*/
export interface HttpHandlerOptions {
prefix?: string | false;
}
/**
* Handles a twirp request
*/
export declare type TwirpHandler<T, S extends TwirpContext = TwirpContext> = (ctx: S, service: T, data: Buffer, interceptors?: Interceptor<S, any, any>[]) => Promise<Uint8Array | string>;
/**
* Callback events for route matching
*/
export interface RouterEvents<T extends TwirpContext = TwirpContext> {
onMatch: (ctx: T) => Promise<void> | void;
onNotFound: () => Promise<void> | void;
}
declare type keys<T extends object> = Array<keyof T>;
/**
* Runtime server implementation of a TwirpServer
*/
export declare class TwirpServer<T extends object, S extends TwirpContext = TwirpContext> {
readonly packageName: string;
readonly serviceName: string;
readonly methodList: keys<T>;
private service;
private pathPrefix;
private hooks;
private interceptors;
private matchRoute;
constructor(options: TwirpServerOptions<T, S>);
/**
* Returns the prefix for this server
*/
get prefix(): string;
/**
* The http handler for twirp complaint endpoints
* @param options
*/
httpHandler(options?: HttpHandlerOptions): (req: http.IncomingMessage, resp: http.ServerResponse) => Promise<void>;
/**
* Adds interceptors or hooks to the request stack
* @param middlewares
*/
use(...middlewares: (ServerHooks<S> | Interceptor<S, any, any>)[]): this;
/**
* Adds a prefix to the service url path
* @param prefix
*/
withPrefix(prefix: string | false): this;
/**
* Returns the regex matching path for this twirp server
*/
matchingPath(): RegExp;
/**
* Returns the base URI for this twirp server
*/
baseURI(): string;
/**
* Create a twirp context
* @param req
* @param res
* @private
*/
protected createContext(req: http.IncomingMessage, res: http.ServerResponse): S;
/**
* Twrip server http handler implementation
* @param req
* @param resp
* @private
*/
private _httpHandler;
/**
* Invoke a hook
* @param hookName
* @param ctx
* @param err
* @protected
*/
protected invokeHook(hookName: keyof ServerHooks<S>, ctx: S, err?: TwirpError): Promise<void>;
}
/**
* Write http error response
* @param res
* @param error
*/
export declare function writeError(res: http.ServerResponse, error: Error | TwirpError): void;
export {};

View file

@ -0,0 +1,195 @@
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.writeError = exports.TwirpServer = void 0;
const hooks_1 = require("./hooks");
const request_1 = require("./request");
const errors_1 = require("./errors");
/**
* Runtime server implementation of a TwirpServer
*/
class TwirpServer {
constructor(options) {
this.pathPrefix = "/twirp";
this.hooks = [];
this.interceptors = [];
this.packageName = options.packageName;
this.serviceName = options.serviceName;
this.methodList = options.methodList;
this.matchRoute = options.matchRoute;
this.service = options.service;
}
/**
* Returns the prefix for this server
*/
get prefix() {
return this.pathPrefix;
}
/**
* The http handler for twirp complaint endpoints
* @param options
*/
httpHandler(options) {
return (req, resp) => {
// setup prefix
if ((options === null || options === void 0 ? void 0 : options.prefix) !== undefined) {
this.withPrefix(options.prefix);
}
return this._httpHandler(req, resp);
};
}
/**
* Adds interceptors or hooks to the request stack
* @param middlewares
*/
use(...middlewares) {
middlewares.forEach((middleware) => {
if (hooks_1.isHook(middleware)) {
this.hooks.push(middleware);
return this;
}
this.interceptors.push(middleware);
});
return this;
}
/**
* Adds a prefix to the service url path
* @param prefix
*/
withPrefix(prefix) {
if (prefix === false) {
this.pathPrefix = "";
}
else {
this.pathPrefix = prefix;
}
return this;
}
/**
* Returns the regex matching path for this twirp server
*/
matchingPath() {
const baseRegex = this.baseURI().replace(/\./g, "\\.");
return new RegExp(`${baseRegex}\/(${this.methodList.join("|")})`);
}
/**
* Returns the base URI for this twirp server
*/
baseURI() {
return `${this.pathPrefix}/${this.packageName ? this.packageName + "." : ""}${this.serviceName}`;
}
/**
* Create a twirp context
* @param req
* @param res
* @private
*/
createContext(req, res) {
return {
packageName: this.packageName,
serviceName: this.serviceName,
methodName: "",
contentType: request_1.getContentType(req.headers["content-type"]),
req: req,
res: res,
};
}
/**
* Twrip server http handler implementation
* @param req
* @param resp
* @private
*/
_httpHandler(req, resp) {
return __awaiter(this, void 0, void 0, function* () {
const ctx = this.createContext(req, resp);
try {
yield this.invokeHook("requestReceived", ctx);
const { method, mimeContentType } = request_1.validateRequest(ctx, req, this.pathPrefix || "");
const handler = this.matchRoute(method, {
onMatch: (ctx) => {
return this.invokeHook("requestRouted", ctx);
},
onNotFound: () => {
const msg = `no handler for path ${req.url}`;
throw new errors_1.BadRouteError(msg, req.method || "", req.url || "");
},
});
const body = yield request_1.getRequestData(req);
const response = yield handler(ctx, this.service, body, this.interceptors);
yield Promise.all([
this.invokeHook("responsePrepared", ctx),
// keep backwards compatibility till next release
this.invokeHook("requestPrepared", ctx),
]);
resp.statusCode = 200;
resp.setHeader("Content-Type", mimeContentType);
resp.end(response);
}
catch (e) {
yield this.invokeHook("error", ctx, mustBeTwirpError(e));
if (!resp.headersSent) {
writeError(resp, e);
}
}
finally {
yield Promise.all([
this.invokeHook("responseSent", ctx),
// keep backwards compatibility till next release
this.invokeHook("requestSent", ctx),
]);
}
});
}
/**
* Invoke a hook
* @param hookName
* @param ctx
* @param err
* @protected
*/
invokeHook(hookName, ctx, err) {
return __awaiter(this, void 0, void 0, function* () {
if (this.hooks.length === 0) {
return;
}
const chainedHooks = hooks_1.chainHooks(...this.hooks);
const hook = chainedHooks === null || chainedHooks === void 0 ? void 0 : chainedHooks[hookName];
if (hook) {
yield hook(ctx, err || new errors_1.InternalServerError("internal server error"));
}
});
}
}
exports.TwirpServer = TwirpServer;
/**
* Write http error response
* @param res
* @param error
*/
function writeError(res, error) {
const twirpError = mustBeTwirpError(error);
res.setHeader("Content-Type", "application/json");
res.statusCode = errors_1.httpStatusFromErrorCode(twirpError.code);
res.end(twirpError.toJSON());
}
exports.writeError = writeError;
/**
* Make sure that the error passed is a TwirpError
* otherwise it will wrap it into an InternalError
* @param err
*/
function mustBeTwirpError(err) {
if (err instanceof errors_1.TwirpError) {
return err;
}
return new errors_1.InternalServerErrorWith(err);
}