All files / estuary-rpc-server server.ts

0% Statements 0/28
0% Branches 0/4
0% Functions 0/3
0% Lines 0/28

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127                                                                                                                                                                                                                                                             
import { Api, Endpoint, SimpleMeta, Duplex, ApiTypeOf } from "estuary-rpc";
 
import { ApiContext, RestEndpoints, ServerOpts, WsEndpoints } from "./types";
import { methodId } from "./middleware";
import { createRestServer, restEndpoint } from "./rest";
import { createWsServer, wsEndpoint } from "./ws";
 
function flattenApi<T extends Api<unknown, Meta>, Meta extends SimpleMeta>(
  api: ApiTypeOf<ApiContext, unknown, T>,
  apiMeta: ApiTypeOf<unknown, Meta, T>,
  serverOpts: ServerOpts<Meta>
): [RestEndpoints, WsEndpoints] {
  const restEndpoints: RestEndpoints = {};
  const wsEndpoints: WsEndpoints = {};
 
  Object.keys(apiMeta).forEach((apiName: string) => {
    if (typeof apiMeta[apiName] === "function") {
      const meta = apiMeta[apiName] as Meta;
      if (meta.method === "WS") {
        wsEndpoints[methodId(meta)] = wsEndpoint(
          api[apiName] as Endpoint<
            Duplex<unknown, unknown>,
            void,
            ApiContext,
            unknown
          >,
          meta,
          serverOpts
        );
      } else {
        restEndpoints[methodId(meta)] = restEndpoint(
          api[apiName] as Endpoint<unknown, unknown, ApiContext, unknown>,
          meta,
          serverOpts
        );
      }
    } else {
      const [childRest, childWs] = flattenApi(
        api[apiName] as any,
        apiMeta[apiName] as any,
        serverOpts
      );
      Object.assign(restEndpoints, childRest);
      Object.assign(wsEndpoints, childWs);
    }
  });
  return [restEndpoints, wsEndpoints];
}
 
/**
 * createApiServer is the primary method through which your server code will interact with estuary-rpc.
 * Calling it will set up and start an 'http' server that listens for HTTP and WS connections and appropriately
 * forwards data to your endpoint implementations, taking into account underlying transport encodeings,
 * authentication tokens, and the wonkiness of dealing with WebSockets
 * @param api Your API definition, the collection of all the endpoint implementations
 * @param description Your API Metadata definition, defined in common code with your client
 * @param serverOpts options for configuring the server
 * * @example
 * ```ts
 * // Common Code
 * export interface ExampleApi<Closure, Meta> extends Api<Closure, Meta> {
 *   foo: FooService<Closure, Meta>;
 *   fileUpload: Endpoint<void, void, Closure, Meta>;
 * }
 *
 * export interface FooService<Closure, Meta> extends Api<Closure, Meta> {
 *   emptyPost: Endpoint<void, void, Closure, Meta>;
 *   simpleGet: Endpoint<number, number, Closure, Meta>;
 *   simpleStream: StreamDesc<string, boolean, Closure, Meta>;
 * }
 *
 * export const exampleApiMeta: ExampleApi<never, ExampleMeta> = {
 *   foo: {
 *     emptyPost: post("foo/emptyPost"),
 *     simpleGet: get("foo/simpleGet", { authentication: "bearer", token: "" }),
 *     simpleStream: ws("foo/simpleStream"),
 *   },
 *   fileUpload: post("fileUpload", { uploads: ["someFile.txt"] }),
 * };
 *
 * // Server Code
 * const server: ExampleApi<ApiContext, unknown> = {
 *   foo: {
 *     emptyPost: async () => {
 *       console.log("got post");
 *     },
 *     simpleGet: async (num: number) => 4,
 *     simpleStream: async ({ server }: Duplex<string, boolean>) => {
 *       server.on("message", (input: string) => {
 *         console.log("Got message", input);
 *         server.write(input === "foo");
 *       });
 *     },
 *   } as FooService<ApiContext, unknown>,
 *
 *   fileUpload: (_1: void, _2: ApiContext) => {
 *     return null;
 *   },
 * };
 * ```
 * @group Server
 */
export function createApiServer<
  Meta extends SimpleMeta,
  T extends Api<unknown, Meta>
>(
  api: ApiTypeOf<ApiContext, unknown, T>,
  apiMeta: ApiTypeOf<unknown, Meta, T>,
  serverOpts: ServerOpts<Meta>
) {
  const [restEndpoints, wsEndpoints] = flattenApi(api, apiMeta, serverOpts);
 
  const server = createRestServer(restEndpoints, serverOpts);
  createWsServer(server, wsEndpoints, serverOpts);
  server.listen(serverOpts.port);
 
  console.log("Listening on :", serverOpts.port);
}
 
export * from "estuary-rpc";
export * from "./errors";
export * from "./middleware";
export * from "./multipart";
export * from "./rest";
export * from "./types";
export * from "./ws";