@homeflare/alchemy 0.27.1 β†’ 0.27.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/README.md +3 -2
  2. package/dist/cloudflare/fake-r2-lock.d.ts +53 -0
  3. package/dist/cloudflare/fake-r2-lock.d.ts.map +1 -0
  4. package/dist/cloudflare/index.d.ts +4 -5
  5. package/dist/cloudflare/index.d.ts.map +1 -1
  6. package/dist/cloudflare/index.js +79 -103
  7. package/dist/cloudflare/index.js.map +6 -7
  8. package/dist/cloudflare/mesh-node-api.d.ts +3 -2
  9. package/dist/cloudflare/mesh-node-api.d.ts.map +1 -1
  10. package/dist/cloudflare/providers.d.ts +1 -1
  11. package/dist/cloudflare/providers.d.ts.map +1 -1
  12. package/dist/cloudflare/r2-bucket-lock.d.ts +28 -21
  13. package/dist/cloudflare/r2-bucket-lock.d.ts.map +1 -1
  14. package/dist/forgejo/branch-protection-form.d.ts +16 -5
  15. package/dist/forgejo/branch-protection-form.d.ts.map +1 -1
  16. package/dist/forgejo/branch-protection.d.ts +34 -3
  17. package/dist/forgejo/branch-protection.d.ts.map +1 -1
  18. package/dist/forgejo/fake-forgejo.d.ts +41 -0
  19. package/dist/forgejo/fake-forgejo.d.ts.map +1 -0
  20. package/dist/forgejo/index.js +220 -284
  21. package/dist/forgejo/index.js.map +14 -15
  22. package/dist/forgejo/org-actions-secrets.d.ts +42 -12
  23. package/dist/forgejo/org-actions-secrets.d.ts.map +1 -1
  24. package/dist/forgejo/org-label.d.ts +30 -3
  25. package/dist/forgejo/org-label.d.ts.map +1 -1
  26. package/dist/forgejo/org-team.d.ts +37 -4
  27. package/dist/forgejo/org-team.d.ts.map +1 -1
  28. package/dist/forgejo/repo-webhook-form.d.ts +9 -18
  29. package/dist/forgejo/repo-webhook-form.d.ts.map +1 -1
  30. package/dist/forgejo/repo-webhook.d.ts +35 -3
  31. package/dist/forgejo/repo-webhook.d.ts.map +1 -1
  32. package/dist/forgejo/repository.d.ts +47 -2
  33. package/dist/forgejo/repository.d.ts.map +1 -1
  34. package/dist/forgejo/resource.d.ts +37 -37
  35. package/dist/forgejo/resource.d.ts.map +1 -1
  36. package/dist/forgejo/team-member.d.ts +48 -4
  37. package/dist/forgejo/team-member.d.ts.map +1 -1
  38. package/dist/forgejo/values.d.ts +9 -5
  39. package/dist/forgejo/values.d.ts.map +1 -1
  40. package/docs/credentials.md +6 -0
  41. package/docs/distilled-interim.md +151 -0
  42. package/docs/peers.md +15 -6
  43. package/docs/upstream-conformance.md +46 -22
  44. package/package.json +2 -2
  45. package/src/cloudflare/fake-r2-lock.ts +120 -0
  46. package/src/cloudflare/index.ts +4 -5
  47. package/src/cloudflare/mesh-node-api.ts +3 -2
  48. package/src/cloudflare/providers.ts +13 -16
  49. package/src/cloudflare/r2-bucket-lock.test.ts +139 -0
  50. package/src/cloudflare/r2-bucket-lock.ts +59 -51
  51. package/src/forgejo/branch-protection-form.ts +12 -5
  52. package/src/forgejo/branch-protection.ts +58 -26
  53. package/src/forgejo/fake-forgejo.ts +54 -0
  54. package/src/forgejo/org-actions-secrets.ts +44 -36
  55. package/src/forgejo/org-label.test.ts +100 -0
  56. package/src/forgejo/org-label.ts +46 -36
  57. package/src/forgejo/org-team.ts +40 -27
  58. package/src/forgejo/repo-webhook-form.ts +10 -27
  59. package/src/forgejo/repo-webhook.ts +66 -35
  60. package/src/forgejo/repository.test.ts +125 -0
  61. package/src/forgejo/repository.ts +72 -52
  62. package/src/forgejo/resource.ts +80 -79
  63. package/src/forgejo/team-member.test.ts +97 -0
  64. package/src/forgejo/team-member.ts +56 -38
  65. package/src/forgejo/values.ts +9 -17
  66. package/dist/cloudflare/client.d.ts +0 -45
  67. package/dist/cloudflare/client.d.ts.map +0 -1
  68. package/dist/forgejo/client.d.ts +0 -37
  69. package/dist/forgejo/client.d.ts.map +0 -1
  70. package/src/cloudflare/client.ts +0 -76
  71. package/src/forgejo/client.ts +0 -136
