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 | 28x 28x 388x 388x 182x 113x 181x 40x 75x 75x 21x 54x 19x 134x 134x 133x | import { MissingBindingError } from '@/exceptions/MissingBindingError'
import type { CloudflareEnv } from '@/types/CloudflareEnv'
/**
* Base class for every Cloudflare binding accessor.
*
* Cloudflare exposes bindings on the per-request `E` object rather than on a
* module-level singleton, so an instance never holds the binding itself — it
* holds the *name* of the binding and resolves it from the `E` passed to
* each call. That keeps instances safe to create once at module scope and
* reuse across requests.
*
* The name arrives already resolved: subclasses take their settings from
* `resolve`, so neither this class nor its callers
* know whether a value was defaulted or overridden.
*
* @typeParam TBinding The Cloudflare binding type this class wraps.
*
* @class
*
* @author Bayu Dwiyan Satria
* @version 1.0.0
* @since 1.0.0
*/
export abstract class Binding<TBinding, E extends CloudflareEnv = CloudflareEnv> {
/**
* Produces the binding name, on first read.
*/
private readonly settle: () => string
/**
* The name once settled, so it is produced only once.
*/
private settled: string | null = null
/**
* Constructs a binding accessor.
*
* @remarks
* The name may arrive as a function, and every subclass passes one. Accessors
* are created at module scope so a single instance is reused across requests,
* and module scope runs while a consumer's imports are still being hoisted,
* before it can call `configure()`. A thunk moves the lookup to first use,
* which is inside a request.
*
* @param binding Binding name as declared in `wrangler.json`, or a function
* producing it when first needed.
*/
protected constructor(binding: string | (() => string)) {
this.settle = typeof binding === 'function' ? binding : () => binding
}
/**
* The binding name this instance resolves.
*
* @returns The binding name as declared in `wrangler.json`.
*/
public get name(): string {
if (this.settled === null) {
this.settled = this.settle()
}
return this.settled
}
/**
* Checks whether the binding is available on the given environment.
*
* Use this to degrade gracefully when a binding is optional.
*
* @param env The Worker environment.
* @returns `true` when the binding is present.
*/
public isBound(env: E): boolean {
return this.lookup(env) !== null
}
/**
* Resolves the binding, failing fast when it is not configured.
*
* @param env The Worker environment.
* @returns The resolved binding.
* @throws {@link MissingBindingError} When the binding is absent from the environment.
*/
public resolve(env: E): TBinding {
const binding = this.lookup(env)
if (binding === null) {
throw new MissingBindingError(this.name)
}
return binding
}
/**
* Resolves the binding without throwing.
*
* @param env The Worker environment.
* @returns The resolved binding, or `null` when it is not configured.
*/
public tryResolve(env: E): TBinding | null {
return this.lookup(env)
}
/**
* Reads the binding off the environment by name.
*
* @param env The Worker environment.
* @returns The binding, or `null` when it is absent.
*/
private lookup(env: E): TBinding | null {
const bindings = env as unknown as Record<string, TBinding | undefined>
const binding = bindings ? bindings[this.name] : undefined
return binding === undefined || binding === null ? null : binding
}
}
|