import { WorkerEntrypoint } from "cloudflare:workers"; import { PluginManifest, SandboxEmailSendCallback, SandboxOptions, SandboxRunner, SandboxRunnerFactory, SandboxedPluginInstance } from "emdash"; import { D1Database } from "@cloudflare/workers-types"; //#region src/sandbox/runner.d.ts interface PluginBridgeProps$1 { pluginId: string; pluginVersion: string; capabilities: string[]; allowedHosts: string[]; storageCollections: string[]; storageConfig?: Record; uniqueIndexes?: Array; }>; } /** * Cloudflare sandbox runner using Worker Loader. */ declare class CloudflareSandboxRunner implements SandboxRunner { private plugins; private options; private resolvedLimits; private siteInfo?; constructor(options: SandboxOptions); /** * Set the email send callback for sandboxed plugins. * Called after the EmailPipeline is created, since the pipeline * doesn't exist when the sandbox runner is constructed. */ setEmailSend(callback: SandboxEmailSendCallback | null): void; /** * Check if Worker Loader is available. */ isAvailable(): boolean; /** * Worker Loader runs in-process, always healthy if available. */ isHealthy(): boolean; /** * Load a sandboxed plugin. * * @param manifest - Plugin manifest with capabilities and storage declarations * @param code - The bundled plugin JavaScript code */ load(manifest: PluginManifest, code: string): Promise; /** * Terminate all loaded plugins. */ terminateAll(): Promise; } /** * Factory function for creating the Cloudflare sandbox runner. * * Matches the SandboxRunnerFactory signature. The LOADER and PluginBridge * are obtained internally from cloudflare:workers imports. */ declare const createSandboxRunner: SandboxRunnerFactory; //#endregion //#region src/sandbox/bridge.d.ts /** * Set the email send callback for all bridge instances. * Called by the runner when the EmailPipeline is available. */ declare function setEmailSendCallback(callback: SandboxEmailSendCallback | null): void; /** * Environment bindings required by PluginBridge */ interface PluginBridgeEnv { DB: D1Database; MEDIA?: R2Bucket; } /** * Props passed to the bridge via ctx.props when creating the loopback binding */ interface PluginBridgeProps { pluginId: string; pluginVersion: string; capabilities: string[]; allowedHosts: string[]; storageCollections: string[]; /** Per-collection storage config (matches manifest.storage entries) */ storageConfig?: Record; uniqueIndexes?: Array; }>; } /** * PluginBridge WorkerEntrypoint * * Provides the context API to sandboxed plugins via RPC. * All methods validate capabilities and scope operations to the plugin. * * Usage: * 1. Export this class from your worker entrypoint * 2. Sandboxed plugins get a binding to it via ctx.exports.PluginBridge({...}) * 3. Plugins call bridge methods which validate and proxy to the database */ declare class PluginBridge extends WorkerEntrypoint { /** * Construct a PluginStorageRepository for the requested collection. * Uses the indexes from the plugin's storage config (if provided) so * query/count operations support WHERE/ORDER BY/cursor pagination * matching in-process and workerd sandbox plugins. */ private getStorageRepo; /** * KV operations use _plugin_storage with a special "__kv" collection. * This provides consistent storage across sandboxed and non-sandboxed modes. */ kvGet(key: string): Promise; kvSet(key: string, value: unknown): Promise; kvDelete(key: string): Promise; kvList(prefix?: string): Promise>; storageGet(collection: string, id: string): Promise; storagePut(collection: string, id: string, data: unknown): Promise; storageDelete(collection: string, id: string): Promise; storageQuery(collection: string, opts?: { limit?: number; cursor?: string; where?: Record; orderBy?: Record; }): Promise<{ items: Array<{ id: string; data: unknown; }>; hasMore: boolean; cursor?: string; }>; storageCount(collection: string, where?: Record): Promise; storageGetMany(collection: string, ids: string[]): Promise>; storagePutMany(collection: string, items: Array<{ id: string; data: unknown; }>): Promise; storageDeleteMany(collection: string, ids: string[]): Promise; contentGet(collection: string, id: string): Promise<{ id: string; type: string; data: Record; createdAt: string; updatedAt: string; } | null>; contentList(collection: string, opts?: { limit?: number; cursor?: string; }): Promise<{ items: Array<{ id: string; type: string; data: Record; createdAt: string; updatedAt: string; }>; cursor?: string; hasMore: boolean; }>; contentCreate(collection: string, data: Record): Promise<{ id: string; type: string; data: Record; createdAt: string; updatedAt: string; }>; contentUpdate(collection: string, id: string, data: Record): Promise<{ id: string; type: string; data: Record; createdAt: string; updatedAt: string; }>; contentDelete(collection: string, id: string): Promise; taxonomyList(opts?: { locale?: string; }): Promise>; taxonomyTerms(taxonomy: string, opts?: { locale?: string; }): Promise | null; locale: string; translationGroup: string | null; }>>; taxonomyEntryTerms(collection: string, entryId: string, opts?: { taxonomy?: string; locale?: string; }): Promise | null; locale: string; translationGroup: string | null; }>>; mediaGet(id: string): Promise<{ id: string; filename: string; mimeType: string; size: number | null; url: string; createdAt: string; } | null>; mediaList(opts?: { limit?: number; cursor?: string; mimeType?: string; }): Promise<{ items: Array<{ id: string; filename: string; mimeType: string; size: number | null; url: string; createdAt: string; }>; cursor?: string; hasMore: boolean; }>; /** * Create a pending media record and write bytes directly to R2. * * Unlike the admin UI flow (presigned URL → client PUT → confirm), sandboxed * plugins are network-isolated and can't make external requests. The bridge * accepts the file bytes directly and writes them to storage. * * Returns the media ID, storage key, and confirm URL. The plugin should * call the confirm endpoint after this to finalize the record. */ mediaUpload(filename: string, contentType: string, bytes: ArrayBuffer): Promise<{ mediaId: string; storageKey: string; url: string; }>; mediaDelete(id: string): Promise; httpFetch(url: string, init?: RequestInit): Promise<{ status: number; headers: Record; text: string; }>; userGet(id: string): Promise<{ id: string; email: string; name: string | null; role: number; createdAt: string; } | null>; userGetByEmail(email: string): Promise<{ id: string; email: string; name: string | null; role: number; createdAt: string; } | null>; userList(opts?: { role?: number; limit?: number; cursor?: string; }): Promise<{ items: Array<{ id: string; email: string; name: string | null; role: number; createdAt: string; }>; nextCursor?: string; }>; emailSend(message: { to: string; subject: string; text: string; html?: string; }): Promise; log(level: "debug" | "info" | "warn" | "error", msg: string, data?: unknown): void; } //#endregion //#region src/sandbox/wrapper.d.ts /** * Options for wrapper generation * * **Known limitation:** `site` info is baked into the generated wrapper code * at load time. If site settings change (e.g., admin updates site name/URL), * sandboxed plugins will see stale values until the worker restarts. * Trusted-mode plugins always read fresh values from the database. */ interface WrapperOptions { /** Site info to inject into the context (no RPC needed) */ site?: { name: string; url: string; locale: string; trailingSlash?: "always" | "never" | "ignore"; }; } declare function generatePluginWrapper(manifest: PluginManifest, options?: WrapperOptions): string; //#endregion export { CloudflareSandboxRunner as a, setEmailSendCallback as i, PluginBridge as n, PluginBridgeProps$1 as o, PluginBridgeEnv as r, createSandboxRunner as s, generatePluginWrapper as t };