@@ -1,19 +1,18 @@
1
1
  {
2
2
  "version": 3,
3
- "sources": ["../src/cloudflare/r2-bucket-lock.ts", "../src/cloudflare/client.ts", "../src/cloudflare/lock-rules.ts", "../src/cloudflare/mesh-node.ts", "../src/cloudflare/mesh-node-api.ts", "../src/cloudflare/mesh-node-form.ts", "../src/cloudflare/mesh-node-lifecycle.ts", "../src/cloudflare/mesh-node-token.ts", "../src/cloudflare/providers.ts", "../src/cloudflare/website.ts"],
3
+ "sources": ["../src/cloudflare/r2-bucket-lock.ts", "../src/cloudflare/lock-rules.ts", "../src/cloudflare/mesh-node.ts", "../src/cloudflare/mesh-node-api.ts", "../src/cloudflare/mesh-node-form.ts", "../src/cloudflare/mesh-node-lifecycle.ts", "../src/cloudflare/mesh-node-token.ts", "../src/cloudflare/providers.ts", "../src/cloudflare/website.ts"],
4
4
  "sourcesContent": [
5
- "import { Unowned } from 'alchemy/AdoptPolicy';\nimport * as Provider from 'alchemy/Provider';\nimport { Resource, type ResourceClass } from 'alchemy/Resource';\n/**\n * `Cloudflare.R2BucketLock` β€” the lock rule set on one R2 bucket, declared rather than applied by\n * hand. A bucket lock is a RETENTION FLOOR: while a rule covers an object, no API call, no\n * lifecycle rule and no credential can delete it.\n *\n * πŸ”΄ WHY THIS EXISTS AT ALL, AND WHAT IT REPLACES. a consuming stack shipped the retention floor as a\n * documented `wrangler r2 bucket lock add …` step in the runbook, on the measured ground that\n * `alchemy@2.0.0-beta.77` has no lock property anywhere in its R2 namespace. That measurement is\n * still true and it was the wrong conclusion: a step a human runs once is a step an Alchemy plan\n * can never show missing, so removing the floor by hand leaves that stack, its plan and\n * every gate completely green. The operator's rule (2026-09-15) is to cover such a gap with a\n * custom resource over the vendor's SDK, which is what this is.\n *\n * β›” THE SDK ALREADY HAD IT, AND THAT IS WORTH SAYING BECAUSE IT DECIDED THE SHAPE. Measured\n * 2026-09-15 in `cloudflare@4.5.0`, `resources/r2/buckets/locks.d.ts`:\n *\n * update(bucketName: string, params: LockUpdateParams, options?): APIPromise<…>\n * get(bucketName: string, params: LockGetParams, options?): APIPromise<LockGetResponse>\n *\n * over `PUT`/`GET /accounts/{account_id}/r2/buckets/{bucket_name}/lock` with the jurisdiction\n * carried as the `cf-r2-jurisdiction` header (`locks.mjs:17` and `:41`). So there is no raw\n * `client.put` fallback here and no path string this package invented β€” the fallback the rule\n * allows was not needed.\n *\n * ⚠️ THE RULE SET IS A REPLACE, NOT A MERGE. `PUT` takes the WHOLE list, exactly like R2's\n * lifecycle rules β€” which is the trap a consuming stack records for\n * `homeflare-git`. Declaring this resource on a bucket whose live rules nobody has read would\n * drop them, so `read` fetches them first and `reconcile` refuses to write when they already\n * match.\n */\nimport type Cloudflare from 'cloudflare';\nimport { NotFoundError } from 'cloudflare';\nimport * as Effect from 'effect/Effect';\nimport { CloudflareApi } from './client.ts';\nimport {\n type Jurisdiction,\n type R2LockRule,\n jurisdictionOf,\n rulesEqual,\n toBody,\n} from './lock-rules.ts';\nimport type { Providers } from './providers.ts';\n\nexport interface R2BucketLockProps {\n readonly accountId: string;\n readonly bucketName: string;\n /** ⚠️ Part of the bucket's IDENTITY, not a preference β€” a bucket in another jurisdiction is another bucket. */\n readonly jurisdiction?: Jurisdiction;\n /**\n * β›” REQUIRED, AND ITS EMPTY VALUE IS THE DANGEROUS ONE. `rules: []` means NO FLOOR: it is what\n * `delete` sends to take a lock off deliberately. An optional property would let a declaration\n * that simply forgot it send `[]` and silently unlock the repository, reporting success β€” the\n * same reasoning `LiteLLM.McpServer.allowedTools` is required for.\n */\n readonly rules: readonly R2LockRule[];\n}\n\nexport type R2BucketLockAttributes = {\n readonly bucketName: string;\n readonly jurisdiction: Jurisdiction;\n readonly rules: readonly R2LockRule[];\n};\n\nexport interface R2BucketLock extends Resource<\n 'Cloudflare.R2BucketLock',\n R2BucketLockProps,\n R2BucketLockAttributes,\n never,\n Providers\n> {}\n\n// ⚠️ EXPLICIT ANNOTATION, REQUIRED BY isolatedDeclarations. This file was internal to a\n// monorepo before it was published; a published package emits its .d.ts from a separate\n// tsc pass, so every exported symbol must state its type rather than infer it.\nexport const R2BucketLock: ResourceClass<R2BucketLock> = Resource<R2BucketLock>(\n 'Cloudflare.R2BucketLock',\n {\n /**\n * β›” RETAIN, AND ON THIS RESOURCE IT IS NOT THE ESTATE'S HABIT BUT THE WHOLE POINT. A lock is a\n * safety floor: removing the declaration must not remove the floor, because the state it\n * protects is the only physical backup of the estate's one Postgres. `delete` below is\n * implemented β€” a provider that cannot delete cannot be tested or corrected β€” and it is\n * reachable only by an operator running the CLI directly, past a deploy wrapper.\n */\n defaultRemovalPolicy: 'retain',\n },\n);\n\n/**\n * πŸ”΄ A 404 HERE IS \"NOTHING TO ADOPT\", AND THE FIRST PLAN PROVED IT IS THE ORDINARY CASE.\n * Measured 2026-09-15, an alchemy plan --detailed on a bucket that does not\n * exist yet: `404 {\"success\":false,\"errors\":[{\"code\":10006,\"message\":\"The specified bucket does\n * not exist.\"}]}` β€” raised from `read`, during PLANNING, before anything is created. Alchemy\n * calls `read` for a resource with no prior state, so on every first deploy the lock is asked\n * about a bucket the same plan is about to create. Letting that throw makes the resource\n * impossible to plan; swallowing it with `catchAll` would also hide a 403 from a mis-scoped\n * token, which is the failure this estate spends the most time on. So: 404 β†’ absent, everything\n * else propagates.\n *\n * β›” AN EMPTY LIVE RULE SET IS \"NO LOCK\", NOT \"A LOCK WITH NOTHING IN IT\", so it reads as absent\n * and the resource CREATES. The API answers `{}` for a bucket that has never been locked and\n * `{ rules: [] }` for one whose rules were removed; both mean the floor is gone.\n * ⚠️ `Unowned` UNTIL THIS STACK HAS WRITTEN IT. A lock someone applied by hand β€” which is exactly\n * what the retired runbook step did β€” must not be silently rewritten by a mistyped declaration;\n * the planner refuses until the resource is piped through `adopt(true)`, which is one line an\n * operator reads in the diff. An adopted rule set that already MATCHES then costs no request at\n * all, because `reconcile` below returns before writing.\n */\nexport const readLock = (\n client: Cloudflare,\n props: { accountId: string; bucketName: string; jurisdiction?: Jurisdiction },\n written: boolean,\n): Effect.Effect<R2BucketLockAttributes | undefined> =>\n Effect.gen(function* () {\n const jurisdiction = jurisdictionOf(props);\n const live = yield* Effect.tryPromise({\n try: () =>\n client.r2.buckets.locks.get(props.bucketName, {\n account_id: props.accountId,\n jurisdiction,\n }),\n catch: (cause) => cause,\n }).pipe(\n Effect.catchIf(\n (cause) => cause instanceof NotFoundError,\n () => Effect.succeed(undefined),\n ),\n Effect.orDie,\n );\n if (live === undefined) return undefined;\n const rules = (live.rules ?? []) as readonly R2LockRule[];\n if (rules.length === 0) return undefined;\n const attributes: R2BucketLockAttributes = {\n bucketName: props.bucketName,\n jurisdiction,\n rules,\n };\n return written ? attributes : Unowned(attributes);\n });\n\n/**\n * β›” A NO-OP MAKES NO REQUEST, AND ON A LOCK THAT MATTERS MORE THAN TIDINESS. `PUT` replaces the\n * rule set; re-sending an identical one on every deploy is a write against the only thing\n * standing between this repository and a delete, for no change. `output` is what the last deploy\n * persisted (or what `read` just adopted), so the comparison costs nothing either.\n */\nexport const reconcileLock = (\n client: Cloudflare,\n news: R2BucketLockProps,\n output: R2BucketLockAttributes | undefined,\n): Effect.Effect<R2BucketLockAttributes> =>\n Effect.gen(function* () {\n const jurisdiction = jurisdictionOf(news);\n if (output !== undefined && rulesEqual(output.rules, news.rules)) return output;\n yield* Effect.promise(() =>\n client.r2.buckets.locks.update(news.bucketName, {\n account_id: news.accountId,\n jurisdiction,\n ...toBody(news.rules),\n }),\n );\n return { bucketName: news.bucketName, jurisdiction, rules: news.rules };\n });\n\n/**\n * β›” DELETING THIS RESOURCE MEANS PUTTING AN EMPTY RULE SET, WHICH IS UNLOCKING THE BUCKET. It is\n * implemented because the retirement path is real β€” R2 refuses to empty a bucket while any lock\n * rule is configured, so retiring the repository has to start here β€” and it is behind\n * `defaultRemovalPolicy: 'retain'` and the estate CLI's refusal of `destroy` precisely because\n * no ordinary deploy should ever reach it.\n */\nexport const deleteLock = (\n client: Cloudflare,\n output: R2BucketLockAttributes,\n accountId: string,\n): Effect.Effect<void> =>\n Effect.gen(function* () {\n yield* Effect.promise(() =>\n client.r2.buckets.locks.update(output.bucketName, {\n account_id: accountId,\n jurisdiction: output.jurisdiction,\n rules: [],\n }),\n );\n });\n\nexport const R2BucketLockProvider = () =>\n Provider.effect(\n R2BucketLock,\n Effect.gen(function* () {\n const client = yield* CloudflareApi;\n return R2BucketLock.Provider.of({\n /**\n * β›” EMPTY, AND ALCHEMY'S OWN CONTRACT ASKS FOR EXACTLY THAT. `ProviderService.list`:\n * \"Resources with no native enumeration API (… sub-resources keyed entirely by a parent)\n * should return an empty array rather than throwing.\" There is no list-locks endpoint β€”\n * a lock is reachable only through the bucket that owns it β€” so enumerating would mean\n * listing every bucket in the account and GETting each one's lock.\n * β›”β›” AND `nuke.singleton` IS WHY THAT IS NOT MERELY UNAVAILABLE BUT UNWANTED. `list`\n * feeds `alchemy unsafe nuke`, which lists and then deletes; `delete` here is an UNLOCK.\n * A working enumeration would hand one command the ability to strip the retention floor\n * off every bucket in the account β€” the precise event a floor exists to make impossible.\n * `singleton` is the documented word for it: always-present configuration whose delete\n * resets rather than removes.\n */\n list: () => Effect.succeed([]),\n nuke: { singleton: true },\n read: Effect.fn(function* ({ olds, output }) {\n const props = olds ?? undefined;\n if (props === undefined) return undefined;\n return yield* readLock(client, props, output !== undefined);\n }),\n reconcile: Effect.fn(({ news, output }) => reconcileLock(client, news, output)),\n delete: Effect.fn(({ olds, output }) => deleteLock(client, output, olds.accountId)),\n });\n }),\n );\n",
6
- "/**\n * The Cloudflare REST API as an Effect service, over `cloudflare-typescript`. Codex standard 5:\n * the vendor's SDK, never a hand-rolled client β€” and never a hand-rolled `fetch` either.\n *\n * β›” THE SDK WAS ALREADY IN THE STORE AND DECLARED BY NOBODY. Measured 2026-09-15:\n * `node_modules/.pnpm/cloudflare@4.5.0` exists as a transitive dependency of `alchemy`, and\n * `pnpm ls cloudflare --depth 0` at the root and in every workspace package returns nothing.\n * `<estate>/local/scripts/vendor-sdk-ratchet.ts` row R2 says exactly that β€” \"not declared anywhere\n * in the workspace yet … the row adds the dependency once, then converts the TS callers\" β€” so\n * this package is that row's first half, and the twenty ratcheted offences keep their rows.\n *\n * β›” AND IT IS PINNED EXACTLY, NOT `catalog:`. There is no catalog entry for `cloudflare`, and the\n * version that resolves today is the one alchemy pulls in. Pinning `4.5.0` means a future\n * alchemy bump moves alchemy's copy and not this package's contract β€” the alternative is a\n * provider whose request shape changes because an unrelated dependency was upgraded.\n *\n * ⚠️ `fetch` IS A SEAM, NOT A CONVENIENCE β€” the same argument `packages/litellm-client` makes. The\n * SDK hands its custom fetch the URL and init it built, so a test double conforming to\n * `Core.Fetch` exercises the real path assembly, the real body serialisation and the real header\n * set. A test that stubbed `client.r2.buckets.locks.update` would prove only that a stub was\n * called, which is precisely the class of check this estate keeps finding green over nothing.\n */\nimport Cloudflare from 'cloudflare';\nimport * as Config from 'effect/Config';\nimport * as Context from 'effect/Context';\nimport * as Effect from 'effect/Effect';\nimport * as Layer from 'effect/Layer';\nimport * as Redacted from 'effect/Redacted';\n\n/** What the SDK accepts for `fetch`, restated so tests can name it without importing SDK internals. */\nexport type ClientFetch = (url: string | URL | Request, init?: RequestInit) => Promise<Response>;\n\nexport class CloudflareApi extends Context.Service<CloudflareApi, Cloudflare>()(\n 'homeflare/CloudflareApi',\n) {}\n\n/**\n * ⚠️ THE FAKE FETCH IS THE ONLY REASON THIS TAKES OPTIONS AT ALL. Everything else the SDK reads\n * from the environment the estate CLI already populates.\n */\nexport const makeClient = (options: { apiToken: string; fetch?: ClientFetch }): Cloudflare =>\n new Cloudflare({\n apiToken: options.apiToken,\n ...(options.fetch === undefined ? {} : { fetch: options.fetch }),\n });\n\n/**\n * β›” FAIL CLOSED ON THE VALUE, NEVER ON THE VARIABLE EXISTING. AGENTS.md \"the four that cost the\n * most\" Β§4: a denied OpenBao path renders the template EMPTY rather than erroring, and every\n * Cloudflare call then answers 403 exactly like a revoked token β€” which sends the operator to\n * Cloudflare to rotate a credential that was fine. Your deploy wrapper should write\n * CLOUDFLARE_API_TOKEN from the stack's declared role, short-lived and scoped.\n */\nconst MISSING_TOKEN =\n 'CLOUDFLARE_API_TOKEN is not set. Mint a short-lived, scoped token and export it before ' +\n 'role named in estate.stack.ts; running `alchemy` directly skips that and there is no default.';\n\nconst EMPTY_TOKEN =\n 'CLOUDFLARE_API_TOKEN is set but EMPTY. That is a denied or missing OpenBao grant on the ' +\n 'stack’s role, not a rotated token β€” re-mint it rather than rotating anything at Cloudflare.';\n\n/**\n * β›” A LAYER'S ERROR CHANNEL MUST BE `never`, so a ConfigError becomes a defect carrying a sentence\n * rather than a type error pushed up into `Stack`. Same decision, same words, as `LiteLLMLive`.\n */\nexport const CloudflareApiLive = Layer.effect(\n CloudflareApi,\n Effect.gen(function* () {\n const token = yield* Config.Redacted('CLOUDFLARE_API_TOKEN').pipe(\n Effect.catchTag('ConfigError', () => Effect.die(new Error(MISSING_TOKEN))),\n );\n const value = Redacted.value(token);\n if (value.length === 0) return yield* Effect.die(new Error(EMPTY_TOKEN));\n return makeClient({ apiToken: value });\n }),\n);\n",
5
+ "import { Unowned } from 'alchemy/AdoptPolicy';\nimport * as Provider from 'alchemy/Provider';\nimport { Resource, type ResourceClass } from 'alchemy/Resource';\n/**\n * `Cloudflare.R2BucketLock` β€” the lock rule set on one R2 bucket, declared rather than applied by\n * hand. A bucket lock is a RETENTION FLOOR: while a rule covers an object, no API call, no\n * lifecycle rule and no credential can delete it.\n *\n * πŸ”΄ WHY THIS EXISTS AT ALL, AND WHAT IT REPLACES. a consuming stack shipped the retention floor as a\n * documented `wrangler r2 bucket lock add …` step in the runbook, on the measured ground that\n * `alchemy@2.0.0-beta.77` has no lock property anywhere in its R2 namespace. That measurement is\n * still true and it was the wrong conclusion: a step a human runs once is a step an Alchemy plan\n * can never show missing, so removing the floor by hand leaves that stack, its plan and\n * every gate completely green. The operator's rule (2026-09-15) is to cover such a gap with a\n * custom resource over the vendor's SDK, which is what this is.\n *\n * β˜… OVER `@distilled.cloud/cloudflare/r2`, THE SAME SDK `Cloudflare.MeshNode` USES\n * (mesh-node-api.ts), NOT THE `cloudflare` NPM SDK THIS RESOURCE USED TO CALL. Measured\n * 2026-09-23 in distilled `1.0.0-rc.12` (the version alchemy@2.0.0-beta.79 pins),\n * `packages/cloudflare/src/services/r2.ts`: `getBucketLock` / `putBucketLock` cover the same\n * `GET`/`PUT /accounts/{account_id}/r2/buckets/{bucket_name}/lock` route the old SDK's\n * `r2.buckets.locks.get`/`.update` did β€” same `rules[]` body, the `cf-r2-jurisdiction` header\n * spelled as the `jurisdiction` field β€” but typed as `NoSuchBucket | InvalidRoute |\n * CloudflareRateLimited | CloudflareError` instead of an opaque HTTP status, so the \"bucket\n * does not exist yet\" case below is `catchTag('NoSuchBucket', …)`, never a status check. This\n * is also the swap that lets `client.ts` (the hand-rolled `cloudflare@4.5.0` wrapper) go away:\n * nothing else in the kit imported it.\n *\n * ⚠️ THE RULE SET IS A REPLACE, NOT A MERGE. `PUT` takes the WHOLE list, exactly like R2's\n * lifecycle rules β€” which is the trap a consuming stack records for\n * `homeflare-git`. Declaring this resource on a bucket whose live rules nobody has read would\n * drop them, so `read` fetches them first and `reconcile` refuses to write when they already\n * match.\n */\nimport type { CloudflareOpContext } from '@distilled.cloud/cloudflare/r2';\nimport { Credentials } from '@distilled.cloud/cloudflare/Credentials';\nimport * as r2 from '@distilled.cloud/cloudflare/r2';\nimport * as Context from 'effect/Context';\nimport * as Effect from 'effect/Effect';\nimport * as HttpClient from 'effect/unstable/http/HttpClient';\nimport {\n type Jurisdiction,\n type R2LockRule,\n jurisdictionOf,\n rulesEqual,\n toBody,\n} from './lock-rules.ts';\nimport type { Providers } from './providers.ts';\n\nexport interface R2BucketLockProps {\n readonly accountId: string;\n readonly bucketName: string;\n /** ⚠️ Part of the bucket's IDENTITY, not a preference β€” a bucket in another jurisdiction is another bucket. */\n readonly jurisdiction?: Jurisdiction;\n /**\n * β›” REQUIRED, AND ITS EMPTY VALUE IS THE DANGEROUS ONE. `rules: []` means NO FLOOR: it is what\n * `delete` sends to take a lock off deliberately. An optional property would let a declaration\n * that simply forgot it send `[]` and silently unlock the repository, reporting success β€” the\n * same reasoning `LiteLLM.McpServer.allowedTools` is required for.\n */\n readonly rules: readonly R2LockRule[];\n}\n\nexport type R2BucketLockAttributes = {\n readonly bucketName: string;\n readonly jurisdiction: Jurisdiction;\n readonly rules: readonly R2LockRule[];\n};\n\nexport interface R2BucketLock extends Resource<\n 'Cloudflare.R2BucketLock',\n R2BucketLockProps,\n R2BucketLockAttributes,\n never,\n Providers\n> {}\n\n// ⚠️ EXPLICIT ANNOTATION, REQUIRED BY isolatedDeclarations. This file was internal to a\n// monorepo before it was published; a published package emits its .d.ts from a separate\n// tsc pass, so every exported symbol must state its type rather than infer it.\nexport const R2BucketLock: ResourceClass<R2BucketLock> = Resource<R2BucketLock>(\n 'Cloudflare.R2BucketLock',\n {\n /**\n * β›” RETAIN, AND ON THIS RESOURCE IT IS NOT THE ESTATE'S HABIT BUT THE WHOLE POINT. A lock is a\n * safety floor: removing the declaration must not remove the floor, because the state it\n * protects is the only physical backup of the estate's one Postgres. `delete` below is\n * implemented β€” a provider that cannot delete cannot be tested or corrected β€” and it is\n * reachable only by an operator running the CLI directly, past a deploy wrapper.\n */\n defaultRemovalPolicy: 'retain',\n },\n);\n\n/**\n * πŸ”΄ A 404 HERE IS \"NOTHING TO ADOPT\", AND THE FIRST PLAN PROVED IT IS THE ORDINARY CASE.\n * Measured 2026-09-15, an alchemy plan --detailed on a bucket that does not\n * exist yet: `404 {\"success\":false,\"errors\":[{\"code\":10006,\"message\":\"The specified bucket does\n * not exist.\"}]}` β€” raised from `read`, during PLANNING, before anything is created. Alchemy\n * calls `read` for a resource with no prior state, so on every first deploy the lock is asked\n * about a bucket the same plan is about to create. Code 10006 is exactly distilled's\n * `NoSuchBucket` tag (r2.ts's error matcher, `[{ code: 10006 }]`), so `catchTag` on it is the\n * same case the status check used to cover β€” but by the vendor's own error identity, not by\n * reading a number off the response. Letting it propagate would make the resource impossible to\n * plan; swallowing every failure would also hide a 403 from a mis-scoped token, which is the\n * failure this estate spends the most time on. So: `NoSuchBucket` β†’ absent, everything else dies.\n *\n * β›” AN EMPTY LIVE RULE SET IS \"NO LOCK\", NOT \"A LOCK WITH NOTHING IN IT\", so it reads as absent\n * and the resource CREATES. The API answers `{}` for a bucket that has never been locked and\n * `{ rules: [] }` for one whose rules were removed; both mean the floor is gone.\n * ⚠️ `Unowned` UNTIL THIS STACK HAS WRITTEN IT. A lock someone applied by hand β€” which is exactly\n * what the retired runbook step did β€” must not be silently rewritten by a mistyped declaration;\n * the planner refuses until the resource is piped through `adopt(true)`, which is one line an\n * operator reads in the diff. An adopted rule set that already MATCHES then costs no request at\n * all, because `reconcile` below returns before writing.\n */\nexport const readLock = (\n props: { accountId: string; bucketName: string; jurisdiction?: Jurisdiction },\n written: boolean,\n): Effect.Effect<R2BucketLockAttributes | undefined, never, CloudflareOpContext> =>\n Effect.gen(function* () {\n const jurisdiction = jurisdictionOf(props);\n const live = yield* r2\n .getBucketLock({ accountId: props.accountId, bucketName: props.bucketName, jurisdiction })\n .pipe(\n Effect.catchTag('NoSuchBucket', () => Effect.succeed(undefined)),\n Effect.orDie,\n );\n if (live === undefined) return undefined;\n const rules = (live.rules ?? []) as readonly R2LockRule[];\n if (rules.length === 0) return undefined;\n const attributes: R2BucketLockAttributes = {\n bucketName: props.bucketName,\n jurisdiction,\n rules,\n };\n return written ? attributes : Unowned(attributes);\n });\n\n/**\n * β›” A NO-OP MAKES NO REQUEST, AND ON A LOCK THAT MATTERS MORE THAN TIDINESS. `PUT` replaces the\n * rule set; re-sending an identical one on every deploy is a write against the only thing\n * standing between this repository and a delete, for no change. `output` is what the last deploy\n * persisted (or what `read` just adopted), so the comparison costs nothing either.\n * β›” `NoSuchBucket` IS NOT CAUGHT HERE. Unlike `read`, a `reconcile` that hits it means the bucket\n * this declaration names is genuinely gone β€” that is a real failure to surface, not a case to\n * paper over, so it dies like every other tagged error `putBucketLock` can raise.\n */\nexport const reconcileLock = (\n news: R2BucketLockProps,\n output: R2BucketLockAttributes | undefined,\n): Effect.Effect<R2BucketLockAttributes, never, CloudflareOpContext> =>\n Effect.gen(function* () {\n const jurisdiction = jurisdictionOf(news);\n if (output !== undefined && rulesEqual(output.rules, news.rules)) return output;\n yield* r2\n .putBucketLock({\n accountId: news.accountId,\n bucketName: news.bucketName,\n jurisdiction,\n ...toBody(news.rules),\n })\n .pipe(Effect.orDie);\n return { bucketName: news.bucketName, jurisdiction, rules: news.rules };\n });\n\n/**\n * β›” DELETING THIS RESOURCE MEANS PUTTING AN EMPTY RULE SET, WHICH IS UNLOCKING THE BUCKET. It is\n * implemented because the retirement path is real β€” R2 refuses to empty a bucket while any lock\n * rule is configured, so retiring the repository has to start here β€” and it is behind\n * `defaultRemovalPolicy: 'retain'` and the estate CLI's refusal of `destroy` precisely because\n * no ordinary deploy should ever reach it.\n */\nexport const deleteLock = (\n output: R2BucketLockAttributes,\n accountId: string,\n): Effect.Effect<void, never, CloudflareOpContext> =>\n Effect.gen(function* () {\n yield* r2\n .putBucketLock({\n accountId,\n bucketName: output.bucketName,\n jurisdiction: output.jurisdiction,\n rules: [],\n })\n .pipe(Effect.orDie);\n });\n\nexport const R2BucketLockProvider = () =>\n Provider.effect(\n R2BucketLock,\n Effect.gen(function* () {\n // β˜… CAPTURED AT BUILD, LIKE `MeshNodeProvider`. `Credentials` and `HttpClient.HttpClient`\n // are the whole of distilled's `CloudflareOpContext` (r2.ts re-exports the type); pinning\n // them here rather than reading ambient context inside each handler keeps this provider\n // correct whether or not the stack also merges `Cloudflare.providers()`.\n const services = Context.make(Credentials, yield* Credentials).pipe(\n Context.add(HttpClient.HttpClient, yield* HttpClient.HttpClient),\n );\n const run = <A, E>(effect: Effect.Effect<A, E, CloudflareOpContext>) =>\n Effect.provideContext(effect, services);\n\n return R2BucketLock.Provider.of({\n /**\n * β›” EMPTY, AND ALCHEMY'S OWN CONTRACT ASKS FOR EXACTLY THAT. `ProviderService.list`:\n * \"Resources with no native enumeration API (… sub-resources keyed entirely by a parent)\n * should return an empty array rather than throwing.\" There is no list-locks endpoint β€”\n * a lock is reachable only through the bucket that owns it β€” so enumerating would mean\n * listing every bucket in the account and GETting each one's lock.\n * β›”β›” AND `nuke.singleton` IS WHY THAT IS NOT MERELY UNAVAILABLE BUT UNWANTED. `list`\n * feeds `alchemy unsafe nuke`, which lists and then deletes; `delete` here is an UNLOCK.\n * A working enumeration would hand one command the ability to strip the retention floor\n * off every bucket in the account β€” the precise event a floor exists to make impossible.\n * `singleton` is the documented word for it: always-present configuration whose delete\n * resets rather than removes.\n */\n list: () => Effect.succeed([]),\n nuke: { singleton: true },\n read: Effect.fn(function* ({ olds, output }) {\n const props = olds ?? undefined;\n if (props === undefined) return undefined;\n return yield* run(readLock(props, output !== undefined));\n }),\n reconcile: Effect.fn(({ news, output }) => run(reconcileLock(news, output))),\n delete: Effect.fn(({ olds, output }) => run(deleteLock(output, olds.accountId))),\n });\n }),\n );\n",
7
6
  "/**\n * The vocabulary of an R2 lock rule, and the comparison that decides whether to write one.\n *\n * β˜… ITS OWN MODULE FOR THE REASON `<estate>/litellm/src/equal.ts` IS ONE: the comparison is the\n * riskiest function in the provider and it deserves to be readable and testable without the\n * lifecycle around it. An equality that returned true for everything would make every reconcile\n * a no-op and the resource would silently never write anything β€” which looks exactly like a\n * converged stack.\n */\n/** Jurisdictions the API accepts, as the SDK declares them. */\nexport type Jurisdiction = 'default' | 'eu' | 'fedramp';\n\n/**\n * One lock rule. ⚠️ `prefix` OMITTED MEANS THE WHOLE BUCKET β€” the SDK documents an empty prefix as\n * the way to scope a rule to every object, and omitting the key is how that is spelled on the wire.\n */\nexport type R2LockRule = {\n readonly id: string;\n readonly enabled: boolean;\n readonly prefix?: string;\n readonly condition:\n | { readonly type: 'Age'; readonly maxAgeSeconds: number }\n | { readonly type: 'Date'; readonly date: string }\n | { readonly type: 'Indefinite' };\n};\n\n/** ⚠️ `undefined` and `'default'` are the same jurisdiction; normalise so a diff never sees both. */\nexport const jurisdictionOf = (props: { readonly jurisdiction?: Jurisdiction }): Jurisdiction =>\n props.jurisdiction ?? 'default';\n\n/**\n * β›” FIELD BY FIELD, IN ORDER, AND NOT `JSON.stringify` OF EITHER SIDE. The API returns rules in\n * its own key order and omits `prefix` where this repo may spell it `''`; a stringify compare\n * would report drift on every plan and PUT an identical body each deploy. Order across the list\n * IS compared, because the rule set is a replace and the list is what gets sent.\n */\nexport function rulesEqual(a: readonly R2LockRule[], b: readonly R2LockRule[]): boolean {\n if (a.length !== b.length) return false;\n return a.every((left, index) => {\n const right = b[index];\n if (right === undefined) return false;\n if (left.id !== right.id || left.enabled !== right.enabled) return false;\n if ((left.prefix ?? '') !== (right.prefix ?? '')) return false;\n const [lc, rc] = [left.condition, right.condition];\n if (lc.type !== rc.type) return false;\n if (lc.type === 'Age') return rc.type === 'Age' && lc.maxAgeSeconds === rc.maxAgeSeconds;\n if (lc.type === 'Date') return rc.type === 'Date' && lc.date === rc.date;\n return true;\n });\n}\n\n/**\n * The body the SDK sends. Split out so the test can assert the wire shape against one function.\n *\n * ⚠️ IT TAKES THE RULES, NOT THE PROPS, so this module names nothing from the resource β€” the\n * comparison and the vocabulary stay readable without the lifecycle around them.\n */\nexport const toBody = (rules: readonly R2LockRule[]): { rules: R2LockRule[] } => ({\n rules: rules.map((rule) => ({ ...rule })),\n});\n",
8
7
  "/**\n * `Cloudflare.MeshNode` β€” a Cloudflare Mesh node (a `warp_connector` object), declared without\n * its token ever touching Alchemy state. Usage and the enrolment step: docs/mesh-node.md.\n *\n * πŸ”΄ WHY THIS EXISTS ALONGSIDE `Cloudflare.Tunnel.WarpConnector`. A Mesh node that fronts a\n * private service needs a node record in the graph, so the Gateway rules and DNS records that\n * name it deploy together. Alchemy beta.79's WarpConnector would put the node token β€” the\n * credential that lets any Linux host join the account's Mesh as that node β€” into plaintext\n * state on every read, and it cannot create an HA node. See mesh-node-form.ts.\n *\n * β˜… REMOVAL POLICY: `retain` BY DEFAULT β€” see the β˜… on `MeshNode` below, and what it does to an\n * `ha` replace in mesh-node-lifecycle.ts.\n */\nimport { Credentials } from '@distilled.cloud/cloudflare/Credentials';\nimport * as Cloudflare from 'alchemy/Cloudflare';\nimport { isResolved } from 'alchemy/Diff';\nimport * as Provider from 'alchemy/Provider';\nimport { Resource, type ResourceClass } from 'alchemy/Resource';\nimport * as Context from 'effect/Context';\nimport * as Effect from 'effect/Effect';\nimport * as HttpClient from 'effect/unstable/http/HttpClient';\nimport {\n type MeshNodeAttributes,\n type MeshNodeProps,\n diffMeshNode,\n validateMeshNode,\n} from './mesh-node-form.ts';\nimport { deleteMeshNode, readMeshNode, reconcileMeshNode } from './mesh-node-lifecycle.ts';\nimport type { Providers } from './providers.ts';\n\nexport interface MeshNode extends Resource<\n 'Cloudflare.MeshNode',\n MeshNodeProps,\n MeshNodeAttributes,\n never,\n Providers\n> {}\n\n/**\n * β˜… `defaultRemovalPolicy: 'retain'`, LIKE EVERY KIT RESOURCE WHOSE DELETION BREAKS ITS CONSUMERS\n * (the `Bao.*` families, `R2BucketLock`, the Proxmox storage families; the reasoning is written\n * once in proxmox/resource.ts). Deleting a node cuts every enrolled replica off the Mesh, and\n * nothing brings it back: a new node has a new id, a new token and a new Mesh IP per replica, so\n * each host must be re-enrolled by hand and every Gateway rule or allow-list that pins the old\n * Mesh IP breaks. So dropping the declaration, or `alchemy destroy`, leaves the node live.\n * ⚠️ IT ALSO SKIPS THE OLD NODE'S DELETE IN A REPLACE (Apply.ts, `deleteOldGenerations` and the\n * GC's `retainOldGeneration`, beta.79). A same-name `ha` change therefore REFUSES, deleting\n * nothing, until that deploy opts in with `.pipe(RemovalPolicy.destroy())`; a create-first\n * replace leaves the old node live and unmanaged. Both measured in mesh-node-policy.test.ts.\n * β›” `delete` STAYS FULLY IMPLEMENTED. Retain is Terraform's `prevent_destroy`, not a stub: it runs\n * the moment a declaration opts in with `.pipe(RemovalPolicy.destroy())`.\n */\nexport const MeshNode: ResourceClass<MeshNode> = Resource<MeshNode>('Cloudflare.MeshNode', {\n defaultRemovalPolicy: 'retain',\n});\n\n/** What every call below needs: the account, credentials and HTTP β€” Alchemy's own trio. */\nexport type MeshNodeServices =\n | Cloudflare.CloudflareEnvironment\n | Credentials\n | HttpClient.HttpClient;\n\n/** The account id, resolved the way every `alchemy/Cloudflare` provider resolves it. */\nconst accountId = Effect.gen(function* () {\n const resolved = yield* yield* Cloudflare.CloudflareEnvironment;\n return resolved.accountId;\n});\n\n/**\n * β˜… `Provider.effect`, CAPTURING THE THREE SERVICES AT BUILD. `Provider.succeed` (what Alchemy's\n * own providers use) leaves each handler to find CloudflareEnvironment and Credentials in the\n * engine's context at call time β€” which works for `Cloudflare.providers()` only because it\n * `provideMerge`s them into the stack. Capturing them here keeps this provider correct whether\n * or not the stack also merges `Cloudflare.providers()`.\n */\nexport const MeshNodeProvider = () =>\n Provider.effect(\n MeshNode,\n Effect.gen(function* () {\n const services = Context.make(\n Cloudflare.CloudflareEnvironment,\n yield* Cloudflare.CloudflareEnvironment,\n ).pipe(\n Context.add(Credentials, yield* Credentials),\n Context.add(HttpClient.HttpClient, yield* HttpClient.HttpClient),\n );\n const run = <A, E>(effect: Effect.Effect<A, E, MeshNodeServices>) =>\n Effect.provideContext(effect, services);\n\n return MeshNode.Provider.of({\n stables: ['id', 'accountId', 'ha'],\n /**\n * β›” EMPTY, AND `nuke.skip`. Alchemy's own `Cloudflare.Tunnel.WarpConnector` already lists\n * every `warp_connector` in the account for `alchemy unsafe nuke`; a second listing here\n * would make nuke delete each node twice. `read` does not depend on this.\n */\n list: () => Effect.succeed([]),\n nuke: { skip: true },\n /**\n * β›” VALIDATE AT PLAN TIME, NOT ONLY IN `reconcile`. An `ha` replace is delete-first: the\n * engine deletes the old node BEFORE the new generation's reconcile runs, so a\n * declaration refused there would leave no node at all. launchd's job-preflight.ts\n * refuses at plan time for the same reason.\n */\n diff: ({ news, output }) =>\n run(\n Effect.gen(function* () {\n const invalid = isResolved(news) ? validateMeshNode(news) : undefined;\n if (invalid !== undefined) return yield* Effect.fail(invalid);\n return diffMeshNode(news, output, yield* accountId);\n }),\n ),\n read: ({ olds, output }) =>\n run(Effect.flatMap(accountId, (account) => readMeshNode(account, olds, output))),\n reconcile: ({ news, output }) =>\n run(Effect.flatMap(accountId, (account) => reconcileMeshNode(account, news, output))),\n delete: ({ output }) => run(deleteMeshNode(output)),\n });\n }),\n );\n",
9
- "/**\n * The `warp_connector` calls a Cloudflare Mesh node needs, and nothing else.\n *\n * β˜… A MESH NODE IS A `warp_connector` OBJECT. Cloudflare's Mesh docs create one with\n * `POST /accounts/{account_id}/warp_connector` and fetch its enrolment token from\n * `GET …/warp_connector/{id}/token` (developers.cloudflare.com/mesh/get-started, read\n * 2026-09-21). There is no separate \"mesh node\" endpoint.\n *\n * β˜… OVER `@distilled.cloud/cloudflare`, THE SDK ALCHEMY'S OWN CLOUDFLARE PROVIDERS USE, not the\n * `cloudflare` npm SDK the R2 lock uses. Measured 2026-09-21: `cloudflare@4.5.0`'s\n * `WARPConnectorCreateParams` has only `account_id` and `name`, so it cannot create an HA node.\n * distilled `1.0.0-rc.12` (the version alchemy@2.0.0-beta.79 pins) has `ha?: boolean` on\n * `CreateTunnelWarpConnectorRequest`, typed errors (`TunnelNotFound` code 1002 / 404,\n * `DuplicateTunnelName` code 1013), and resolves credentials and the account exactly the way\n * `Cloudflare.Tunnel.WarpConnector` and `Cloudflare.Gateway.Rule` do β€” so the node and the Gateway\n * rules that pin its Mesh IP always land in the same account.\n *\n * β›” NO FUNCTION IN THIS FILE READS THE NODE TOKEN, and `observe` copies named fields only. The\n * HA docs say the create response carries a `token` field; distilled's decoder copies only the\n * fields in its schema (protocol.ts, `getProps(outputAst)`), and `observe` below would drop it\n * again if it ever got through. The one token read lives in mesh-node-token.ts, on demand.\n */\nimport * as zeroTrust from '@distilled.cloud/cloudflare/zero-trust';\nimport * as Data from 'effect/Data';\nimport * as Effect from 'effect/Effect';\nimport * as Stream from 'effect/Stream';\n\n/** `inactive` (never run), `degraded`, `healthy` or `down` β€” Cloudflare's own four. */\nexport type MeshNodeStatus = 'inactive' | 'degraded' | 'healthy' | 'down';\n\n/** A live node as this package sees it. β›” There is no token field here, on purpose. */\nexport interface ObservedNode {\n readonly id: string;\n readonly name: string;\n readonly status: MeshNodeStatus | undefined;\n}\n\n/** A refusal or failure, carrying the sentence an operator needs. Never a token. */\nexport class MeshNodeError extends Data.TaggedError('MeshNodeError')<{\n readonly message: string;\n}> {}\n\ntype Raw = {\n readonly id?: string | null;\n readonly name?: string | null;\n readonly status?: string | null;\n readonly deletedAt?: string | null;\n};\n\nconst STATUSES: readonly string[] = ['inactive', 'degraded', 'healthy', 'down'];\n\n/**\n * ⚠️ A DELETED NODE IS NOT A NODE. Cloudflare keeps deleted tunnels (every response carries\n * `deleted_at`, and the list takes `is_deleted`), so a `GET` by id can answer for one. Treating\n * that as live would \"adopt\" a tombstone and PATCH it.\n */\nexport const observe = (raw: Raw): ObservedNode | undefined => {\n if (typeof raw.id !== 'string' || raw.id.length === 0) return undefined;\n if (raw.deletedAt !== undefined && raw.deletedAt !== null) return undefined;\n const status =\n typeof raw.status === 'string' && STATUSES.includes(raw.status)\n ? (raw.status as MeshNodeStatus)\n : undefined;\n return { id: raw.id, name: raw.name ?? '', status };\n};\n\n/** The node with this id, or `undefined` when it is gone (404 / code 1002, or soft-deleted). */\nexport const getNode = (accountId: string, nodeId: string) =>\n zeroTrust.getTunnelWarpConnector({ accountId, tunnelId: nodeId }).pipe(\n Effect.map(observe),\n Effect.catchTag('TunnelNotFound', () => Effect.succeed(undefined)),\n );\n\n/**\n * The live node with exactly this name, or `undefined`.\n *\n * β˜… `name` is also sent as the list filter, but the match is re-checked here: the filter's\n * exactness is not documented, and a prefix match would adopt the wrong node.\n * β›” Two live matches is refused rather than resolved. Names are unique per account (the create\n * answers code 1013 otherwise), so two means something this package does not understand.\n */\nexport const findNodeByName = (accountId: string, name: string) =>\n zeroTrust.listTunnelWarpConnectors.items({ accountId, name, isDeleted: false }).pipe(\n Stream.map(observe),\n Stream.filter((node): node is ObservedNode => node !== undefined && node.name === name),\n Stream.runCollect,\n Effect.flatMap((chunk) => {\n const matches = Array.from(chunk);\n if (matches.length <= 1) return Effect.succeed(matches[0]);\n const ids = matches.map((node) => node.id).join(', ');\n return Effect.fail(\n new MeshNodeError({\n message: `Mesh node name \"${name}\" matches ${matches.length} live nodes (${ids}). Names are unique per account, so refusing to guess which one is meant.`,\n }),\n );\n }),\n );\n\n/**\n * β›” `ha` IS SENT ON CREATE AND NOWHERE ELSE. Cloudflare's HA page: \"High availability is set at\n * node creation time and cannot be changed afterward.\" The PATCH body (`PatchTunnelWarpConnector\n * Request`) has only `name` and `tunnelSecret`.\n */\nexport const createNode = (accountId: string, name: string, ha: boolean) =>\n zeroTrust.createTunnelWarpConnector({ accountId, name, ha }).pipe(\n Effect.flatMap((raw) => {\n const node = observe(raw);\n return node === undefined\n ? Effect.fail(\n new MeshNodeError({ message: `Creating Mesh node \"${name}\" returned no id.` }),\n )\n : Effect.succeed(node);\n }),\n );\n\n/** β˜… A rename is a PATCH: same id, same token, same enrolled replicas and Mesh IP. */\nexport const renameNode = (accountId: string, nodeId: string, name: string) =>\n zeroTrust\n .patchTunnelWarpConnector({ accountId, tunnelId: nodeId, name })\n .pipe(Effect.map((raw) => observe(raw) ?? { id: nodeId, name, status: undefined }));\n\n/** Idempotent: a node already gone (404 / code 1002) is a successful delete. */\nexport const deleteNode = (accountId: string, nodeId: string) =>\n zeroTrust.deleteTunnelWarpConnector({ accountId, tunnelId: nodeId }).pipe(\n Effect.asVoid,\n Effect.catchTag('TunnelNotFound', () => Effect.void),\n );\n",
8
+ "/**\n * The `warp_connector` calls a Cloudflare Mesh node needs, and nothing else.\n *\n * β˜… A MESH NODE IS A `warp_connector` OBJECT. Cloudflare's Mesh docs create one with\n * `POST /accounts/{account_id}/warp_connector` and fetch its enrolment token from\n * `GET …/warp_connector/{id}/token` (developers.cloudflare.com/mesh/get-started, read\n * 2026-09-21). There is no separate \"mesh node\" endpoint.\n *\n * β˜… OVER `@distilled.cloud/cloudflare`, THE SDK ALCHEMY'S OWN CLOUDFLARE PROVIDERS USE β€” and, as\n * of 2026-09-23, the same one `R2BucketLock` calls too (r2-bucket-lock.ts), not the `cloudflare`\n * npm SDK either used before that. Measured 2026-09-21: `cloudflare@4.5.0`'s\n * `WARPConnectorCreateParams` has only `account_id` and `name`, so it cannot create an HA node.\n * distilled `1.0.0-rc.12` (the version alchemy@2.0.0-beta.79 pins) has `ha?: boolean` on\n * `CreateTunnelWarpConnectorRequest`, typed errors (`TunnelNotFound` code 1002 / 404,\n * `DuplicateTunnelName` code 1013), and resolves credentials and the account exactly the way\n * `Cloudflare.Tunnel.WarpConnector` and `Cloudflare.Gateway.Rule` do β€” so the node and the Gateway\n * rules that pin its Mesh IP always land in the same account.\n *\n * β›” NO FUNCTION IN THIS FILE READS THE NODE TOKEN, and `observe` copies named fields only. The\n * HA docs say the create response carries a `token` field; distilled's decoder copies only the\n * fields in its schema (protocol.ts, `getProps(outputAst)`), and `observe` below would drop it\n * again if it ever got through. The one token read lives in mesh-node-token.ts, on demand.\n */\nimport * as zeroTrust from '@distilled.cloud/cloudflare/zero-trust';\nimport * as Data from 'effect/Data';\nimport * as Effect from 'effect/Effect';\nimport * as Stream from 'effect/Stream';\n\n/** `inactive` (never run), `degraded`, `healthy` or `down` β€” Cloudflare's own four. */\nexport type MeshNodeStatus = 'inactive' | 'degraded' | 'healthy' | 'down';\n\n/** A live node as this package sees it. β›” There is no token field here, on purpose. */\nexport interface ObservedNode {\n readonly id: string;\n readonly name: string;\n readonly status: MeshNodeStatus | undefined;\n}\n\n/** A refusal or failure, carrying the sentence an operator needs. Never a token. */\nexport class MeshNodeError extends Data.TaggedError('MeshNodeError')<{\n readonly message: string;\n}> {}\n\ntype Raw = {\n readonly id?: string | null;\n readonly name?: string | null;\n readonly status?: string | null;\n readonly deletedAt?: string | null;\n};\n\nconst STATUSES: readonly string[] = ['inactive', 'degraded', 'healthy', 'down'];\n\n/**\n * ⚠️ A DELETED NODE IS NOT A NODE. Cloudflare keeps deleted tunnels (every response carries\n * `deleted_at`, and the list takes `is_deleted`), so a `GET` by id can answer for one. Treating\n * that as live would \"adopt\" a tombstone and PATCH it.\n */\nexport const observe = (raw: Raw): ObservedNode | undefined => {\n if (typeof raw.id !== 'string' || raw.id.length === 0) return undefined;\n if (raw.deletedAt !== undefined && raw.deletedAt !== null) return undefined;\n const status =\n typeof raw.status === 'string' && STATUSES.includes(raw.status)\n ? (raw.status as MeshNodeStatus)\n : undefined;\n return { id: raw.id, name: raw.name ?? '', status };\n};\n\n/** The node with this id, or `undefined` when it is gone (404 / code 1002, or soft-deleted). */\nexport const getNode = (accountId: string, nodeId: string) =>\n zeroTrust.getTunnelWarpConnector({ accountId, tunnelId: nodeId }).pipe(\n Effect.map(observe),\n Effect.catchTag('TunnelNotFound', () => Effect.succeed(undefined)),\n );\n\n/**\n * The live node with exactly this name, or `undefined`.\n *\n * β˜… `name` is also sent as the list filter, but the match is re-checked here: the filter's\n * exactness is not documented, and a prefix match would adopt the wrong node.\n * β›” Two live matches is refused rather than resolved. Names are unique per account (the create\n * answers code 1013 otherwise), so two means something this package does not understand.\n */\nexport const findNodeByName = (accountId: string, name: string) =>\n zeroTrust.listTunnelWarpConnectors.items({ accountId, name, isDeleted: false }).pipe(\n Stream.map(observe),\n Stream.filter((node): node is ObservedNode => node !== undefined && node.name === name),\n Stream.runCollect,\n Effect.flatMap((chunk) => {\n const matches = Array.from(chunk);\n if (matches.length <= 1) return Effect.succeed(matches[0]);\n const ids = matches.map((node) => node.id).join(', ');\n return Effect.fail(\n new MeshNodeError({\n message: `Mesh node name \"${name}\" matches ${matches.length} live nodes (${ids}). Names are unique per account, so refusing to guess which one is meant.`,\n }),\n );\n }),\n );\n\n/**\n * β›” `ha` IS SENT ON CREATE AND NOWHERE ELSE. Cloudflare's HA page: \"High availability is set at\n * node creation time and cannot be changed afterward.\" The PATCH body (`PatchTunnelWarpConnector\n * Request`) has only `name` and `tunnelSecret`.\n */\nexport const createNode = (accountId: string, name: string, ha: boolean) =>\n zeroTrust.createTunnelWarpConnector({ accountId, name, ha }).pipe(\n Effect.flatMap((raw) => {\n const node = observe(raw);\n return node === undefined\n ? Effect.fail(\n new MeshNodeError({ message: `Creating Mesh node \"${name}\" returned no id.` }),\n )\n : Effect.succeed(node);\n }),\n );\n\n/** β˜… A rename is a PATCH: same id, same token, same enrolled replicas and Mesh IP. */\nexport const renameNode = (accountId: string, nodeId: string, name: string) =>\n zeroTrust\n .patchTunnelWarpConnector({ accountId, tunnelId: nodeId, name })\n .pipe(Effect.map((raw) => observe(raw) ?? { id: nodeId, name, status: undefined }));\n\n/** Idempotent: a node already gone (404 / code 1002) is a successful delete. */\nexport const deleteNode = (accountId: string, nodeId: string) =>\n zeroTrust.deleteTunnelWarpConnector({ accountId, tunnelId: nodeId }).pipe(\n Effect.asVoid,\n Effect.catchTag('TunnelNotFound', () => Effect.void),\n );\n",
10
9
  "/**\n * `Cloudflare.MeshNode` β€” its props, its attributes, and the pure `diff` that decides between\n * update and replace.\n *\n * β›” THE ATTRIBUTES NEVER HOLD THE NODE TOKEN. That is the whole reason this resource exists:\n * Alchemy beta.79's `Cloudflare.Tunnel.WarpConnector` calls `getTunnelWarpConnectorToken` in\n * `toAttributes`, so EVERY read, reconcile and list fetches the token, and Alchemy state (which\n * is not encrypted) stores it β€” `Redacted` hides it from logs, not from the state store. It also\n * has no `ha` prop. The token is a runtime credential: fetch it on demand with\n * `fetchMeshNodeToken` (mesh-node-token.ts) and write it straight to the host.\n * β˜… A BONUS OF NOT READING IT: the token endpoint needs a Write permission (\"Cloudflare One\n * Connectors Write\", per its API reference, read 2026-09-21), so a plan over this resource can\n * run on a read-only token. WarpConnector's cannot.\n */\nimport type { Diff } from 'alchemy/Diff';\nimport type { Input } from 'alchemy/Input';\nimport { MeshNodeError, type MeshNodeStatus } from './mesh-node-api.ts';\n\nexport interface MeshNodeProps {\n /**\n * The node's name in the account β€” and its identity when adopting (names are unique per\n * account). β˜… Changing it RENAMES the node in place (`PATCH`): the id, the token, the\n * enrolled replicas and their Mesh IPs all survive, so a rename never re-enrols a host.\n */\n readonly name: string;\n /**\n * High availability (active-passive replicas sharing one node token).\n *\n * β›” CREATE-ONLY, AND REQUIRED FOR THAT REASON. Cloudflare's Mesh HA page: \"High availability\n * is set at node creation time and cannot be changed afterward.\" The PATCH body carries no\n * `ha`. So a change REPLACES the node β€” a new id, a new token, a new Mesh IP per replica β€”\n * and a default would silently fix an irreversible choice. The API's own default is `false`;\n * the dashboard's is `true`. Say which one you mean.\n * β˜… A NODE WITH NO ROUTES β€” A DOOR β€” IS `ha: false`, AND A SECOND DOOR IS A SECOND NODE. HA\n * fails over the CIDR routes a node advertises; \"Nodes without routes do not benefit from HA\n * failover\", because the node's Mesh IP is tied to the individual replica (same page, \"When to\n * use\" and \"Considerations\", read 2026-09-21). After a failover the promoted replica answers\n * on its own Mesh IP, so nothing that dials a door's first replica follows it. Declare the\n * second door as its own `MeshNode` (own name, token, Mesh IP) and have callers list both.\n * ⚠️ `ha` NEEDS MASQUE on the node's device profile (same page).\n */\n readonly ha: boolean;\n}\n\nexport interface MeshNodeAttributes {\n /** The node's UUID (the `warp_connector` tunnel id). Pass it to `fetchMeshNodeToken`. */\n readonly id: string;\n /** The account the provider environment resolved when the node was written. */\n readonly accountId: string;\n readonly name: string;\n /** As of the last read or write; it moves on its own as replicas connect and drop. */\n readonly status: MeshNodeStatus | undefined;\n /**\n * The value the node was CREATED with (or declared with when adopted β€” see mesh-node.md).\n * ⚠️ No documented read returns it: `GET /warp_connector/{id}` has no `ha` field (API\n * reference, read 2026-09-21), so drift here cannot be observed, only prevented.\n */\n readonly ha: boolean;\n}\n\n/** β›” Fail closed on a declaration the API would accept but nobody meant. */\nexport const validateMeshNode = (props: MeshNodeProps): MeshNodeError | undefined => {\n if (typeof props.name !== 'string' || props.name.trim().length === 0) {\n return new MeshNodeError({ message: 'MeshNode `name` must be a non-empty string.' });\n }\n if (props.name !== props.name.trim()) {\n return new MeshNodeError({\n message: `MeshNode name \"${props.name}\" has leading or trailing whitespace; adoption matches names exactly.`,\n });\n }\n if (typeof props.ha !== 'boolean') {\n return new MeshNodeError({\n message: 'MeshNode `ha` must be true or false. It is create-only, so it has no default.',\n });\n }\n return undefined;\n};\n\n/**\n * update or replace, from the declaration and what was last written.\n *\n * - Account changed β†’ `replace`, create-first: names are unique per ACCOUNT, so the two\n * generations cannot collide.\n * - `ha` changed β†’ `replace`. β›” `deleteFirst` when the name stays the same: the new node needs\n * the name the old one holds, so create-first would fail with code 1013 (or, worse, a\n * create path that \"converged\" on the old node would record an `ha` it does not have).\n * Create-first when the name changes in the same edit, because then nothing collides.\n * ⚠️ UNDER THE DEFAULT `retain` (mesh-node.ts) the engine skips the old node's delete in both\n * orders: delete-first then REFUSES at the create (mesh-node-lifecycle.ts) until the deploy\n * opts in with `RemovalPolicy.destroy()`, and create-first leaves the old node live, unmanaged.\n * The answer here is the same under both policies: `diff` is not handed the policy (Provider.ts,\n * its input), and delete-first is exactly what lets the opted-in deploy reuse the name.\n * - Name changed alone β†’ `update` (a PATCH rename).\n * - Otherwise β†’ no opinion; the engine compares props.\n *\n * β˜… An unresolved `ha` gives no answer here; `reconcile` re-checks it once resolved and refuses\n * rather than updating across a create-only change. An unresolved `name` next to a changed `ha`\n * counts as \"unchanged\", which picks the delete-first order that cannot collide.\n */\nexport const diffMeshNode = (\n news: Input<MeshNodeProps>,\n output: MeshNodeAttributes | undefined,\n accountId: string,\n): Diff | undefined => {\n if (output === undefined) return undefined;\n if (output.accountId !== accountId) return { action: 'replace' };\n const fields = news as { readonly name?: unknown; readonly ha?: unknown };\n const name = typeof fields.name === 'string' ? fields.name : undefined;\n if (typeof fields.ha === 'boolean' && fields.ha !== output.ha) {\n const sameName = name === undefined || name === output.name;\n return sameName ? { action: 'replace', deleteFirst: true } : { action: 'replace' };\n }\n if (name !== undefined && name !== output.name) return { action: 'update' };\n return undefined;\n};\n",
11
10
  "/**\n * read / reconcile / delete for `Cloudflare.MeshNode`, as plain Effects over the SDK calls in\n * mesh-node-api.ts. mesh-node.ts only wires them into Alchemy.\n */\nimport { Unowned } from 'alchemy/AdoptPolicy';\nimport * as Effect from 'effect/Effect';\nimport {\n MeshNodeError,\n type ObservedNode,\n createNode,\n deleteNode,\n findNodeByName,\n getNode,\n renameNode,\n} from './mesh-node-api.ts';\nimport { type MeshNodeAttributes, type MeshNodeProps, validateMeshNode } from './mesh-node-form.ts';\n\nconst attributesOf = (node: ObservedNode, accountId: string, ha: boolean): MeshNodeAttributes => ({\n id: node.id,\n accountId,\n name: node.name,\n status: node.status,\n ha,\n});\n\n/**\n * Owned: refresh by the stored id. Cold: find the live node by exact name and hand it back\n * `Unowned`, so Alchemy refuses to take it over until the stack says `adopt(true)` β€” the same\n * gate `Cloudflare.Tunnel.WarpConnector` uses, because a `warp_connector` carries no ownership\n * marker to prove it was ours.\n *\n * ⚠️ ADOPTION RECORDS THE DECLARED `ha`, BECAUSE NOTHING ELSE CAN. No documented read returns a\n * node's HA flag. Adopting an existing node with the wrong `ha` records the wrong value, and no\n * later plan can notice. Check the node's HA badge in the dashboard before adopting.\n */\nexport const readMeshNode = (\n accountId: string,\n olds: MeshNodeProps | undefined,\n output: MeshNodeAttributes | undefined,\n) =>\n Effect.gen(function* () {\n const account = output?.accountId ?? accountId;\n if (output !== undefined) {\n const node = yield* getNode(account, output.id);\n if (node !== undefined) return attributesOf(node, account, output.ha);\n }\n const name = olds?.name ?? output?.name;\n const ha = olds?.ha ?? output?.ha;\n if (name === undefined || ha === undefined) return undefined;\n const match = yield* findNodeByName(account, name);\n return match === undefined ? undefined : Unowned(attributesOf(match, account, ha));\n });\n\n/**\n * β›” NEVER CONVERGE ON A NODE THIS RESOURCE DID NOT FIND BY ITS OWN ID. Alchemy's WarpConnector\n * answers `DuplicateTunnelName` by re-reading the node that holds the name and returning it.\n * Here that would be wrong in exactly the case that matters: an `ha` replace whose old node is\n * still alive (the default `retain` skips the delete-first teardown, mesh-node.ts) would\n * \"create\" by silently re-using the old node and record the new `ha` on it. Adoption belongs to\n * `read` and `adopt(true)`. An interrupted create is recovered through `read` too, which returns\n * the node `Unowned`, so the engine asks for `adopt(true)` there as well (as for WarpConnector).\n */\nconst nameTaken = (name: string, holder: string) =>\n new MeshNodeError({\n message:\n `A tunnel named \"${name}\" already exists (${holder}). Names are unique per account, and ` +\n 'this resource never takes over a node it did not create by its own id.',\n });\n\n/**\n * β˜… HOW TO TAKE A NODE OVER WHILE THIS RESOURCE HAS A STATE ROW, WRITTEN ONCE. The plan asks\n * `read` to adopt only for a resource with NO row (Plan.ts, `oldState === undefined`) or for an\n * interrupted create's `creating` row. A replace's `replacing` row gets neither, so `adopt(true)`\n * alone changes nothing there and the create refuses again (measured in mesh-node-policy.test.ts).\n * `alchemy state rm` is Alchemy's \"Delete state records without deleting cloud resources\".\n */\nconst REOWN =\n \"drop this resource's state row (alchemy state rm, which leaves the node alone), then deploy \" +\n 'under adopt(true)';\n\n/**\n * πŸ”΄ THE REFUSAL AN `ha` CHANGE MEETS UNDER THE DEFAULT, SO IT NAMES EVERY WAY ON. The plan says\n * `replace` (delete-first), the engine skips that delete because of `retain`, and the old node\n * still holds the name here. A node made outside the stack is caught earlier, by the plan's\n * adoption probe in `read`.\n * β›” BUT THE HOLDER IS NOT ALWAYS THE OLD NODE, AND `create` CANNOT TELL: the engine passes it no\n * `olds` (Apply.ts, the replace branch). An `ha` + `name` change is a create-first replace, and\n * there the holder of the NEW name is some other node, which `RemovalPolicy.destroy()` never\n * touches. So the old-node advice is conditional, and the other case says not to delete it: a\n * sentence that called any holder \"the old node\" would send an operator to delete a live node\n * and cut its replicas off the Mesh (measured 2026-09-21 in mesh-node-policy.test.ts).\n * ⚠️ REVERTING `ha` DOES NOT UNDO IT. The failed deploy leaves a `replacing` row, and the next\n * plan resumes that replace (Plan.ts, the `replacing` branch), so it refuses again. Dropping the\n * row and adopting is the way back. All measured in mesh-node-policy.test.ts.\n */\nconst oldNodeHoldsName = (name: string, holder: string) =>\n new MeshNodeError({\n message:\n `Mesh node \"${name}\" already exists (${holder}) and is not the node this resource tracks, ` +\n 'so it is left alone: names are unique per account. If this deploy changed `ha` and kept ' +\n \"the name, it is this resource's old node, kept by MeshNode's default \" +\n 'RemovalPolicy.retain because HA cannot change in place. To replace it (a new id, token and ' +\n 'Mesh IP per replica), deploy once with .pipe(RemovalPolicy.destroy()), which deletes it ' +\n `first, or delete it by hand. To keep it, reverting \\`ha\\` is not enough: ${REOWN}, with ` +\n 'the original `ha`. Otherwise it is another node: do not delete it. Choose another name, ' +\n `or, if it is meant to be this one, check its HA badge, then ${REOWN}.`,\n });\n\n/**\n * πŸ”΄ A 1013 AFTER A CLEAN LOOKUP IS USUALLY THIS DEPLOY'S OWN NODE. distilled retries a POST on\n * a transport error or a 5xx (core `isTransientError`, measured in its source 2026-09-21), so a\n * create that succeeded but lost its response is sent again and answered 1013 by the node the\n * first attempt made. The lookup is repeated so the sentence names that node instead of blaming\n * another tunnel type; it is still not adopted here (see above), because a concurrent creator\n * looks the same and its `ha` is unknown.\n * ⚠️ `adopt(true)` ALONE ONLY RECOVERS A FIRST CREATE. Inside a replace (any `ha` change, either\n * order) the row is `replacing`, which the plan never probes, so the sentence names `REOWN` for\n * that case (measured in mesh-node-policy.test.ts).\n */\nconst raced = (accountId: string, name: string) =>\n findNodeByName(accountId, name).pipe(\n Effect.flatMap((holder) =>\n Effect.fail(\n holder === undefined\n ? // ⚠️ No live warp_connector of that name: another tunnel TYPE holds it (a cloudflared\n // tunnel), or β€” UNMEASURED β€” a node deleted moments ago (a delete-first `ha`\n // replace) still reserves it.\n nameTaken(name, 'not a live Mesh node: another tunnel type, or a just-deleted node')\n : new MeshNodeError({\n message:\n `Mesh node \"${name}\" (${holder.id}) appeared between this deploy's lookup and its ` +\n 'create. The likeliest cause is this deploy itself: the SDK retries a create whose ' +\n 'response was lost. If so, check its HA badge matches the declared `ha`, then ' +\n 'adopt it: adopt(true) after a first create, but after a replace ' +\n `${REOWN}, because adopt(true) alone does not act on a replace's row. ` +\n 'Otherwise another creator raced this one.',\n }),\n ),\n ),\n );\n\nconst createFresh = (accountId: string, news: MeshNodeProps) =>\n Effect.gen(function* () {\n const existing = yield* findNodeByName(accountId, news.name);\n if (existing !== undefined) return yield* Effect.fail(oldNodeHoldsName(news.name, existing.id));\n const node = yield* createNode(accountId, news.name, news.ha).pipe(\n Effect.catchTag('DuplicateTunnelName', () => raced(accountId, news.name)),\n );\n return attributesOf(node, accountId, news.ha);\n });\n\nexport const reconcileMeshNode = (\n accountId: string,\n news: MeshNodeProps,\n output: MeshNodeAttributes | undefined,\n) =>\n Effect.gen(function* () {\n const invalid = validateMeshNode(news);\n if (invalid !== undefined) return yield* Effect.fail(invalid);\n const owned = output !== undefined && output.accountId === accountId ? output : undefined;\n const live = owned === undefined ? undefined : yield* getNode(accountId, owned.id);\n // β˜… Gone out of band (or a replace's new generation): create, under the same refusals.\n if (owned === undefined || live === undefined) return yield* createFresh(accountId, news);\n if (owned.ha !== news.ha) {\n // β›” The plan would have said `replace`; it can only reach here with `ha` unresolved then.\n return yield* Effect.fail(\n new MeshNodeError({\n message: `MeshNode \"${news.name}\": \\`ha\\` is create-only (stored ${owned.ha}, declared ${news.ha}). Plan again so the change is a replace.`,\n }),\n );\n }\n if (live.name === news.name) return attributesOf(live, accountId, owned.ha);\n const renamed = yield* renameNode(accountId, live.id, news.name).pipe(\n Effect.catchTag('DuplicateTunnelName', () =>\n Effect.fail(nameTaken(news.name, 'held by another tunnel')),\n ),\n );\n return attributesOf(renamed, accountId, owned.ha);\n });\n\n/**\n * ⚠️ Cloudflare may refuse to delete a node while replicas are connected (Alchemy's WarpConnector\n * notes \"Deleting requires all connector sessions to be down\"; unmeasured here). Stop the\n * client on every replica first. A node already gone is a successful delete.\n */\nexport const deleteMeshNode = (output: MeshNodeAttributes) =>\n deleteNode(output.accountId, output.id);\n",
12
11
  "/**\n * `fetchMeshNodeToken` β€” a Mesh node's enrolment token, fetched on demand for a one-off Bun\n * `mesh-enroll` step. β›” Not a Resource, and never an attribute: see mesh-node-form.ts for why\n * the token must stay out of Alchemy state.\n *\n * β›” WHERE THE VALUE MAY GO. Straight into a root-owned `0600` file on the node's host, which the\n * enrolment reads (`warp-cli --accept-tos connector new <token>`, per the Mesh get-started page,\n * read 2026-09-21). Nowhere else: not Alchemy state, not a secrets store (a runtime credential\n * belongs to the host that runs it), not a log line, not a CI variable. It is returned\n * `Redacted`, so an accidental `console.log` or `Effect.log` prints `<redacted>`; call\n * `Redacted.value` only at the write.\n * ⚠️ `warp-cli` takes the token as an ARGUMENT, so it is visible in the host's process list for\n * the life of that command. Run the enrolment as root on the node itself, never over a shell\n * whose history is kept.\n * ⚠️ THE ENDPOINT NEEDS A WRITE PERMISSION. Its API reference (read 2026-09-21) accepts\n * \"Cloudflare One Connectors Write\", \"Cloudflare One Connector: cloudflared Write\" or\n * \"Cloudflare Tunnel Write\"; the Mesh get-started guide also names \"Cloudflare One Connector:\n * WARP Write\". Connectors Write is in both lists. Mint a short-lived token for this step alone;\n * a read-only plan token gets a 403 here.\n *\n * β˜… Requires distilled `Credentials` and an `HttpClient`, not Alchemy's CloudflareEnvironment: a\n * Bun script has no Alchemy profile, so the account id is an argument. Provide\n * `Credentials.fromApiToken({ apiToken })` (from `@distilled.cloud/cloudflare/Credentials`) and\n * `FetchHttpClient.layer`. β›” Not `Credentials.fromEnv()`: it falls back to the Global API Key\n * (`CLOUDFLARE_API_KEY` + `CLOUDFLARE_EMAIL`) when no token is set β€” and `refuseUnsafe` below\n * refuses that key rather than trusting the caller to have read this.\n */\nimport { Credentials } from '@distilled.cloud/cloudflare/Credentials';\nimport * as zeroTrust from '@distilled.cloud/cloudflare/zero-trust';\nimport * as Effect from 'effect/Effect';\nimport * as Redacted from 'effect/Redacted';\nimport { MeshNodeError, findNodeByName } from './mesh-node-api.ts';\n\n/** Which node: by id (the `MeshNode` attribute) or by its exact name. */\nexport type MeshNodeRef = { readonly accountId: string } & (\n | { readonly id: string; readonly name?: undefined }\n | { readonly name: string; readonly id?: undefined }\n);\n\n/**\n * β›” REFUSED BEFORE ANY REQUEST IS SENT:\n * - `DISTILLED_DEBUG_HTTP` set. distilled's Cloudflare protocol then `console.error`s the first\n * 400 characters of EVERY response body (protocol.ts, measured in 1.0.0-rc.12) β€” the token\n * response is `{\"success\":true,…,\"result\":\"<token>\"}`, so the whole token lands on stderr and in\n * whatever captures it. The check reads `process.env` because that is what distilled reads.\n * - The Global API Key (`apiKey` credentials). Account-wide and long-lived: the one credential a\n * one-off enrolment must never use.\n * - An EMPTY API token. A denied secrets-store grant renders empty, and Cloudflare then answers\n * like a bad permission β€” which would send the operator to widen a grant that was fine\n * (client.ts says the same for `CLOUDFLARE_API_TOKEN`).\n */\nconst refuseUnsafe = Effect.gen(function* () {\n if ((globalThis.process?.env?.['DISTILLED_DEBUG_HTTP'] ?? '') !== '') {\n return yield* Effect.fail(\n new MeshNodeError({\n message:\n 'Refusing to fetch a Mesh node token while DISTILLED_DEBUG_HTTP is set: the SDK would print the token to stderr. Unset it and run again.',\n }),\n );\n }\n const credentials = yield* yield* Credentials;\n if (credentials.type === 'apiKey') {\n return yield* Effect.fail(\n new MeshNodeError({\n message:\n 'Refusing the Global API Key for a Mesh node token. Mint a short-lived API token with \"Cloudflare One Connectors Write\" and pass it with fromApiToken.',\n }),\n );\n }\n if (credentials.type === 'apiToken' && Redacted.value(credentials.apiToken).length === 0) {\n return yield* Effect.fail(\n new MeshNodeError({\n message:\n 'The Cloudflare API token is EMPTY. That is a missing or denied secrets grant, not a Cloudflare permission: re-mint the token before changing any grant.',\n }),\n );\n }\n});\n\nconst resolveId = (ref: MeshNodeRef) => {\n if (ref.id !== undefined) return Effect.succeed(ref.id);\n const name = ref.name;\n return Effect.flatMap(findNodeByName(ref.accountId, name), (node) =>\n node === undefined\n ? Effect.fail(new MeshNodeError({ message: `No live Mesh node named \"${name}\".` }))\n : Effect.succeed(node.id),\n );\n};\n\n/**\n * β›” An EMPTY token fails closed: `warp-cli connector new \"\"` would not enrol, and a script that\n * wrote an empty file would report success over a host that never joined.\n * β›” The failures made here name the node and account and quote no response body, because the\n * success body IS the token. Other SDK errors pass through; they come only from non-2xx\n * responses, which carry an error envelope, not a token.\n */\nexport const fetchMeshNodeToken = (ref: MeshNodeRef) =>\n Effect.gen(function* () {\n yield* refuseUnsafe;\n const nodeId = yield* resolveId(ref);\n const token = yield* zeroTrust\n .getTunnelWarpConnectorToken({ accountId: ref.accountId, tunnelId: nodeId })\n .pipe(\n Effect.catchTag('TunnelNotFound', () =>\n Effect.fail(\n new MeshNodeError({\n message: `Mesh node ${nodeId} does not exist in account ${ref.accountId}.`,\n }),\n ),\n ),\n Effect.catchTag('Forbidden', () =>\n Effect.fail(\n new MeshNodeError({\n message: `Reading Mesh node ${nodeId}'s token was refused (403). The endpoint needs a Write permission such as \"Cloudflare One Connectors Write\", on a token scoped to account ${ref.accountId}.`,\n }),\n ),\n ),\n );\n if (typeof token !== 'string' || token.length === 0) {\n return yield* Effect.fail(\n new MeshNodeError({ message: `Mesh node ${nodeId} returned an empty token.` }),\n );\n }\n return Redacted.make(token);\n });\n",
13
- "/**\n * The HomeFlare Cloudflare provider collection, in the shape `LiteLLM.providers()` has.\n *\n * β˜… ONE LAYER, BUILT ONCE, so the single `Config.Redacted('CLOUDFLARE_API_TOKEN')` read is shared\n * by every resource in the stack rather than repeated per resource.\n *\n * ⚠️ THIS IS NOT `alchemy/Cloudflare`'s COLLECTION AND DOES NOT REPLACE IT. A stack that declares\n * both a `Cloudflare.R2.Bucket` and this lock merges the two:\n * `Layer.mergeAll(Cloudflare.providers(), HomeflareCloudflare.providers())` β€” the shape\n * a consuming stack names for one talking to more than one account.\n *\n * β˜… TWO CREDENTIAL PATHS, ONE PER SDK. `R2BucketLock` uses the `cloudflare` SDK through\n * `CloudflareApiLive` (`CLOUDFLARE_API_TOKEN`, fail-closed). `MeshNode` uses Alchemy's own SDK\n * and resolves credentials and the account through `Cloudflare.CloudflareApiLive()` β€” the same\n * source `Cloudflare.providers()` uses (an Alchemy profile, or `CLOUDFLARE_API_TOKEN` +\n * `CLOUDFLARE_ACCOUNT_ID` in CI), so a node lands in the account its Gateway rules do.\n * ⚠️ That second layer is built again here even when the stack also merges\n * `Cloudflare.providers()`. `Layer.provide`, not `provideMerge`: it feeds MeshNode only and adds\n * nothing to the stack's context.\n */\nimport * as Cloudflare from 'alchemy/Cloudflare';\nimport * as Provider from 'alchemy/Provider';\nimport * as Layer from 'effect/Layer';\nimport { CloudflareApiLive } from './client.ts';\nimport { MeshNode, MeshNodeProvider } from './mesh-node.ts';\nimport { R2BucketLock, R2BucketLockProvider } from './r2-bucket-lock.ts';\n\nexport class Providers extends Provider.ProviderCollection<Providers>()('HomeflareCloudflare') {}\n\nexport const providers = () =>\n Layer.effect(Providers, Provider.collection([R2BucketLock, MeshNode])).pipe(\n Layer.provide(\n Layer.mergeAll(\n R2BucketLockProvider(),\n // β›” `orDie`, as `Cloudflare.providers()` ends with: a stack's providers layer must not\n // fail, and a profile that cannot resolve is a defect carrying Alchemy's own sentence.\n MeshNodeProvider().pipe(Layer.provide(Cloudflare.CloudflareApiLive()), Layer.orDie),\n ),\n ),\n Layer.provideMerge(CloudflareApiLive),\n );\n",
12
+ "/**\n * The HomeFlare Cloudflare provider collection, in the shape `LiteLLM.providers()` has.\n *\n * ⚠️ THIS IS NOT `alchemy/Cloudflare`'s COLLECTION AND DOES NOT REPLACE IT. A stack that declares\n * both a `Cloudflare.R2.Bucket` and this lock merges the two:\n * `Layer.mergeAll(Cloudflare.providers(), HomeflareCloudflare.providers())` β€” the shape\n * a consuming stack names for one talking to more than one account.\n *\n * β˜… ONE CREDENTIAL PATH NOW, NOT TWO. Until 2026-09-23 `R2BucketLock` ran over the `cloudflare`\n * npm SDK with its own `CLOUDFLARE_API_TOKEN` read (`client.ts`, since deleted) while `MeshNode`\n * ran over `@distilled.cloud/cloudflare` through `Cloudflare.CloudflareApiLive()`. Both\n * providers now call distilled, so both resolve credentials and the account the same way\n * `Cloudflare.providers()` does (an Alchemy profile, or `CLOUDFLARE_API_TOKEN` +\n * `CLOUDFLARE_ACCOUNT_ID` in CI) β€” a lock and the Gateway rules that reference the same bucket\n * land in the same account.\n * ⚠️ THE LAYER IS BUILT TWICE ON PURPOSE, once per provider, even when the stack also merges\n * `Cloudflare.providers()`. `Layer.provide`, not `provideMerge`: each feeds its own provider\n * only and adds nothing to the stack's context.\n */\nimport * as Cloudflare from 'alchemy/Cloudflare';\nimport * as Provider from 'alchemy/Provider';\nimport * as Layer from 'effect/Layer';\nimport { MeshNode, MeshNodeProvider } from './mesh-node.ts';\nimport { R2BucketLock, R2BucketLockProvider } from './r2-bucket-lock.ts';\n\nexport class Providers extends Provider.ProviderCollection<Providers>()('HomeflareCloudflare') {}\n\n// β›” `orDie` ON BOTH, as `Cloudflare.providers()` ends with: a stack's providers layer must not\n// fail, and a profile that cannot resolve is a defect carrying Alchemy's own sentence.\nexport const providers = () =>\n Layer.effect(Providers, Provider.collection([R2BucketLock, MeshNode])).pipe(\n Layer.provide(\n Layer.mergeAll(\n R2BucketLockProvider().pipe(Layer.provide(Cloudflare.CloudflareApiLive()), Layer.orDie),\n MeshNodeProvider().pipe(Layer.provide(Cloudflare.CloudflareApiLive()), Layer.orDie),\n ),\n ),\n );\n",
14
13
  "/**\n * House defaults on Alchemy's Website stacks β€” Astro and Vite, which is what we ship.\n *\n * β˜… NOT Nextjs. The blog measured Website.Nextjs planning as **create** because it\n * hashes source; adopt is `Worker`, not Nextjs. Do not add a Nextjs helper until an\n * app actually deploys that way.\n *\n * β›” `disable_nodejs_process_v2` ON ASTRO IS LOAD-BEARING. Workerd process-v2 (compat\n * date 2025-09-15) makes every Astro page return `[object Object]`. Vite/TanStack\n * Start does not need that flag; it needs `nodejs_compat` (Alchemy already defaults\n * it on Vite).\n *\n * β›” Alchemy injects `@alchemy.run/frontend-frameworks/astro`. Do not add\n * `@astrojs/cloudflare` β€” that is the wrangler path.\n *\n * β˜… Alchemy's Website props are an Effect-wrapped InputProps union. We do not re-export\n * that type β€” isolatedDeclarations cannot name it. Extra Alchemy fields still pass\n * through via the rest bag; the assertion is only at the call into Alchemy.\n */\nimport * as AdoptPolicy from 'alchemy/AdoptPolicy';\nimport * as Cloudflare from 'alchemy/Cloudflare';\nimport * as RemovalPolicy from 'alchemy/RemovalPolicy';\n\nconst ASTRO_FLAGS: readonly string[] = ['nodejs_compat', 'disable_nodejs_process_v2'];\n\nexport interface AstroWebsiteProps {\n readonly rootDir?: string;\n readonly name?: string;\n readonly sessionKVBindingName?: string | false;\n readonly compatibility?: { readonly flags?: readonly string[]; readonly date?: string };\n}\n\nexport interface ViteWebsiteProps {\n readonly rootDir?: string;\n readonly name?: string;\n}\n\n/**\n * `Cloudflare.Website.Astro` with house flags, no auto session KV, adopt+retain.\n *\n * const site = yield* astroWebsite('subnetcalc', { rootDir });\n */\nexport function astroWebsite(\n name: string,\n props: AstroWebsiteProps,\n): ReturnType<typeof Cloudflare.Website.Astro> {\n const flags = props.compatibility?.flags ?? ASTRO_FLAGS;\n const merged = {\n ...props,\n name: props.name ?? name,\n sessionKVBindingName: props.sessionKVBindingName ?? false,\n compatibility: { ...props.compatibility, flags: [...flags] },\n };\n return Cloudflare.Website.Astro(name, merged as never).pipe(\n AdoptPolicy.adopt(true),\n RemovalPolicy.retain(),\n );\n}\n\n/**\n * `Cloudflare.Website.Vite` β€” TanStack Start, SolidStart, static Vite.\n * `nodejs_compat` is Alchemy's Vite default; we do not add process-v2 here.\n *\n * const app = yield* viteWebsite('aimto', { rootDir: webRoot });\n */\nexport function viteWebsite(\n name: string,\n props?: ViteWebsiteProps,\n): ReturnType<typeof Cloudflare.Website.Vite> {\n const merged = { ...props, name: props?.name ?? name };\n return Cloudflare.Website.Vite(name, merged as never).pipe(\n AdoptPolicy.adopt(true),\n RemovalPolicy.retain(),\n );\n}\n"
15
14
  ],
16
- "mappings": "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",
17
- "debugId": "8A155CE09F54FFB164756E2164756E21",
15
+ "mappings": "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",
16
+ "debugId": "AB671D282C098EA364756E2164756E21",
18
17
  "names": []
19
18
  }
