Skip to content

Instantly share code, notes, and snippets.

@xesrevinu
Last active July 15, 2025 14:00
Show Gist options
  • Select an option

  • Save xesrevinu/cf545dda28df77fdaf5b049aa44c0bc6 to your computer and use it in GitHub Desktop.

Select an option

Save xesrevinu/cf545dda28df77fdaf5b049aa44c0bc6 to your computer and use it in GitHub Desktop.
Simple Cloudflare Miniflare effect layer
import { D1Database, DurableObjectNamespace, KVNamespace, Queue, R2Bucket, Socket, SocketAddress, SocketOptions } from '@cloudflare/workers-types'
import { FileSystem, Path } from '@effect/platform'
import type { PlatformError } from '@effect/platform/Error'
import * as Context from 'effect/Context'
import * as Effect from 'effect/Effect'
import * as Layer from 'effect/Layer'
import * as LogLevelE from 'effect/LogLevel'
import * as Esbuild from 'esbuild'
import { Log, LogLevel, Miniflare as MiniflareBase, type MiniflareOptions, type WorkerOptions } from 'miniflare'
import { unstable_readConfig } from 'wrangler'
interface TestModule {
path: string
config: string
}
interface TestOptions {
cwd: string
port?: number | undefined
alias?: Record<string, string> | undefined
}
export const make = (options: TestOptions, modules: TestModule[]) =>
Effect.gen(function* () {
const { cwd, alias, port = 5999 } = options
const fs = yield* FileSystem.FileSystem
const { join, basename } = yield* Path.Path
const scriptOutputPath = join(cwd, '.scripts')
const miniflarCachePath = '.miniflare-cache'
const cachePersistPath = join(miniflarCachePath, 'cache')
const d1PersistPath = join(miniflarCachePath, 'd1')
const kvPersistPath = join(miniflarCachePath, 'kv')
const r2PersistPath = join(miniflarCachePath, 'r2')
const durableObjectsPersistPath = join(miniflarCachePath, 'durable-objects')
const workflowsPersistPath = join(miniflarCachePath, 'workflows')
const analyticsEngineDatasetsPersistPath = join(miniflarCachePath, 'analytics-engine-datasets')
const cachePersist = join(cwd, cachePersistPath)
const d1Persist = join(cwd, d1PersistPath)
const kvPersist = join(cwd, kvPersistPath)
const r2Persist = join(cwd, r2PersistPath)
const durableObjectsPersist = join(cwd, durableObjectsPersistPath)
const workflowsPersist = join(cwd, workflowsPersistPath)
const analyticsEngineDatasetsPersist = join(cwd, analyticsEngineDatasetsPersistPath)
const wranglerConfigs = modules.map((_) => unstable_readConfig({ config: _.config }))
const scripts = yield* Effect.forEach(
wranglerConfigs,
(item) =>
Effect.promise(() =>
Esbuild.build({
entryPoints: [item.main!],
bundle: true,
write: false,
format: 'esm',
target: 'es2022',
platform: 'browser',
define: {
'process.env.NODE_ENV': '"development"',
},
legalComments: 'none',
treeShaking: true,
metafile: true,
sourcemap: 'linked',
outfile: `${item.name!}.js`,
external: ['cloudflare:*', 'node:*'],
alias,
}),
),
{
concurrency: 3,
},
).pipe(
Effect.map((results) =>
results.flatMap((result) => {
const outputName = basename(result.outputFiles[0].path).replace('.map', '').replace('.js', '')
const metafileJson = JSON.stringify(result.metafile, null, 2)
const metafile = {
main: false,
filename: `${outputName}.metafile.json`,
contents: new TextEncoder().encode(metafileJson),
text: metafileJson,
}
return result.outputFiles
.map((file) => {
const filename = basename(file.path)
const isMain = filename.indexOf('.map') === -1
return {
filename,
main: isMain,
contents: file.contents,
text: file.text,
}
})
.concat(metafile)
}),
),
)
yield* Effect.forEach(scripts, (script) => {
const path = join(scriptOutputPath, script.filename)
return fs.writeFile(path, script.contents)
})
const workerScripts = scripts.filter((script) => !!script.main)
const workers: WorkerOptions[] = wranglerConfigs.map((config, index) => {
const script = workerScripts[index].text
return {
name: config.name,
modules: true,
script,
compatibilityFlags: config.compatibility_flags,
compatibilityDate: config.compatibility_date,
cache: true,
d1Databases: Object.fromEntries(
config.d1_databases.map((_) => {
return [_.binding, _.database_id || '']
}),
),
kvNamespaces: Object.fromEntries(
config.kv_namespaces.map((_) => {
return [_.binding, _.id || '']
}),
),
r2Buckets: Object.fromEntries(
config.r2_buckets.map((_) => {
return [_.binding, _.bucket_name || '']
}),
),
durableObjects: Object.fromEntries(
config.durable_objects.bindings.map((_) => {
return [
_.name,
{
className: _.class_name,
scriptName: _.script_name,
useSQLite: true,
},
]
}),
),
serviceBindings: Object.fromEntries(
(config.services || []).map((_) => {
return [
_.binding,
{
name: _.service,
entrypoint: _.entrypoint,
},
]
}),
),
workflows: Object.fromEntries(
config.workflows.map((_) => {
return [
_.name,
{
name: _.name,
className: _.class_name,
scriptName: _.script_name,
},
]
}),
),
analyticsEngineDatasets: Object.fromEntries(
config.analytics_engine_datasets.map((_) => {
return [
_.binding,
{
dataset: _.dataset || '',
},
]
}),
),
bindings: {
NODE_ENV: 'development',
LOG_LEVEL: LogLevelE.All._tag,
STAGE: 'test',
},
} satisfies WorkerOptions
})
const miniflare = new MiniflareBase({
log: new Log(LogLevel.DEBUG), // Logger Miniflare uses for debugging
port,
inspectorPort: port - 1,
cachePersist,
d1Persist,
kvPersist,
r2Persist,
durableObjectsPersist,
workflowsPersist,
analyticsEngineDatasetsPersist,
workers,
liveReload: false,
verbose: false,
})
yield* Effect.addFinalizer(() => Effect.promise(() => miniflare.dispose()))
yield* Effect.promise(() => miniflare.ready)
return {
getCf: () => Effect.promise(() => miniflare.getCf()),
getInspectorURL: () => Effect.promise(() => miniflare.getInspectorURL()),
setOptions: (opts: MiniflareOptions) => Effect.promise(() => miniflare.setOptions(opts)),
url: Effect.sync(() => new URL(`http://localhost:${port}`)),
fetch: (input: globalThis.RequestInfo | URL, init?: globalThis.RequestInit) =>
Effect.promise(() => miniflare.dispatchFetch(input as any, init as any) as unknown as Promise<Response>),
unsafeGetDirectURL: (workerName: string) => Effect.promise(() => miniflare.unsafeGetDirectURL(workerName)),
getBindings: <Env = Record<string, unknown>>(workerName?: string) =>
Effect.promise(() => miniflare.getBindings<Env>(workerName)),
getWorker: (workerName?: string | undefined) => Effect.promise(() => miniflare.getWorker(workerName)),
getCaches: () => Effect.promise(() => miniflare.getCaches()),
getD1Database: (bindingName: string, workerName?: string) =>
Effect.promise(() => miniflare.getD1Database(bindingName, workerName)),
getDurableObjectNamespace: (bindingName: string, workerName?: string) =>
Effect.promise(() => miniflare.getDurableObjectNamespace(bindingName, workerName)),
getKVNamespace: (bindingName: string, workerName?: string) =>
Effect.promise(() => miniflare.getKVNamespace(bindingName, workerName)),
getQueueProducer: <Body = unknown>(bindingName: string, workerName?: string) =>
Effect.promise(() => miniflare.getQueueProducer<Body>(bindingName, workerName)),
getR2Bucket: (bindingName: string, workerName?: string) =>
Effect.promise(() => miniflare.getR2Bucket(bindingName, workerName)),
}
})
type Fetcher = {
fetch(input: globalThis.RequestInfo | URL, init?: globalThis.RequestInit): Promise<globalThis.Response>
connect(address: SocketAddress | string, options?: SocketOptions): Socket
}
export class Miniflare extends Context.Tag('Miniflare')<
Miniflare,
{
getCf(): Effect.Effect<Record<string, any>>
getInspectorURL(): Effect.Effect<URL>
url: Effect.Effect<URL>
setOptions(opts: MiniflareOptions): Effect.Effect<void>
fetch: (input: globalThis.RequestInfo | URL, init?: globalThis.RequestInit) => Effect.Effect<globalThis.Response>
unsafeGetDirectURL: (workerName: string) => Effect.Effect<URL, never, never>
getBindings<Env = Record<string, unknown>>(workerName?: string | undefined): Effect.Effect<Env>
getWorker(workerName?: string | undefined): Effect.Effect<{
fetch: Fetcher['fetch']
}>
getCaches(): Effect.Effect<CacheStorage>
getD1Database(bindingName: string, workerName?: string | undefined): Effect.Effect<D1Database>
getDurableObjectNamespace(
bindingName: string,
workerName?: string | undefined,
): Effect.Effect<DurableObjectNamespace>
getKVNamespace(bindingName: string, workerName?: string | undefined): Effect.Effect<KVNamespace>
getQueueProducer<Body = unknown>(bindingName: string, workerName?: string | undefined): Effect.Effect<Queue<Body>>
getR2Bucket(bindingName: string, workerName?: string | undefined): Effect.Effect<R2Bucket>
}
>() {
static Config: (
options: TestOptions,
modules: TestModule[],
) => Layer.Layer<Miniflare, PlatformError, FileSystem.FileSystem | Path.Path> = (
options: TestOptions,
modules: TestModule[],
// F**K CF Types
) => Layer.scoped(Miniflare, make(options, modules) as any)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment