/** * Local Cloudflare runtime adapter for the new `Cloudflare` injection * surface. Backs `private cf: Cloudflare` constructor params (and the * forgiving auto-inject path for plain classes that reference * `this.cf.*`) with a miniflare sandbox when a photon runs outside a * deployed Worker. * * Auto-naming rule (single source of truth — `bindingNameFor`): * cf.kv() → _kv * cf.kv('x') → _x_kv * cf.ai → AI (shared, single per Worker) * cf.images → IMAGES (shared) * cf.browser → BROWSER (shared) * * Photons no longer pick global binding names. The framework derives * them from the photon name plus an optional qualifier so per-photon * resources never collide. * * Boot is keyed off the `cf-usage-scanner` output: every literal * qualifier the photon uses is configured in miniflare up front, so * the first call to a binding doesn't pay a reconfigure cost. Dynamic * qualifiers (`cf.kv(this.tenantId)`) only get the default binding * unless the author lists overrides in `protected cfBindings`. * * Storage persists under `/.data/cf-sandbox//` so * state survives process restarts and matches the Phase A2 layout. */ import { type Cloudflare, type ScopedBindingCategory, type R2BucketLike, type KVNamespaceLike, type D1DatabaseLike, type QueueLike, type VectorizeIndexLike, type AiLike, type ImagesBindingLike, type FetcherLike } from '@portel/photon-core'; import { type CfUsage } from '../cf-usage-scanner.js'; /** * Optional override layer. Most photons leave this empty — the * auto-naming convention covers the common case. Authors set it when * they need a binding to point at a pre-existing CF resource owned * outside the photon (typically only matters at deploy time, not for * the local sandbox). * * Schema mirrors the deploy-side override JSON (~/.photon/state/...). * For local boot, we accept the same shape so a single * `protected cfBindings` block round-trips through both layers. */ export interface CfOverrides { /** Per-qualifier override of the resource name a binding points at. */ kv?: Record; r2?: Record; d1?: Record; queue?: Record; vectorize?: Record; } export interface CFLocalRuntimeOptions { photonName: string; baseDir: string; /** * Static-analysis output from cf-usage-scanner. Determines which * binding names miniflare boots with. When absent, only the * default per-category binding is configured (callers that hit a * qualified binding without source-scanning will get a clear miss * error from miniflare). */ usage?: CfUsage; /** Optional `protected cfBindings` override layer, see CfOverrides. */ overrides?: CfOverrides; } /** * Legacy override schema. The Phase A2 release shipped `cfBindings` * as a `Record` declaration that doubled as * a binding declaration. Under the new auto-naming model * (bindingNameFor) the same shape becomes a pure override layer — * authors who want to repoint a default binding at a pre-existing * resource still write the same JSON. Kept exported so deploy code * and the photon-cf CLI keep building during the migration. */ export type CfBindingsConfig = CfOverrides & { ai?: boolean; images?: boolean; browser?: boolean; }; /** * Merge two CfBindingsConfig blobs. Used by the deploy pipeline to * stack a per-photon override JSON on top of in-source declarations. * Boolean shared flags are last-write-wins; per-binding records merge * key-by-key. */ export declare function mergeBindings(base: CfBindingsConfig | null | undefined, override: CfBindingsConfig | null | undefined): CfBindingsConfig; export declare class CFLocalRuntime implements Cloudflare { private mfPromise; private readonly photonName; private readonly baseDir; private readonly usage; private readonly overrides; /** * Legacy mode means the photon was constructed via the Phase-A2 * positional `(name, cfBindings, baseDir)` form. In that mode the * `qualifier` arg passed to `cf.kv(...)` etc. is treated as a * literal binding name (not run through `bindingNameFor`), and * miniflare seeds the names declared in `overrides.`. * The new options-form constructor flips this to false and the * auto-naming convention takes over. */ private readonly legacyMode; private readonly legacyBooleanFlags; /** * New options-object form (preferred). Hosts that source-scan the * photon for `Cloudflare` usage pass the scanner output as `usage` * so miniflare seeds every literal-qualified binding upfront. */ constructor(opts: CFLocalRuntimeOptions); /** * Legacy positional form retained for the classic loader and * existing CF-runtime tests. Translates the Phase-A2 `cfBindings` * declaration to the new override schema; auto-naming is disabled * (no usage scan) so explicit binding names take precedence over * `_` defaults. Callers should migrate to the * options form when the demolition commit lands. * * @deprecated use the options-object constructor. */ constructor(photonName: string, bindings: CfBindingsConfig, baseDir: string); /** Legacy alias retained for callers that built against the Phase-A2 API. */ getBindings(): CfBindingsConfig; /** Inspector for tools/CLI: lists every binding miniflare seeded. */ getSeededBindings(): Record; /** * Resolve the miniflare binding name for a `cf.(qualifier?)` * call. In auto-naming mode runs through `bindingNameFor`; in * legacy mode treats the arg as a literal binding name (matching * the Phase-A2 `cf.kv(name)` semantics). */ private resolveBindingName; dispose(): Promise; kv(qualifier?: string): KVNamespaceLike; r2(qualifier?: string): R2BucketLike; d1(qualifier?: string): D1DatabaseLike; queue(qualifier?: string): QueueLike; vectorize(qualifier?: string): VectorizeIndexLike; get ai(): AiLike; get images(): ImagesBindingLike; get browser(): FetcherLike; fetch(_input: string, _init?: unknown): Promise; /** Lazily boot a Miniflare instance scoped to this photon. */ private getMiniflare; /** * Build a thin proxy whose methods defer to a binding fetched via * `mf.getKVNamespace(name)` / `mf.getR2Bucket(name)` / etc on first * invocation. Avoids paying the boot cost on cold paths. */ private scopedBinding; /** * Pull a binding off the miniflare-built env (for AI, Vectorize, * Images — categories that surface as env props rather than via a * dedicated `mf.getXxx(name)` accessor). */ private envBindingProxy; } //# sourceMappingURL=cf-local.d.ts.map