Skip to content

Instantly share code, notes, and snippets.

@cristianoc
Forked from zth/bun-headers.md
Created November 1, 2023 13:57
Show Gist options
  • Save cristianoc/d556de8581fad3a53a67294b67f495c5 to your computer and use it in GitHub Desktop.
Save cristianoc/d556de8581fad3a53a67294b67f495c5 to your computer and use it in GitHub Desktop.
TS -> ReScript examples

TypeScript:

/**
 * This Fetch API interface allows you to perform various actions on HTTP
 * request and response headers. These actions include retrieving, setting,
 * adding to, and removing. A Headers object has an associated header list,
 * which is initially empty and consists of zero or more name and value
 * pairs.
 *
 * You can add to this using methods like append()
 *
 * In all methods of this interface, header names are matched by
 * case-insensitive byte sequence.
 */
interface Headers {
  append(name: string, value: string): void;
  delete(name: string): void;
  get(name: string): string | null;
  has(name: string): boolean;
  set(name: string, value: string): void;
  entries(): IterableIterator<[string, string]>;
  keys(): IterableIterator<string>;
  values(): IterableIterator<string>;
  [Symbol.iterator](): IterableIterator<[string, string]>;
  forEach(
    callbackfn: (value: string, key: string, parent: Headers) => void,
    thisArg?: any,
  ): void;

  /**
   * Convert {@link Headers} to a plain JavaScript object.
   *
   * About 10x faster than `Object.fromEntries(headers.entries())`
   *
   * Called when you run `JSON.stringify(headers)`
   *
   * Does not preserve insertion order. Well-known header names are lowercased. Other header names are left as-is.
   */
  toJSON(): Record<string, string>;

  /**
   * Get the total number of headers
   */
  readonly count: number;

  /**
   * Get all headers matching the name
   *
   * Only supports `"Set-Cookie"`. All other headers are empty arrays.
   *
   * @param name - The header name to get
   *
   * @returns An array of header values
   *
   * @example
   * ```ts
   * const headers = new Headers();
   * headers.append("Set-Cookie", "foo=bar");
   * headers.append("Set-Cookie", "baz=qux");
   * headers.getAll("Set-Cookie"); // ["foo=bar", "baz=qux"]
   * ```
   */
  getAll(name: "set-cookie" | "Set-Cookie"): string[];
}

declare var Headers: {
  prototype: Headers;
  new (init?: HeadersInit): Headers;
};

ReScript:

module HeadersInit = {
  @unboxed type t = FromArray(array<(string, string)>) | FromDict(Js.Dict.t<string>)
}

/**
 * This Fetch API interface allows you to perform various actions on HTTP
 * request and response headers. These actions include retrieving, setting,
 * adding to, and removing. A Headers object has an associated header list,
 * which is initially empty and consists of zero or more name and value
 * pairs.
 *
 * You can add to this using methods like append()
 *
 * In all methods of this interface, header names are matched by
 * case-insensitive byte sequence.
 */
module Headers = {
  type t

  @new external make: unit => t = "Headers"
  @new external makeWithInit: HeadersInit.t => t = "Headers"

  @send external append: (t, string, string) => unit = "append"
  @send external delete: (t, string) => unit = "delete"
  @return(nullable) @send external get: (t, string) => option<string> = "get"
  @send external has: (t, string) => bool = "has"
  @send external set: (t, string, string) => unit = "set"
  @send external entries: t => Iterator.t<(string, string)> = "entries"
  @send external keys: t => Iterator.t<string> = "keys"
  @send external values: t => Iterator.t<string> = "values"
  @send external forEach: (t, (string, string, t) => unit) => unit = "forEach"

  /**
   * Convert {@link Headers} to a plain JavaScript object.
   *
   * About 10x faster than `Object.fromEntries(headers.entries())`
   *
   * Called when you run `JSON.stringify(headers)`
   *
   * Does not preserve insertion order. Well-known header names are lowercased. Other header names are left as-is.
   */
  @send
  external toJSON: t => Dict.t<string> = "toJSON"

  /**
   * Get the total number of headers
   */
  @get
  external count: t => int = "count"

  /**
   * Get all headers matching "Set-Cookie"
   *
   * Only supports `"Set-Cookie"`. All other headers are empty arrays.
   *
   * @returns An array of header values
   *
   * @example
   * ```rescript
   * let headers = Headers.make()
   * headers->Headers.append("Set-Cookie", "foo=bar")
   * headers->Headers.append("Set-Cookie", "baz=qux")
   * let cookies = headers->Headers.getAllCookies // ["foo=bar", "baz=qux"]
   * ```
   */
  @send
  external getAllCookies: (t, @as("Set-Cookie") _) => array<string> = "getAll"
}