@@ -6,8 +6,9 @@
6
6
  * `GET …/warp_connector/{id}/token` (developers.cloudflare.com/mesh/get-started, read
7
7
  * 2026-09-21). There is no separate "mesh node" endpoint.
8
8
  *
9
- * β˜… OVER `@distilled.cloud/cloudflare`, THE SDK ALCHEMY'S OWN CLOUDFLARE PROVIDERS USE, not the
10
- * `cloudflare` npm SDK the R2 lock uses. Measured 2026-09-21: `cloudflare@4.5.0`'s
9
+ * β˜… OVER `@distilled.cloud/cloudflare`, THE SDK ALCHEMY'S OWN CLOUDFLARE PROVIDERS USE β€” and, as
10
+ * of 2026-09-23, the same one `R2BucketLock` calls too (r2-bucket-lock.ts), not the `cloudflare`
11
+ * npm SDK either used before that. Measured 2026-09-21: `cloudflare@4.5.0`'s
11
12
  * `WARPConnectorCreateParams` has only `account_id` and `name`, so it cannot create an HA node.
12
13
  * distilled `1.0.0-rc.12` (the version alchemy@2.0.0-beta.79 pins) has `ha?: boolean` on
13
14
  * `CreateTunnelWarpConnectorRequest`, typed errors (`TunnelNotFound` code 1002 / 404,
@@ -1 +1 @@
1
- {"version":3,"file":"mesh-node-api.d.ts","sourceRoot":"","sources":["../../src/cloudflare/mesh-node-api.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,OAAO,KAAK,SAAS,MAAM,wCAAwC,CAAC;AAEpE,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AAGxC,uFAAuF;AACvF,MAAM,MAAM,cAAc,GAAG,UAAU,GAAG,UAAU,GAAG,SAAS,GAAG,MAAM,CAAC;AAE1E,uFAAuF;AACvF,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,cAAc,GAAG,SAAS,CAAC;CAC7C;;;;AAED,oFAAoF;AACpF,qBAAa,aAAc,SAAQ,mBAAkC;IACnE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B,CAAC;CAAG;AAEL,KAAK,GAAG,GAAG;IACT,QAAQ,CAAC,EAAE,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACpC,CAAC;AAIF;;;;GAIG;AACH,eAAO,MAAM,OAAO,QAAS,GAAG,KAAG,YAAY,GAAG,SAQjD,CAAC;AAEF,gGAAgG;AAChG,eAAO,MAAM,OAAO,cAAe,MAAM,UAAU,MAAM,8HAItD,CAAC;AAEJ;;;;;;;GAOG;AACH,eAAO,MAAM,cAAc,cAAe,MAAM,QAAQ,MAAM,oIAe3D,CAAC;AAEJ;;;;GAIG;AACH,eAAO,MAAM,UAAU,cAAe,MAAM,QAAQ,MAAM,MAAM,OAAO,yHAUpE,CAAC;AAEJ,sFAAsF;AACtF,eAAO,MAAM,UAAU,cAAe,MAAM,UAAU,MAAM,QAAQ,MAAM,wGAGa,CAAC;AAExF,gFAAgF;AAChF,eAAO,MAAM,UAAU,cAAe,MAAM,UAAU,MAAM,0GAIzD,CAAC"}
1
+ {"version":3,"file":"mesh-node-api.d.ts","sourceRoot":"","sources":["../../src/cloudflare/mesh-node-api.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,OAAO,KAAK,SAAS,MAAM,wCAAwC,CAAC;AAEpE,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AAGxC,uFAAuF;AACvF,MAAM,MAAM,cAAc,GAAG,UAAU,GAAG,UAAU,GAAG,SAAS,GAAG,MAAM,CAAC;AAE1E,uFAAuF;AACvF,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,cAAc,GAAG,SAAS,CAAC;CAC7C;;;;AAED,oFAAoF;AACpF,qBAAa,aAAc,SAAQ,mBAAkC;IACnE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B,CAAC;CAAG;AAEL,KAAK,GAAG,GAAG;IACT,QAAQ,CAAC,EAAE,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACpC,CAAC;AAIF;;;;GAIG;AACH,eAAO,MAAM,OAAO,QAAS,GAAG,KAAG,YAAY,GAAG,SAQjD,CAAC;AAEF,gGAAgG;AAChG,eAAO,MAAM,OAAO,cAAe,MAAM,UAAU,MAAM,8HAItD,CAAC;AAEJ;;;;;;;GAOG;AACH,eAAO,MAAM,cAAc,cAAe,MAAM,QAAQ,MAAM,oIAe3D,CAAC;AAEJ;;;;GAIG;AACH,eAAO,MAAM,UAAU,cAAe,MAAM,QAAQ,MAAM,MAAM,OAAO,yHAUpE,CAAC;AAEJ,sFAAsF;AACtF,eAAO,MAAM,UAAU,cAAe,MAAM,UAAU,MAAM,QAAQ,MAAM,wGAGa,CAAC;AAExF,gFAAgF;AAChF,eAAO,MAAM,UAAU,cAAe,MAAM,UAAU,MAAM,0GAIzD,CAAC"}
@@ -3,6 +3,6 @@ import * as Layer from 'effect/Layer';
3
3
  declare const Providers_base: Provider.ProviderCollection<Providers, "HomeflareCloudflare">;
4
4
  export declare class Providers extends Providers_base {
5
5
  }
6
- export declare const providers: () => Layer.Layer<import("./client.ts").CloudflareApi | Providers, never, import("alchemy").AuthProviders | import("effect/unstable/process/ChildProcessSpawner").ChildProcessSpawner | import("effect/Crypto").Crypto | import("effect/FileSystem").FileSystem | import("effect/unstable/http/HttpClient").HttpClient | import("effect/Path").Path>;
6
+ export declare const providers: () => Layer.Layer<Providers, never, import("alchemy").AuthProviders | import("effect/unstable/process/ChildProcessSpawner").ChildProcessSpawner | import("effect/Crypto").Crypto | import("effect/FileSystem").FileSystem | import("effect/unstable/http/HttpClient").HttpClient | import("effect/Path").Path>;
7
7
  export {};
8
8
  //# sourceMappingURL=providers.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"providers.d.ts","sourceRoot":"","sources":["../../src/cloudflare/providers.ts"],"names":[],"mappings":"AAqBA,OAAO,KAAK,QAAQ,MAAM,kBAAkB,CAAC;AAC7C,OAAO,KAAK,KAAK,MAAM,cAAc,CAAC;;AAKtC,qBAAa,SAAU,SAAQ,cAA+D;CAAG;AAEjG,eAAO,MAAM,SAAS,sVAWnB,CAAC"}
1
+ {"version":3,"file":"providers.d.ts","sourceRoot":"","sources":["../../src/cloudflare/providers.ts"],"names":[],"mappings":"AAoBA,OAAO,KAAK,QAAQ,MAAM,kBAAkB,CAAC;AAC7C,OAAO,KAAK,KAAK,MAAM,cAAc,CAAC;;AAItC,qBAAa,SAAU,SAAQ,cAA+D;CAAG;AAIjG,eAAO,MAAM,SAAS,gTAQnB,CAAC"}
@@ -13,16 +13,17 @@ import { Resource, type ResourceClass } from 'alchemy/Resource';
13
13
  * every gate completely green. The operator's rule (2026-09-15) is to cover such a gap with a
14
14
  * custom resource over the vendor's SDK, which is what this is.
15
15
  *
16
- * β›” THE SDK ALREADY HAD IT, AND THAT IS WORTH SAYING BECAUSE IT DECIDED THE SHAPE. Measured
17
- * 2026-09-15 in `cloudflare@4.5.0`, `resources/r2/buckets/locks.d.ts`:
18
- *
19
- * update(bucketName: string, params: LockUpdateParams, options?): APIPromise<…>
20
- * get(bucketName: string, params: LockGetParams, options?): APIPromise<LockGetResponse>
21
- *
22
- * over `PUT`/`GET /accounts/{account_id}/r2/buckets/{bucket_name}/lock` with the jurisdiction
23
- * carried as the `cf-r2-jurisdiction` header (`locks.mjs:17` and `:41`). So there is no raw
24
- * `client.put` fallback here and no path string this package invented β€” the fallback the rule
25
- * allows was not needed.
16
+ * β˜… OVER `@distilled.cloud/cloudflare/r2`, THE SAME SDK `Cloudflare.MeshNode` USES
17
+ * (mesh-node-api.ts), NOT THE `cloudflare` NPM SDK THIS RESOURCE USED TO CALL. Measured
18
+ * 2026-09-23 in distilled `1.0.0-rc.12` (the version alchemy@2.0.0-beta.79 pins),
19
+ * `packages/cloudflare/src/services/r2.ts`: `getBucketLock` / `putBucketLock` cover the same
20
+ * `GET`/`PUT /accounts/{account_id}/r2/buckets/{bucket_name}/lock` route the old SDK's
21
+ * `r2.buckets.locks.get`/`.update` did β€” same `rules[]` body, the `cf-r2-jurisdiction` header
22
+ * spelled as the `jurisdiction` field β€” but typed as `NoSuchBucket | InvalidRoute |
23
+ * CloudflareRateLimited | CloudflareError` instead of an opaque HTTP status, so the "bucket
24
+ * does not exist yet" case below is `catchTag('NoSuchBucket', …)`, never a status check. This
25
+ * is also the swap that lets `client.ts` (the hand-rolled `cloudflare@4.5.0` wrapper) go away:
26
+ * nothing else in the kit imported it.
26
27
  *
27
28
  * ⚠️ THE RULE SET IS A REPLACE, NOT A MERGE. `PUT` takes the WHOLE list, exactly like R2's
28
29
  * lifecycle rules β€” which is the trap a consuming stack records for
@@ -30,9 +31,10 @@ import { Resource, type ResourceClass } from 'alchemy/Resource';
30
31
  * drop them, so `read` fetches them first and `reconcile` refuses to write when they already
31
32
  * match.
32
33
  */
33
- import type Cloudflare from 'cloudflare';
34
+ import type { CloudflareOpContext } from '@distilled.cloud/cloudflare/r2';
35
+ import { Credentials } from '@distilled.cloud/cloudflare/Credentials';
34
36
  import * as Effect from 'effect/Effect';
35
- import { CloudflareApi } from './client.ts';
37
+ import * as HttpClient from 'effect/unstable/http/HttpClient';
36
38
  import { type Jurisdiction, type R2LockRule } from './lock-rules.ts';
37
39
  import type { Providers } from './providers.ts';
38
40
  export interface R2BucketLockProps {
@@ -62,10 +64,12 @@ export declare const R2BucketLock: ResourceClass<R2BucketLock>;
62
64
  * exist yet: `404 {"success":false,"errors":[{"code":10006,"message":"The specified bucket does
63
65
  * not exist."}]}` β€” raised from `read`, during PLANNING, before anything is created. Alchemy
64
66
  * calls `read` for a resource with no prior state, so on every first deploy the lock is asked
65
- * about a bucket the same plan is about to create. Letting that throw makes the resource
66
- * impossible to plan; swallowing it with `catchAll` would also hide a 403 from a mis-scoped
67
- * token, which is the failure this estate spends the most time on. So: 404 β†’ absent, everything
68
- * else propagates.
67
+ * about a bucket the same plan is about to create. Code 10006 is exactly distilled's
68
+ * `NoSuchBucket` tag (r2.ts's error matcher, `[{ code: 10006 }]`), so `catchTag` on it is the
69
+ * same case the status check used to cover β€” but by the vendor's own error identity, not by
70
+ * reading a number off the response. Letting it propagate would make the resource impossible to
71
+ * plan; swallowing every failure would also hide a 403 from a mis-scoped token, which is the
72
+ * failure this estate spends the most time on. So: `NoSuchBucket` β†’ absent, everything else dies.
69
73
  *
70
74
  * β›” AN EMPTY LIVE RULE SET IS "NO LOCK", NOT "A LOCK WITH NOTHING IN IT", so it reads as absent
71
75
  * and the resource CREATES. The API answers `{}` for a bucket that has never been locked and
@@ -76,18 +80,21 @@ export declare const R2BucketLock: ResourceClass<R2BucketLock>;
76
80
  * operator reads in the diff. An adopted rule set that already MATCHES then costs no request at
77
81
  * all, because `reconcile` below returns before writing.
78
82
  */
79
- export declare const readLock: (client: Cloudflare, props: {
83
+ export declare const readLock: (props: {
80
84
  accountId: string;
81
85
  bucketName: string;
82
86
  jurisdiction?: Jurisdiction;
83
- }, written: boolean) => Effect.Effect<R2BucketLockAttributes | undefined>;
87
+ }, written: boolean) => Effect.Effect<R2BucketLockAttributes | undefined, never, CloudflareOpContext>;
84
88
  /**
85
89
  * β›” A NO-OP MAKES NO REQUEST, AND ON A LOCK THAT MATTERS MORE THAN TIDINESS. `PUT` replaces the
86
90
  * rule set; re-sending an identical one on every deploy is a write against the only thing
87
91
  * standing between this repository and a delete, for no change. `output` is what the last deploy
88
92
  * persisted (or what `read` just adopted), so the comparison costs nothing either.
93
+ * β›” `NoSuchBucket` IS NOT CAUGHT HERE. Unlike `read`, a `reconcile` that hits it means the bucket
94
+ * this declaration names is genuinely gone β€” that is a real failure to surface, not a case to
95
+ * paper over, so it dies like every other tagged error `putBucketLock` can raise.
89
96
  */
90
- export declare const reconcileLock: (client: Cloudflare, news: R2BucketLockProps, output: R2BucketLockAttributes | undefined) => Effect.Effect<R2BucketLockAttributes>;
97
+ export declare const reconcileLock: (news: R2BucketLockProps, output: R2BucketLockAttributes | undefined) => Effect.Effect<R2BucketLockAttributes, never, CloudflareOpContext>;
91
98
  /**
92
99
  * β›” DELETING THIS RESOURCE MEANS PUTTING AN EMPTY RULE SET, WHICH IS UNLOCKING THE BUCKET. It is
93
100
  * implemented because the retirement path is real β€” R2 refuses to empty a bucket while any lock
@@ -95,6 +102,6 @@ export declare const reconcileLock: (client: Cloudflare, news: R2BucketLockProps
95
102
  * `defaultRemovalPolicy: 'retain'` and the estate CLI's refusal of `destroy` precisely because
96
103
  * no ordinary deploy should ever reach it.
97
104
  */
98
- export declare const deleteLock: (client: Cloudflare, output: R2BucketLockAttributes, accountId: string) => Effect.Effect<void>;
99
- export declare const R2BucketLockProvider: () => import("effect/Layer").Layer<Provider.Provider<R2BucketLock>, never, CloudflareApi>;
105
+ export declare const deleteLock: (output: R2BucketLockAttributes, accountId: string) => Effect.Effect<void, never, CloudflareOpContext>;
106
+ export declare const R2BucketLockProvider: () => import("effect/Layer").Layer<Provider.Provider<R2BucketLock>, never, Credentials | HttpClient.HttpClient>;
100
107
  //# sourceMappingURL=r2-bucket-lock.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"r2-bucket-lock.d.ts","sourceRoot":"","sources":["../../src/cloudflare/r2-bucket-lock.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,QAAQ,MAAM,kBAAkB,CAAC;AAC7C,OAAO,EAAE,QAAQ,EAAE,KAAK,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAChE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,OAAO,KAAK,UAAU,MAAM,YAAY,CAAC;AAEzC,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AACxC,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EACL,KAAK,YAAY,EACjB,KAAK,UAAU,EAIhB,MAAM,iBAAiB,CAAC;AACzB,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAEhD,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,+GAA+G;IAC/G,QAAQ,CAAC,YAAY,CAAC,EAAE,YAAY,CAAC;IACrC;;;;;OAKG;IACH,QAAQ,CAAC,KAAK,EAAE,SAAS,UAAU,EAAE,CAAC;CACvC;AAED,MAAM,MAAM,sBAAsB,GAAG;IACnC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,YAAY,EAAE,YAAY,CAAC;IACpC,QAAQ,CAAC,KAAK,EAAE,SAAS,UAAU,EAAE,CAAC;CACvC,CAAC;AAEF,MAAM,WAAW,YAAa,SAAQ,QAAQ,CAC5C,yBAAyB,EACzB,iBAAiB,EACjB,sBAAsB,EACtB,KAAK,EACL,SAAS,CACV;CAAG;AAKJ,eAAO,MAAM,YAAY,EAAE,aAAa,CAAC,YAAY,CAYpD,CAAC;AAEF;;;;;;;;;;;;;;;;;;;GAmBG;AACH,eAAO,MAAM,QAAQ,WACX,UAAU,SACX;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,YAAY,CAAC,EAAE,YAAY,CAAA;CAAE,WACpE,OAAO,KACf,MAAM,CAAC,MAAM,CAAC,sBAAsB,GAAG,SAAS,CA0B/C,CAAC;AAEL;;;;;GAKG;AACH,eAAO,MAAM,aAAa,WAChB,UAAU,QACZ,iBAAiB,UACf,sBAAsB,GAAG,SAAS,KACzC,MAAM,CAAC,MAAM,CAAC,sBAAsB,CAYnC,CAAC;AAEL;;;;;;GAMG;AACH,eAAO,MAAM,UAAU,WACb,UAAU,UACV,sBAAsB,aACnB,MAAM,KAChB,MAAM,CAAC,MAAM,CAAC,IAAI,CASjB,CAAC;AAEL,eAAO,MAAM,oBAAoB,2FA8B9B,CAAC"}
1
+ {"version":3,"file":"r2-bucket-lock.d.ts","sourceRoot":"","sources":["../../src/cloudflare/r2-bucket-lock.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,QAAQ,MAAM,kBAAkB,CAAC;AAC7C,OAAO,EAAE,QAAQ,EAAE,KAAK,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAChE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,gCAAgC,CAAC;AAC1E,OAAO,EAAE,WAAW,EAAE,MAAM,yCAAyC,CAAC;AAGtE,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AACxC,OAAO,KAAK,UAAU,MAAM,iCAAiC,CAAC;AAC9D,OAAO,EACL,KAAK,YAAY,EACjB,KAAK,UAAU,EAIhB,MAAM,iBAAiB,CAAC;AACzB,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAEhD,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,+GAA+G;IAC/G,QAAQ,CAAC,YAAY,CAAC,EAAE,YAAY,CAAC;IACrC;;;;;OAKG;IACH,QAAQ,CAAC,KAAK,EAAE,SAAS,UAAU,EAAE,CAAC;CACvC;AAED,MAAM,MAAM,sBAAsB,GAAG;IACnC,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,YAAY,EAAE,YAAY,CAAC;IACpC,QAAQ,CAAC,KAAK,EAAE,SAAS,UAAU,EAAE,CAAC;CACvC,CAAC;AAEF,MAAM,WAAW,YAAa,SAAQ,QAAQ,CAC5C,yBAAyB,EACzB,iBAAiB,EACjB,sBAAsB,EACtB,KAAK,EACL,SAAS,CACV;CAAG;AAKJ,eAAO,MAAM,YAAY,EAAE,aAAa,CAAC,YAAY,CAYpD,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,eAAO,MAAM,QAAQ,UACZ;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,YAAY,CAAC,EAAE,YAAY,CAAA;CAAE,WACpE,OAAO,KACf,MAAM,CAAC,MAAM,CAAC,sBAAsB,GAAG,SAAS,EAAE,KAAK,EAAE,mBAAmB,CAkB3E,CAAC;AAEL;;;;;;;;GAQG;AACH,eAAO,MAAM,aAAa,SAClB,iBAAiB,UACf,sBAAsB,GAAG,SAAS,KACzC,MAAM,CAAC,MAAM,CAAC,sBAAsB,EAAE,KAAK,EAAE,mBAAmB,CAa/D,CAAC;AAEL;;;;;;GAMG;AACH,eAAO,MAAM,UAAU,WACb,sBAAsB,aACnB,MAAM,KAChB,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,KAAK,EAAE,mBAAmB,CAU7C,CAAC;AAEL,eAAO,MAAM,oBAAoB,iHAuC9B,CAAC"}
@@ -1,11 +1,23 @@
1
1
  /**
2
- * Create/PATCH bodies for `Forgejo.BranchProtection` β€” extracted for the 250-line cap.
2
+ * Create/edit request bodies for `Forgejo.BranchProtection` β€” extracted for the 250-line cap.
3
3
  *
4
- * β˜… READ OFF `<estate>/mcp-servers/docs/api/upstream/forgejo.json` β€” create posts
5
- * `CreateBranchProtectionOption`; update sends `EditBranchProtectionOption` fields only.
4
+ * β˜… `repoCreateBranchProtection`'s request carries `branch_name`; `repoEditBranchProtection`'s
5
+ * carries `name` (the rule-name path segment) instead β€” both are covered by the two exports
6
+ * below, so branch-protection.ts never builds the field list twice.
6
7
  */
7
8
  import type { BranchProtectionProps } from './branch-protection.ts';
8
- export declare const branchProtectionForm: (props: BranchProtectionProps) => {
9
+ export declare const createBranchProtectionForm: (props: BranchProtectionProps) => {
10
+ apply_to_admins?: boolean;
11
+ dismiss_stale_approvals?: boolean;
12
+ enable_push?: boolean;
13
+ enable_status_check?: boolean;
14
+ ignore_stale_approvals?: boolean;
15
+ require_signed_commits?: boolean;
16
+ required_approvals?: number;
17
+ status_check_contexts?: string[];
18
+ branch_name: string;
19
+ };
20
+ export declare const editBranchProtectionForm: (props: BranchProtectionProps) => {
9
21
  apply_to_admins?: boolean;
10
22
  dismiss_stale_approvals?: boolean;
11
23
  enable_push?: boolean;
@@ -14,6 +26,5 @@ export declare const branchProtectionForm: (props: BranchProtectionProps) => {
14
26
  require_signed_commits?: boolean;
15
27
  required_approvals?: number;
16
28
  status_check_contexts?: string[];
17
- rule_name: string;
18
29
  };
19
30
  //# sourceMappingURL=branch-protection-form.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"branch-protection-form.d.ts","sourceRoot":"","sources":["../../src/forgejo/branch-protection-form.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAC;AAEpE,eAAO,MAAM,oBAAoB,UAAW,qBAAqB;;;;;;;;;;CAoB/D,CAAC"}
1
+ {"version":3,"file":"branch-protection-form.d.ts","sourceRoot":"","sources":["../../src/forgejo/branch-protection-form.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAC;AAuBpE,eAAO,MAAM,0BAA0B,UAAW,qBAAqB;;;;;;;;;;CAGrE,CAAC;AAEH,eAAO,MAAM,wBAAwB,UAAW,qBAAqB;;;;;;;;;CAAkB,CAAC"}
@@ -2,8 +2,13 @@
2
2
  * `Forgejo.BranchProtection` β€” one rule at `/repos/{owner}/{repo}/branch_protections/{name}`.
3
3
  *
4
4
  * β˜… READ OFF `<estate>/mcp-servers/docs/api/upstream/forgejo.json`: create is POST collection;
5
- * read/update/delete use the branch/rule name in the path segment `{name}`. Response carries
6
- * `rule_name` (preferred) and deprecated `branch_name`.
5
+ * read/update/delete use the branch/rule name in the path segment `{name}`. Now through
6
+ * `@distilled.cloud/forgejo`'s `repository.repoCreateBranchProtection` /
7
+ * `repoGetBranchProtection` / `repoEditBranchProtection` / `repoDeleteBranchProtection`.
8
+ *
9
+ * ⚠️ `BranchProtection.rule_name` IS A REQUIRED STRING IN THE PACKAGE'S SCHEMA β€” the deprecated
10
+ * `branch_name` fallback the hand-rolled client carried is gone; a response missing `rule_name`
11
+ * now fails the operation's own decode instead of this family silently treating it as absent.
7
12
  *
8
13
  * β›” `defaultRemovalPolicy: 'retain'` β€” dropping a protection rule from the stack must not silently
9
14
  * unprotect a branch; opt into `.pipe(RemovalPolicy.destroy())` to DELETE.
@@ -12,7 +17,9 @@
12
17
  */
13
18
  import { Resource } from 'alchemy';
14
19
  import * as Provider from 'alchemy/Provider';
15
- import { type ForgejoRequirements } from './resource.ts';
20
+ import * as repository from '@distilled.cloud/forgejo/repository';
21
+ import * as Effect from 'effect/Effect';
22
+ import { type ForgejoRequirements, type ForgejoSpec } from './resource.ts';
16
23
  export interface BranchProtectionProps {
17
24
  owner: string;
18
25
  repo: string;
@@ -43,5 +50,29 @@ export interface BranchProtectionAttributes {
43
50
  export interface ForgejoBranchProtection extends Resource<'Forgejo.BranchProtection', BranchProtectionProps, BranchProtectionAttributes, never, ForgejoRequirements> {
44
51
  }
45
52
  export declare const ForgejoBranchProtection: import("alchemy").ResourceClass<ForgejoBranchProtection>;
53
+ /** β˜… EXPORTED for direct testing with an explicit fake `Credentials` layer β€” see repository.ts. */
54
+ export declare const spec: ForgejoSpec<BranchProtectionProps, repository.BranchProtection, BranchProtectionAttributes, repository.RepoCreateBranchProtectionError | repository.RepoGetBranchProtectionError | repository.RepoEditBranchProtectionError | repository.RepoDeleteBranchProtectionError>;
55
+ export declare const handlers: {
56
+ list: () => Effect.Effect<never[], never, never>;
57
+ read: ({ olds }: {
58
+ olds: BranchProtectionProps;
59
+ }) => Effect.Effect<BranchProtectionAttributes | undefined, import("@distilled.cloud/cloudflare/Errors").BadGateway | import("@distilled.cloud/cloudflare/Errors").ConfigError | repository.Forbidden | import("@distilled.cloud/forgejo/Errors").ForgejoParseError | import("@distilled.cloud/cloudflare/Errors").GatewayTimeout | import("effect/unstable/http/HttpClientError").HttpClientError | import("@distilled.cloud/cloudflare/Errors").InternalServerError | repository.Locked | repository.NotFound | import("@distilled.cloud/cloudflare/Errors").ServiceUnavailable | import("@distilled.cloud/cloudflare/Errors").TooManyRequests | import("@distilled.cloud/cloudflare/Errors").Unauthorized | import("@distilled.cloud/forgejo/Errors").UnknownForgejoError | repository.UnprocessableEntity, import("effect/unstable/http/HttpClient").HttpClient>;
60
+ diff: ({ news, output }: {
61
+ news: import("alchemy").Input<BranchProtectionProps>;
62
+ output: BranchProtectionAttributes | undefined;
63
+ }) => Effect.Effect<{
64
+ readonly action: 'update';
65
+ } | {
66
+ readonly action: 'noop';
67
+ } | {
68
+ readonly action: 'replace';
69
+ } | undefined, import("@distilled.cloud/cloudflare/Errors").BadGateway | import("@distilled.cloud/cloudflare/Errors").ConfigError | repository.Forbidden | import("@distilled.cloud/forgejo/Errors").ForgejoParseError | import("@distilled.cloud/cloudflare/Errors").GatewayTimeout | import("effect/unstable/http/HttpClientError").HttpClientError | import("@distilled.cloud/cloudflare/Errors").InternalServerError | repository.Locked | repository.NotFound | import("@distilled.cloud/cloudflare/Errors").ServiceUnavailable | import("@distilled.cloud/cloudflare/Errors").TooManyRequests | import("@distilled.cloud/cloudflare/Errors").Unauthorized | import("@distilled.cloud/forgejo/Errors").UnknownForgejoError | repository.UnprocessableEntity, import("effect/unstable/http/HttpClient").HttpClient>;
70
+ reconcile: ({ news }: {
71
+ news: BranchProtectionProps;
72
+ }) => Effect.Effect<BranchProtectionAttributes, import("@distilled.cloud/cloudflare/Errors").BadGateway | import("@distilled.cloud/cloudflare/Errors").ConfigError | repository.Forbidden | import("@distilled.cloud/forgejo/Errors").ForgejoParseError | import("@distilled.cloud/cloudflare/Errors").GatewayTimeout | import("effect/unstable/http/HttpClientError").HttpClientError | import("@distilled.cloud/cloudflare/Errors").InternalServerError | repository.Locked | repository.NotFound | import("@distilled.cloud/cloudflare/Errors").ServiceUnavailable | import("@distilled.cloud/cloudflare/Errors").TooManyRequests | import("@distilled.cloud/cloudflare/Errors").Unauthorized | import("@distilled.cloud/forgejo/Errors").UnknownForgejoError | repository.UnprocessableEntity, import("effect/unstable/http/HttpClient").HttpClient>;
73
+ delete: ({ olds }: {
74
+ olds: BranchProtectionProps;
75
+ }) => Effect.Effect<void, import("@distilled.cloud/cloudflare/Errors").BadGateway | import("@distilled.cloud/cloudflare/Errors").ConfigError | repository.Forbidden | import("@distilled.cloud/forgejo/Errors").ForgejoParseError | import("@distilled.cloud/cloudflare/Errors").GatewayTimeout | import("effect/unstable/http/HttpClientError").HttpClientError | import("@distilled.cloud/cloudflare/Errors").InternalServerError | repository.Locked | repository.NotFound | import("@distilled.cloud/cloudflare/Errors").ServiceUnavailable | import("@distilled.cloud/cloudflare/Errors").TooManyRequests | import("@distilled.cloud/cloudflare/Errors").Unauthorized | import("@distilled.cloud/forgejo/Errors").UnknownForgejoError | repository.UnprocessableEntity, import("effect/unstable/http/HttpClient").HttpClient>;
76
+ };
46
77
  export declare const ForgejoBranchProtectionProvider: () => import("effect/Layer").Layer<Provider.Provider<ForgejoBranchProtection>, never, import("effect/unstable/http/HttpClient").HttpClient>;
47
78
  //# sourceMappingURL=branch-protection.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"branch-protection.d.ts","sourceRoot":"","sources":["../../src/forgejo/branch-protection.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACnC,OAAO,KAAK,QAAQ,MAAM,kBAAkB,CAAC;AAG7C,OAAO,EAAE,KAAK,mBAAmB,EAAmB,MAAM,eAAe,CAAC;AAG1E,MAAM,WAAW,qBAAqB;IACpC,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,kEAAkE;IAClE,UAAU,EAAE,MAAM,CAAC;IACnB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,mBAAmB,CAAC,EAAE,MAAM,EAAE,CAAC;CAChC;AAED,MAAM,WAAW,0BAA0B;IACzC,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,UAAU,EAAE,OAAO,CAAC;IACpB,iBAAiB,EAAE,OAAO,CAAC;IAC3B,oBAAoB,EAAE,OAAO,CAAC;IAC9B,qBAAqB,EAAE,OAAO,CAAC;IAC/B,oBAAoB,EAAE,OAAO,CAAC;IAC9B,aAAa,EAAE,OAAO,CAAC;IACvB,mBAAmB,EAAE,MAAM,EAAE,CAAC;CAC/B;AAED,MAAM,WAAW,uBAAwB,SAAQ,QAAQ,CACvD,0BAA0B,EAC1B,qBAAqB,EACrB,0BAA0B,EAC1B,KAAK,EACL,mBAAmB,CACpB;CAAG;AAEJ,eAAO,MAAM,uBAAuB,0DAGnC,CAAC;AAqCF,eAAO,MAAM,+BAA+B,6IAIzC,CAAC"}
1
+ {"version":3,"file":"branch-protection.d.ts","sourceRoot":"","sources":["../../src/forgejo/branch-protection.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACnC,OAAO,KAAK,QAAQ,MAAM,kBAAkB,CAAC;AAC7C,OAAO,KAAK,UAAU,MAAM,qCAAqC,CAAC;AAClE,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AAExC,OAAO,EAAE,KAAK,mBAAmB,EAAE,KAAK,WAAW,EAAmB,MAAM,eAAe,CAAC;AAG5F,MAAM,WAAW,qBAAqB;IACpC,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,kEAAkE;IAClE,UAAU,EAAE,MAAM,CAAC;IACnB,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,iBAAiB,CAAC,EAAE,OAAO,CAAC;IAC5B,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,mBAAmB,CAAC,EAAE,MAAM,EAAE,CAAC;CAChC;AAED,MAAM,WAAW,0BAA0B;IACzC,KAAK,EAAE,MAAM,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,MAAM,CAAC;IACnB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,UAAU,EAAE,OAAO,CAAC;IACpB,iBAAiB,EAAE,OAAO,CAAC;IAC3B,oBAAoB,EAAE,OAAO,CAAC;IAC9B,qBAAqB,EAAE,OAAO,CAAC;IAC/B,oBAAoB,EAAE,OAAO,CAAC;IAC9B,aAAa,EAAE,OAAO,CAAC;IACvB,mBAAmB,EAAE,MAAM,EAAE,CAAC;CAC/B;AAED,MAAM,WAAW,uBAAwB,SAAQ,QAAQ,CACvD,0BAA0B,EAC1B,qBAAqB,EACrB,0BAA0B,EAC1B,KAAK,EACL,mBAAmB,CACpB;CAAG;AAEJ,eAAO,MAAM,uBAAuB,0DAGnC,CAAC;AAEF,mGAAmG;AACnG,eAAO,MAAM,IAAI,EAAE,WAAW,CAC5B,qBAAqB,EACrB,UAAU,CAAC,gBAAgB,EAC3B,0BAA0B,EACxB,UAAU,CAAC,+BAA+B,GAC1C,UAAU,CAAC,4BAA4B,GACvC,UAAU,CAAC,6BAA6B,GACxC,UAAU,CAAC,+BAA+B,CAiD7C,CAAC;AAEF,eAAO,MAAM,QAAQ;;;;;;;;;;;;;;;;;;;;;CAAwB,CAAC;AAE9C,eAAO,MAAM,+BAA+B,6IAIzC,CAAC"}
@@ -0,0 +1,41 @@
1
+ /**
2
+ * A minimal in-memory Forgejo v1 API for this family's tests β€” an in-memory `fetch` handed to
3
+ * Effect's real `FetchHttpClient` through its `Fetch` reference, mirroring
4
+ * `cloudflare/fake-mesh.ts`. Tests exercise distilled's REAL path assembly, JSON encode/decode
5
+ * and statusβ†’typed-error matching (`404` β†’ `NotFound`, `403` β†’ `Forbidden`, …) β€” nothing about
6
+ * that matching is re-implemented here, only the wire responses a scenario needs.
7
+ *
8
+ * β›” TEST-ONLY, AND NO NETWORK. No provider imports this file. The base URL is RFC 2606
9
+ * `forgejo.example.com`, and the token is a placeholder.
10
+ */
11
+ import { type Credentials } from '@distilled.cloud/forgejo/Credentials';
12
+ import * as Layer from 'effect/Layer';
13
+ export declare const FAKE_BASE = "https://forgejo.example.com";
14
+ export declare const FAKE_TOKEN = "placeholder-forgejo-token";
15
+ export type Seen = {
16
+ readonly method: string;
17
+ readonly path: string;
18
+ };
19
+ /** Forgejo's failure envelope β€” `{ message, url }` β€” matching protocol.ts's `errorEnvelope`. */
20
+ export declare const fakeFailure: (status: number, message: string) => Response;
21
+ /**
22
+ * `route` decides every response; this wrapper only records what was sent and adapts to
23
+ * `globalThis.fetch`'s signature. A test that expects no request at all asserts `seen` is empty.
24
+ */
25
+ export declare const fakeForgejo: (route: (method: string, url: URL) => Response) => {
26
+ fetch: typeof fetch;
27
+ seen: Seen[];
28
+ };
29
+ /**
30
+ * distilled Credentials + the real FetchHttpClient over the fake β€” `ForgejoOpContext`, what every
31
+ * resource file's exported `spec.fetchLive` needs.
32
+ *
33
+ * ⚠️ NOT PAIRED WITH `handlers` (each resource file's `forgejoHandlers(spec)` export). `handlers`
34
+ * bakes in `CredentialsFromEnv`, which resolves `FORGEJO_URL` / `FORGEJO_TOKEN` through Effect's
35
+ * `Config` β€” whose default provider snapshots `process.env` once and never re-reads it (measured
36
+ * 2026-09-23: mutating `process.env` mid-test-run had no effect). So a test cannot point
37
+ * `handlers` at a fake server; it calls `spec.fetchLive`/`spec.attributes` directly instead, with
38
+ * this explicit layer, exercising exactly the same production code without that env seam.
39
+ */
40
+ export declare const fakeForgejoLayer: (fetchFn: typeof globalThis.fetch, creds?: Layer.Layer<Credentials>) => Layer.Layer<Credentials | import("effect/unstable/http/HttpClient").HttpClient, never, never>;
41
+ //# sourceMappingURL=fake-forgejo.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fake-forgejo.d.ts","sourceRoot":"","sources":["../../src/forgejo/fake-forgejo.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EAAE,KAAK,WAAW,EAAe,MAAM,sCAAsC,CAAC;AACrF,OAAO,KAAK,KAAK,MAAM,cAAc,CAAC;AAGtC,eAAO,MAAM,SAAS,gCAAgC,CAAC;AACvD,eAAO,MAAM,UAAU,8BAA8B,CAAC;AAEtD,MAAM,MAAM,IAAI,GAAG;IAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAEtE,gGAAgG;AAChG,eAAO,MAAM,WAAW,WAAY,MAAM,WAAW,MAAM,aACgC,CAAC;AAE5F;;;GAGG;AACH,eAAO,MAAM,WAAW,UAAW,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,KAAK,QAAQ;;;CAUxE,CAAC;AAEF;;;;;;;;;;GAUG;AACH,eAAO,MAAM,gBAAgB,YAClB,OAAO,UAAU,CAAC,KAAK,UACzB,KAAK,CAAC,KAAK,CAAC,WAAW,CAAC,kGAC+D,CAAC"}