{"_id":"@emergente-labs/effect-cloudflare","_rev":"3-8853ce354e26bcf1994bb82865e19f52","name":"@emergente-labs/effect-cloudflare","dist-tags":{"latest":"0.3.0"},"versions":{"0.1.1":{"name":"@emergente-labs/effect-cloudflare","version":"0.1.1","keywords":["effect","effect-ts","cloudflare","workers","kv","r2","queue","circuit-breaker","cache"],"author":{"name":"Emergente Labs"},"license":"MIT","_id":"@emergente-labs/effect-cloudflare@0.1.1","maintainers":[{"name":"francisco-m001","email":"francisco@sellhub.cx"}],"homepage":"https://github.com/emergente-labs/effect-cloudflare#readme","bugs":{"url":"https://github.com/emergente-labs/effect-cloudflare/issues"},"dist":{"shasum":"ea9427dc9f5e75052fde887a9da401fd5e1137d9","tarball":"https://registry.npmjs.org/@emergente-labs/effect-cloudflare/-/effect-cloudflare-0.1.1.tgz","fileCount":12,"integrity":"sha512-c65pE1CrsHYDWEIATjFeCVxBTtFvrXHVEzubBuc6kawRlEKdig8M3YhhfdtJkxSxwhuK0mtaF1JXisQlGl4Zgw==","signatures":[{"sig":"MEUCIQDuiGM28gQuAdxGy0cWB9WTSSBKwL8ZsMEqE+oNubVypQIgEb1WQFRuC91+JsBen25isUqi5vs6SRGeIJztQdFcK8E=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":189990},"type":"module","types":"./dist/index.d.ts","engines":{"node":">=18.0.0"},"exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"./worker":{"types":"./dist/worker/layer.d.ts","default":"./dist/worker/layer.js"}},"gitHead":"0a7ba222e99921baa7f9c4d16b84b1191a450378","scripts":{"test":"vitest run","build":"tsup","prepare":"pnpm build","typecheck":"tsc --noEmit","test:watch":"vitest","prepublishOnly":"pnpm run build && pnpm run typecheck"},"_npmUser":{"name":"francisco-m001","email":"francisco@sellhub.cx"},"repository":{"url":"git+https://github.com/emergente-labs/effect-cloudflare.git","type":"git"},"_npmVersion":"10.8.2","description":"Effect-TS toolkit for Cloudflare Workers: fetch/queue handlers, config provider bridge, tiered caching, circuit breaker, and crypto utilities","directories":{},"_nodeVersion":"20.20.0","dependencies":{"effect":"^3.19.8","@effect/platform":"^0.93.4"},"_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.4.0","vitest":"^4.0.16","typescript":"^5.9.3","@types/node":"^24.0.0","@effect/vitest":"^0.27.0","@cloudflare/workers-types":"^4.20250117.0"},"_npmOperationalInternal":{"tmp":"tmp/effect-cloudflare_0.1.1_1771348494549_0.5514625784953879","host":"s3://npm-registry-packages-npm-production"}},"0.2.0":{"name":"@emergente-labs/effect-cloudflare","version":"0.2.0","keywords":["effect","effect-ts","cloudflare","workers","kv","r2","queue","circuit-breaker","cache"],"author":{"name":"Emergente Labs"},"license":"MIT","_id":"@emergente-labs/effect-cloudflare@0.2.0","maintainers":[{"name":"francisco-m001","email":"francisco@sellhub.cx"}],"homepage":"https://github.com/emergente-labs/effect-cloudflare#readme","bugs":{"url":"https://github.com/emergente-labs/effect-cloudflare/issues"},"dist":{"shasum":"b700bc47108e60ba7f1afbbe9985dbf612bcc7c6","tarball":"https://registry.npmjs.org/@emergente-labs/effect-cloudflare/-/effect-cloudflare-0.2.0.tgz","fileCount":12,"integrity":"sha512-8x3yNWYJxB9hrpW4GaXl9OFYirHlbFdVsp4joxCQ8RVhOyN0xTrRjD7BeoMx4wGNwpPm+ZxtNaYvLWCnXMWwDQ==","signatures":[{"sig":"MEUCIQDk+jZzZQjetZk3oU9OjbQ+gshYyIPaSKeIqUsFrPy0mQIgDdabAY9wFWFGeYjiibBw9Mipc2GV4urW9ZGkRYk924k=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":169349},"type":"module","types":"./dist/index.d.ts","engines":{"node":">=18.0.0"},"exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"./worker":{"types":"./dist/worker/layer.d.ts","default":"./dist/worker/layer.js"}},"gitHead":"e7f553e36927363e2cb83c8b3b60b223adb3ae9c","scripts":{"test":"vitest run","build":"tsup","prepare":"pnpm build","typecheck":"tsc --noEmit","test:watch":"vitest","prepublishOnly":"pnpm run build && pnpm run typecheck"},"_npmUser":{"name":"francisco-m001","email":"francisco@sellhub.cx"},"repository":{"url":"git+https://github.com/emergente-labs/effect-cloudflare.git","type":"git"},"_npmVersion":"10.8.2","description":"Effect-TS toolkit for Cloudflare Workers: fetch/queue handlers, config provider bridge, tiered caching, circuit breaker, and crypto utilities","directories":{},"_nodeVersion":"20.20.0","dependencies":{"effect":"^3.19.8","@effect/platform":"^0.93.4"},"_hasShrinkwrap":false,"devDependencies":{"tsup":"^8.4.0","vitest":"^4.0.16","typescript":"^5.9.3","@types/node":"^24.0.0","@effect/vitest":"^0.27.0","@cloudflare/workers-types":"^4.20250117.0"},"_npmOperationalInternal":{"tmp":"tmp/effect-cloudflare_0.2.0_1771350587331_0.8025528624311635","host":"s3://npm-registry-packages-npm-production"}},"0.3.0":{"name":"@emergente-labs/effect-cloudflare","version":"0.3.0","description":"Effect-TS toolkit for Cloudflare Workers: fetch/queue handlers, config provider bridge, tiered caching, circuit breaker, and crypto utilities","type":"module","license":"MIT","author":{"name":"Emergente Labs"},"repository":{"type":"git","url":"git+https://github.com/emergente-labs/effect-cloudflare.git"},"bugs":{"url":"https://github.com/emergente-labs/effect-cloudflare/issues"},"homepage":"https://github.com/emergente-labs/effect-cloudflare#readme","keywords":["effect","effect-ts","cloudflare","workers","kv","r2","queue","circuit-breaker","cache","edge","serverless","tiered-cache","resilience","caching"],"engines":{"node":">=18.0.0"},"types":"./dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"./worker":{"types":"./dist/worker/layer.d.ts","default":"./dist/worker/layer.js"}},"scripts":{"prepare":"pnpm build","build":"tsup","test":"vitest run","test:watch":"vitest","typecheck":"tsc --noEmit","prepublishOnly":"pnpm run build && pnpm run typecheck"},"peerDependencies":{"@effect/platform":">=0.93.4","effect":">=3.19.8"},"devDependencies":{"@cloudflare/workers-types":"^4.20250117.0","@effect/platform":"^0.93.4","@effect/vitest":"^0.27.0","@types/node":"^24.0.0","effect":"^3.19.8","tsup":"^8.4.0","typescript":"^5.9.3","vitest":"^4.0.16"},"_id":"@emergente-labs/effect-cloudflare@0.3.0","gitHead":"47ee88de012773c696cf8999a959c055b724082e","_nodeVersion":"20.20.0","_npmVersion":"10.8.2","dist":{"integrity":"sha512-YQ1pcBT/Hbftx+mqQfNsc2wjmhf0jsP8LE1zEMZYBSSQ/O4TWkfoXbg+mb/Hc8ODxYdMkkQc5TTyshyDJFBnYQ==","shasum":"43e7f29819189d90a3cbc1d5afda6fdf8da35219","tarball":"https://registry.npmjs.org/@emergente-labs/effect-cloudflare/-/effect-cloudflare-0.3.0.tgz","fileCount":12,"unpackedSize":185548,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCID4qUK310LO9CqUN7rgLZvDxYlpPWV8mqAc/3wYM/aUMAiBZlZRSjb4BW6704H+Q20dw/IVCrhAXR0jVcG7PQ8HU8A=="}]},"_npmUser":{"name":"francisco-m001","email":"francisco@sellhub.cx"},"directories":{},"maintainers":[{"name":"francisco-m001","email":"francisco@sellhub.cx"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/effect-cloudflare_0.3.0_1771352919691_0.26743757025515813"},"_hasShrinkwrap":false}},"time":{"created":"2026-02-17T17:14:54.455Z","modified":"2026-02-17T18:28:39.972Z","0.1.1":"2026-02-17T17:14:54.685Z","0.2.0":"2026-02-17T17:49:47.473Z","0.3.0":"2026-02-17T18:28:39.845Z"},"bugs":{"url":"https://github.com/emergente-labs/effect-cloudflare/issues"},"author":{"name":"Emergente Labs"},"license":"MIT","homepage":"https://github.com/emergente-labs/effect-cloudflare#readme","keywords":["effect","effect-ts","cloudflare","workers","kv","r2","queue","circuit-breaker","cache","edge","serverless","tiered-cache","resilience","caching"],"repository":{"type":"git","url":"git+https://github.com/emergente-labs/effect-cloudflare.git"},"description":"Effect-TS toolkit for Cloudflare Workers: fetch/queue handlers, config provider bridge, tiered caching, circuit breaker, and crypto utilities","maintainers":[{"name":"francisco-m001","email":"francisco@sellhub.cx"}],"readme":"# @emergente-labs/effect-cloudflare\n\nEffect-TS toolkit for [Cloudflare Workers](https://developers.cloudflare.com/workers/). Composable building blocks for running Effect apps on the edge.\n\n## Installation\n\n```bash\nnpm install @emergente-labs/effect-cloudflare effect @effect/platform\n```\n\n## Features\n\n- **Worker handlers** -- `toFetchHandler` and `toQueueHandler` to wire up Effect services to Workers\n- **Config bridge** -- `cloudflareConfigProvider` maps Worker env bindings to `Effect.ConfigProvider`\n- **KV cache** -- Effect service with in-memory dedupe and operation timeouts\n- **R2 cache** -- TTL-based caching with metadata, tag invalidation, and stale-while-revalidate\n- **Tiered cache** -- Memory -> KV -> R2 with automatic fallback, promotion, and request coalescing\n- **Circuit breaker** -- KV-backed state that persists across isolate restarts\n- **Crypto utilities** -- AES-GCM encryption and SHA-256 checksums via Web Crypto API\n- **Content type inference** -- Built-in MIME type detection from file paths\n\n## Quick Start\n\n```ts\nimport * as HttpRouter from \"@effect/platform/HttpRouter\";\nimport { Effect, Layer } from \"effect\";\nimport {\n  workerContextFactory,\n  toFetchHandler,\n} from \"@emergente-labs/effect-cloudflare\";\n\ninterface Env {\n  MY_KV: KVNamespace;\n}\n\nconst { makeWorkerContext } = workerContextFactory<[Env, ExecutionContext]>();\n\nconst app = HttpRouter.empty;\nconst layer = Layer.empty;\n\nexport default {\n  fetch: toFetchHandler(app, { layer, makeWorkerContext }),\n};\n```\n\n## KV Cache\n\n```ts\nimport { KvCaching, CloudflareKvNamespace } from \"@emergente-labs/effect-cloudflare\";\nimport { Effect, Layer } from \"effect\";\n\n// Provide your KV namespace\nconst kvLayer = Layer.succeed(CloudflareKvNamespace, env.MY_KV);\nconst caching = KvCaching.Default.pipe(Layer.provide(kvLayer));\n\nconst program = Effect.gen(function* () {\n  const kv = yield* KvCaching;\n  const value = yield* kv.get(\"my-key\");\n  yield* kv.put([\"my-key\", \"my-value\", { expirationTtl: 3600 }]);\n});\n```\n\n## R2 Cache\n\n```ts\nimport { R2Caching, CloudflareR2Bucket } from \"@emergente-labs/effect-cloudflare\";\nimport { Effect, Layer } from \"effect\";\n\n// Provide your R2 bucket\nconst r2Layer = Layer.succeed(CloudflareR2Bucket, env.MY_R2_BUCKET);\nconst caching = R2Caching.Default.pipe(Layer.provide(r2Layer));\n\nconst program = Effect.gen(function* () {\n  const r2 = yield* R2Caching;\n\n  // Store with TTL and tags\n  yield* r2.put(\"assets/logo.png\", imageData, {\n    ttl: 86400,\n    contentType: \"image/png\",\n    tags: [\"assets\", \"images\"],\n  });\n\n  // Retrieve (returns Option)\n  const result = yield* r2.get(\"assets/logo.png\");\n\n  // Invalidate by tag\n  yield* r2.invalidateByTag(\"assets\");\n});\n```\n\n## Tiered Cache\n\nMemory -> KV -> R2 with automatic promotion and request coalescing:\n\n```ts\nimport {\n  TieredCache,\n  TieredCacheConfig,\n  CloudflareKvNamespace,\n  CloudflareR2Bucket,\n  RequestContext,\n  inferContentType,\n} from \"@emergente-labs/effect-cloudflare\";\nimport { Effect, Layer } from \"effect\";\n\n// Optional config (defaults to enabled)\nconst configLayer = Layer.succeed(TieredCacheConfig, {\n  enabled: true,\n  defaultKvTtl: 3600,\n  memoryCacheCapacity: 100,\n});\n\n// RequestContext wires background writes to Cloudflare's waitUntil\nconst requestLayer = Layer.succeed(RequestContext, {\n  scheduleBackgroundWork: (p) => executionCtx.waitUntil(p),\n});\n\nconst program = Effect.gen(function* () {\n  const cache = yield* TieredCache;\n\n  // String lookup across all tiers\n  const result = yield* cache.getString({ kvKey: \"kv:key\", r2Key: \"r2/key\" });\n  // result.value, result.tier, result.promoted\n\n  // Schema-validated JSON\n  const json = yield* cache.getJson(MySchema)({ kvKey: \"kv:data\", r2Key: \"r2/data\" });\n\n  // Binary assets with built-in content-type inference\n  const asset = yield* cache.getAsset(inferContentType)({\n    kvKey: \"kv:style.css\",\n    r2Key: \"themes/style.css\",\n  });\n  // asset.bytes, asset.contentType, asset.etag, asset.tier\n});\n```\n\n## Circuit Breaker\n\nKV-backed circuit breaker that persists across Worker isolate restarts:\n\n```ts\nimport { CircuitBreaker, CloudflareKvNamespace } from \"@emergente-labs/effect-cloudflare\";\nimport { Effect } from \"effect\";\n\nconst program = Effect.gen(function* () {\n  const cb = yield* CircuitBreaker;\n\n  // Wrap any effect with circuit breaker protection\n  const result = yield* cb.withCircuitBreaker(\"my-api\", fetchFromApi());\n\n  // With automatic retries and exponential backoff\n  const resilient = yield* cb.withCircuitBreakerAndRetry(\"my-api\", fetchFromApi());\n});\n```\n\n## Config Provider Bridge\n\nMap Worker env bindings to Effect's `ConfigProvider`:\n\n```ts\nimport { makeCloudflareConfigLayer } from \"@emergente-labs/effect-cloudflare\";\nimport { Config, Effect } from \"effect\";\n\n// In your Worker fetch handler\nconst configLayer = makeCloudflareConfigLayer(env);\n\nconst program = Effect.gen(function* () {\n  const dbUrl = yield* Config.string(\"DATABASE_URL\");\n}).pipe(Effect.provide(configLayer));\n```\n\n## Testing\n\nAll services are designed for testability. Context tags accept mock implementations:\n\n```ts\nimport { KvCaching, CloudflareKvNamespace, RequestContext } from \"@emergente-labs/effect-cloudflare\";\nimport { Effect, Layer } from \"effect\";\n\n// Mock KV for tests\nconst mockKvLayer = Layer.succeed(CloudflareKvNamespace, myMockKv);\n\n// Mock RequestContext (no-op background work)\nconst mockRequestCtx = Layer.succeed(RequestContext, {\n  scheduleBackgroundWork: () => {},\n});\n```\n\n## License\n\nMIT\n","readmeFilename":"README.md"}