TypeScript:

interface URLSearchParams {
  /** Appends a specified key/value pair as a new search parameter. */
  append(name: string, value: string): void;
  /** Deletes the given search parameter, and its associated value, from the list of all search parameters. */
  delete(name: string): void;
  /** Returns the first value associated to the given search parameter. */
  get(name: string): string | null;
  /** Returns all the values association with a given search parameter. */
  getAll(name: string): string[];
  /** Returns a Boolean indicating if such a search parameter exists. */
  has(name: string): boolean;
  /** Sets the value associated to a given search parameter to the given value. If there were several values, delete the others. */
  set(name: string, value: string): void;
  sort(): void;
  entries(): IterableIterator<[string, string]>;
  /** Returns an iterator allowing to go through all keys of the key/value pairs of this search parameter. */
  keys(): IterableIterator<string>;
  /** Returns an iterator allowing to go through all values of the key/value pairs of this search parameter. */
  values(): IterableIterator<string>;
  forEach(
    callbackfn: (value: string, key: string, parent: URLSearchParams) => void,
    thisArg?: any,
  ): void;
  /** Returns a string containing a query string suitable for use in a URL. Does not include the question mark. */
  toString(): string;
  [Symbol.iterator](): IterableIterator<[string, string]>;
}

declare var URLSearchParams: {
  prototype: URLSearchParams;
  new (
    init?: string[][] | Record<string, string> | string | URLSearchParams,
  ): URLSearchParams;
  toString(): string;
};

ReScript:

module URLSearchParams = {
  type t

  @unboxed type init = Object(Dict.t<string>) | String(string) | Array(array<array<string>>)

  @new external make: unit => t = "URLSearchParams"
  @new external makeWithInit: init => t = "URLSearchParams"

  /** Appends a specified key/value pair as a new search parameter. */
  @send
  external append: (t, string, string) => unit = "append"

  /** Deletes the given search parameter, and its associated value, from the list of all search parameters. */
  @send
  external delete: (t, string) => unit = "delete"

  /** Returns the first value associated to the given search parameter. */
  @send
  @return(nullable)
  external get: (t, string) => option<string> = "get"

  /** Returns all the values association with a given search parameter. */
  @send
  external getAll: (t, string) => array<string> = "getAll"

  /** Returns a Boolean indicating if such a search parameter exists. */
  @send
  external has: (t, string) => bool = "has"

  /** Sets the value associated to a given search parameter to the given value. If there were several values, delete the others. */
  @send
  external set: (t, string, string) => unit = "set"

  /** Sorts all key/value pairs, if any, by their keys. */
  @send
  external sort: t => unit = "sort"

  /** Returns an iterator allowing to go through all entries of the key/value pairs. */
  @send
  external entries: t => Iterator.t<(string, string)> = "entries"

  /** Returns an iterator allowing to go through all keys of the key/value pairs of this search parameter. */
  @send
  external keys: t => Iterator.t<string> = "keys"

  /** Returns an iterator allowing to go through all values of the key/value pairs of this search parameter. */
  @send
  external values: t => Iterator.t<string> = "values"

  /** Executes a provided function once for each key/value pair. */
  @send
  external forEach: (t, (string, string, t) => unit) => unit = "forEach"

  /** Returns a string containing a query string suitable for use in a URL. Does not include the question mark. */
  @send
  external toString: t => string = "toString"
}

TypeScript:

export type YogaServerOptions<TServerContext, TUserContext> = {
  schema?: YogaSchemaDefinition<TServerContext, TUserContext> | undefined;
 
  /**
   * Enable/disable logging or provide a custom logger.
   * @default true
   */
  logging?: boolean | YogaLogger | LogLevel | undefined;
  /**
   * Prevent leaking unexpected errors to the client. We highly recommend enabling this in production.
   * If you throw `EnvelopError`/`GraphQLError` within your GraphQL resolvers then that error will be sent back to the client.
   *
   * You can lean more about this here:
   * @see https://graphql-yoga.vercel.app/docs/features/error-masking
   *
   * @default true
   */
  maskedErrors?: boolean | Partial<YogaMaskedErrorOpts> | undefined;

  /**
   * GraphQL endpoint
   * So you need to define it explicitly if GraphQL API lives in a different path other than `/graphql`
   *
   * @default "/graphql"
   */
  graphqlEndpoint?: string | undefined;

  /**
   * Readiness check endpoint
   *
   * @default "/health"
   */
  healthCheckEndpoint?: string | undefined;

  /**
   * Whether the landing page should be shown.
   */
  landingPage?: boolean | undefined;

  /**
   * GraphiQL options
   *
   * @default true
   */
  graphiql?: GraphiQLOptionsOrFactory<TServerContext> | undefined;

  /**
   * GraphQL Multipart Request spec support
   *
   * @see https://github.com/jaydenseric/graphql-multipart-request-spec
   *
   * @default true
   */
  multipart?: boolean | undefined;
  id?: string | undefined;
  /**
   * Batching RFC Support configuration
   *
   * @see https://github.com/graphql/graphql-over-http/blob/main/rfcs/Batching.md
   *
   * @default false
   */
  batching?: BatchingOptions | undefined;
};

export type BatchingOptions =
  | boolean
  | {
      /**
       * You can limit the number of batched operations per request.
       *
       * @default 10
       */
      limit?: number;
    };
 
export type YogaLogger = Record<LogLevel, (...args: any[]) => void>;

export type LogLevel = 'debug' | 'info' | 'warn' | 'error';

export type YogaMaskedErrorOpts = {
  maskError: MaskError;
  errorMessage: string;
  isDev?: boolean;
};

export type MaskError = (error: unknown, message: string, isDev?: boolean) => Error;

ReScript:

type logFn = unknown => unit
type yogaLogger = {debug: logFn, info: logFn, warn: logFn, error: logFn}

@unboxed
type logging =
  | @as(true) True
  | @as(false) False
  | YogaLogger(yogaLogger)
  | @as("debug") LogLevelDebug
  | @as("info") LogLevelInfo
  | @as("warn") LogLevelWarn
  | @as("error") LogLevelError
  
type batchingOptions = {
  /**
   * You can limit the number of batched operations per request.
   *
   * @default 10
   */
  limit?: int,
}

@unboxed type batchingConfig = | @as(true) True | @as(false) False | Options(batchingOptions)

type maskErrorFn = (~error: unknown, ~message: string, ~isDev: option<bool>) => Exn.t
type maskedErrorOpts = {
  maskError?: maskErrorFn,
  errorMessage?: string,
  isDev?: bool,
}

@unboxed
type maskedErrors =
  | @as(true) True
  | @as(false) False
  | Options(maskedErrorOpts)

/**
 * Configuration options for the server
 */
type yogaServerOptions<'tServerContext, 'tUserContext> = {
  schema: YogaSchemaDefinition.t<'tServerContext, 'tUserContext>,
  /**
   * Enable/disable logging or provide a custom logger.
   * @default true
   */
  logging?: logging,
  /**
   * Prevent leaking unexpected errors to the client. We highly recommend enabling this in production.
   * If you throw `EnvelopError`/`GraphQLError` within your GraphQL resolvers then that error will be sent back to the client.
   *
   * You can lean more about this here:
   * @see https://graphql-yoga.vercel.app/docs/features/error-masking
   *
   * @default true
   */
  maskedErrors?: maskedErrors,
  /**
   * GraphQL endpoint
   * So you need to define it explicitly if GraphQL API lives in a different path other than `/graphql`
   *
   * @default "/graphql"
   */
  graphqlEndpoint?: string,
  /**
   * Readiness check endpoint
   *
   * @default "/health"
   */
  healthCheckEndpoint?: string,
  /**
   * Whether the landing page should be shown.
   */
  landingPage?: bool,
  parserCache?: bool,
  validationCache?: bool,
  /**
   * GraphQL Multipart Request spec support
   *
   * @see https://github.com/jaydenseric/graphql-multipart-request-spec
   *
   * @default true
   */
  multipart?: bool,
  id?: string,
  /**
   * Batching RFC Support configuration
   *
   * @see https://github.com/graphql/graphql-over-http/blob/main/rfcs/Batching.md
   *
   * @default false
   */
  batching?: batchingConfig,
  /**
   * Whether to use the legacy Yoga Server-Sent Events and not
   * the GraphQL over SSE spec's distinct connection mode.
   *
   * @default true
   *
   * @deprecated Consider using GraphQL over SSE spec instead by setting this to `false`. Starting with the next major release, this flag will default to `false`.
   */
  legacySse?: bool,
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment