All files / src/core/bindings VpcServiceBinding.ts

100% Statements 7/7
100% Branches 0/0
100% Functions 3/3
100% Lines 6/6

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              28x 28x                                                                                       28x               9x   9x                                             5x      
/**
 * @module
 *
 * @author Bayu Dwiyan Satria
 * @version 1.0.0
 * @since 1.3.0
 */
import { Binding } from '@/core/bindings/Binding'
import { lazySettings } from '@/utils/lazySettings'
import { VpcServiceSettings } from '@/types/VpcServiceSettings'
 
import type { CloudflareEnv } from '@/types/CloudflareEnv'
 
/**
 * Accessor for a VPC Service binding.
 *
 * @remarks
 * Reaches one service on a private network — an internal API, a database's HTTP
 * front, something in an AWS VPC or on a rack — without that service being
 * exposed to the internet. Traffic goes through a Cloudflare Tunnel, so the
 * private side needs no inbound firewall rule at all.
 *
 * The destination is fixed in `wrangler.json` as a `service_id`, which is the
 * property worth understanding rather than working around: a Worker holding
 * this binding can reach exactly one private endpoint, and no request it
 * handles can talk it into reaching another. {@link VpcNetworkBinding} trades
 * that away deliberately, and the choice between them is a security decision
 * before it is an ergonomic one.
 *
 * ## `Fetcher`, and no invented type
 *
 * The binding offers `fetch` and nothing else, which is `Fetcher` structurally
 * — the same reasoning that types {@link BrowserBinding}. That matters more
 * here than it did there, because `@cloudflare/workers-types` declares nothing
 * for Workers VPC at the version this package pins. Naming `Fetcher` means this
 * class describes no shape of its own while the product is in beta.
 *
 * {@link VpcNetworkBinding} could not manage the same trick, and says so.
 *
 * @example
 * ```ts
 * const internal = new VpcServiceBinding()
 *
 * const response = await internal.fetch(env, 'https://internal-api.example.com/positions')
 * ```
 *
 * @class
 *
 * @author Bayu Dwiyan Satria
 * @version 1.0.0
 * @since 1.3.0
 */
export class VpcServiceBinding<E extends CloudflareEnv = CloudflareEnv> extends Binding<Fetcher, E> {
  /**
   * Constructs a VpcServiceBinding instance.
   *
   * @param settings Resolved settings. Defaults to `resolve('vpcService')`, so callers
   *   normally construct this with no arguments at all.
   */
  constructor(settings?: VpcServiceSettings) {
    const settle = lazySettings<VpcServiceSettings>('vpcService', settings)
 
    super(() => settle().binding)
  }
 
  /**
   * Sends a request to the bound private service.
   *
   * @remarks
   * The hostname in the URL is resolved on the private side, so it needs to be
   * the name that side answers to rather than anything public. The binding
   * decides where the request goes regardless; the URL supplies the path,
   * method and body.
   *
   * Failures arrive as they would from any `fetch` — an unreachable tunnel is a
   * rejected promise, not a status code — so a caller that must not fail with
   * the private side should catch here rather than inspect the response.
   *
   * @param env The Worker environment.
   * @param resource The request, as a URL, string, or `Request`.
   * @param options Standard request options.
   * @returns The response from the private service.
   * @throws {@link MissingBindingError} When the VPC Service binding is absent.
   */
  public async fetch(env: E, resource: string | URL | Request, options?: RequestInit): Promise<Response> {
    return await this.resolve(env).fetch(resource as RequestInfo, options)
  }
}