@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
package/README.md CHANGED
@@ -4,8 +4,9 @@ Custom [Alchemy](https://alchemy.run) providers for gaps the vendor SDK leaves.
4
4
 
5
5
  ```sh
6
6
  bun add @homeflare/alchemy alchemy@2.0.0-beta.79 effect@4.0.0-rc.115 \
7
- @effect/platform-node@4.0.0-rc.115 cloudflare@4.5.0 mime@4.1.0 \
8
- @distilled.cloud/cloudflare@1.0.0-rc.12 @effect/sql-pg@4.0.0-rc.115
7
+ @effect/platform-node@4.0.0-rc.115 mime@4.1.0 \
8
+ @distilled.cloud/cloudflare@1.0.0-rc.12 @distilled.cloud/forgejo@1.0.0-rc.12 \
9
+ @effect/sql-pg@4.0.0-rc.115
9
10
  ```
10
11
 
11
12
  ⛔ **Every one of those is required, and you also need an `overrides` block** — see
@@ -0,0 +1,53 @@
1
+ /**
2
+ * A fake Cloudflare R2 bucket-lock API for r2-bucket-lock.test.ts — an in-memory `fetch` handed to
3
+ * Effect's real FetchHttpClient through its `Fetch` reference, so the tests exercise distilled's
4
+ * real path assembly, header encoding, envelope decoding and error matching. Same idiom as
5
+ * fake-mesh.ts, for the route `GET`/`PUT /accounts/<FAKE_ACCOUNT>/r2/buckets/<name>/lock`.
6
+ *
7
+ * ⛔ TEST-ONLY, AND NO NETWORK. No provider imports this file. The base URL is RFC 2606
8
+ * `api.example.com`, and any request outside that route throws, so a mis-wired test fails
9
+ * instead of reaching a real API.
10
+ * ★ `existingBuckets` MODELS THE VENDOR'S OWN DISTINCTION. A bucket not in the set answers
11
+ * `NoSuchBucket` (code 10006) on both verbs, matching the 404 the doc comment in
12
+ * r2-bucket-lock.ts measured. A bucket in the set with no rules answers `{}` on GET, matching
13
+ * "never locked" — both collapse to "absent" in `readLock`, but the fake keeps them distinct so
14
+ * a test can put a bucket in either state on purpose.
15
+ * ★ `onRoute` OVERRIDES ONE REQUEST. A test uses it to answer a request with an arbitrary
16
+ * `Response` — a non-404 status carrying code 10006 (proves matching is by code, not status), or
17
+ * a different tagged error entirely (`InvalidRoute`, code 7003) to prove it does NOT fall into
18
+ * `readLock`'s `NoSuchBucket` catch.
19
+ */
20
+ import { type Credentials } from '@distilled.cloud/cloudflare/Credentials';
21
+ import * as Layer from 'effect/Layer';
22
+ import type { R2LockRule } from './lock-rules.ts';
23
+ export declare const FAKE_BASE = "https://api.example.com/client/v4";
24
+ export declare const FAKE_ACCOUNT = "00000000000000000000000000000002";
25
+ /** ⚠️ A placeholder, not a credential. */
26
+ export declare const FAKE_API_TOKEN = "placeholder-api-token";
27
+ export type Seen = {
28
+ readonly method: string;
29
+ readonly path: string;
30
+ readonly headers: Record<string, string>;
31
+ readonly body: unknown;
32
+ };
33
+ export declare const fakeFailure: (status: number, code: number, message: string) => Response;
34
+ /** The response `NoSuchBucket` matches: code 10006, conventionally at 404. */
35
+ export declare const noSuchBucket: (status?: number) => Response;
36
+ export type FakeOptions = {
37
+ /** Buckets the account has. Any other name is `NoSuchBucket` on both GET and PUT. */
38
+ readonly existingBuckets?: readonly string[];
39
+ /** Override one request's response. `undefined` falls through to the default route. */
40
+ readonly onRoute?: (method: string, bucketName: string) => Response | undefined;
41
+ };
42
+ export declare const fakeR2Lock: (options?: FakeOptions) => {
43
+ existing: Set<string>;
44
+ fetch: typeof fetch;
45
+ locks: Map<string, readonly R2LockRule[]>;
46
+ seen: Seen[];
47
+ };
48
+ export type FakeR2Lock = ReturnType<typeof fakeR2Lock>;
49
+ /** distilled Credentials + the real FetchHttpClient over the fake — `CloudflareOpContext`. */
50
+ export declare const fakeR2LockLayer: (fake: FakeR2Lock, credentials?: Layer.Layer<Credentials>) => Layer.Layer<Credentials | import("effect/unstable/http/HttpClient").HttpClient, never, never>;
51
+ /** The writes the fake saw, in order: `['PUT', 'GET', …]`. */
52
+ export declare const writes: (fake: FakeR2Lock) => string[];
53
+ //# sourceMappingURL=fake-r2-lock.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fake-r2-lock.d.ts","sourceRoot":"","sources":["../../src/cloudflare/fake-r2-lock.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,OAAO,EAAE,KAAK,WAAW,EAAgB,MAAM,yCAAyC,CAAC;AACzF,OAAO,KAAK,KAAK,MAAM,cAAc,CAAC;AAEtC,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAElD,eAAO,MAAM,SAAS,sCAAsC,CAAC;AAC7D,eAAO,MAAM,YAAY,qCAAqC,CAAC;AAC/D,0CAA0C;AAC1C,eAAO,MAAM,cAAc,0BAA0B,CAAC;AAEtD,MAAM,MAAM,IAAI,GAAG;IACjB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACzC,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;CACxB,CAAC;AAIF,eAAO,MAAM,WAAW,WAAY,MAAM,QAAQ,MAAM,WAAW,MAAM,aAItE,CAAC;AAEJ,8EAA8E;AAC9E,eAAO,MAAM,YAAY,+BAC2C,CAAC;AAErE,MAAM,MAAM,WAAW,GAAG;IACxB,qFAAqF;IACrF,QAAQ,CAAC,eAAe,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC7C,uFAAuF;IACvF,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,KAAK,QAAQ,GAAG,SAAS,CAAC;CACjF,CAAC;AAEF,eAAO,MAAM,UAAU,aAAa,WAAW;;;;;CA4C9C,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG,UAAU,CAAC,OAAO,UAAU,CAAC,CAAC;AAEvD,8FAA8F;AAC9F,eAAO,MAAM,eAAe,SACpB,UAAU,gBACH,KAAK,CAAC,KAAK,CAAC,WAAW,CAAC,kGASpC,CAAC;AAEJ,8DAA8D;AAC9D,eAAO,MAAM,MAAM,SAAU,UAAU,aAC2B,CAAC"}
@@ -1,11 +1,10 @@
1
1
  /**
2
2
  * Cloudflare providers for Alchemy — the gaps the vendor SDK leaves.
3
3
  *
4
- * ★ Built on vendor SDKs, not hand-rolled HTTP. `R2BucketLock` uses the official `cloudflare`
5
- * SDK, which already has `r2.buckets.locks.update/get` over the documented endpoints.
6
- * `MeshNode` uses `@distilled.cloud/cloudflare`, the SDK Alchemy's own Cloudflare providers
7
- * use, because `cloudflare@4.5.0` cannot create an HA node (mesh-node-api.ts). Either way
8
- * there is no path string this package invents.
4
+ * ★ Built on vendor SDKs, not hand-rolled HTTP. Both `R2BucketLock` and `MeshNode` call
5
+ * `@distilled.cloud/cloudflare`, the SDK Alchemy's own Cloudflare providers use — R2BucketLock
6
+ * over `/r2` (`getBucketLock`/`putBucketLock`), MeshNode over `/zero-trust`, because
7
+ * `cloudflare@4.5.0` cannot create an HA node (mesh-node-api.ts). Neither invents a path string.
9
8
  */
10
9
  export type { R2LockRule } from './lock-rules.ts';
11
10
  export { R2BucketLock, type R2BucketLockProps } from './r2-bucket-lock.ts';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cloudflare/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,YAAY,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAClD,OAAO,EAAE,YAAY,EAAE,KAAK,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAC3E,OAAO,EAAE,QAAQ,EAAE,gBAAgB,EAAE,KAAK,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AACnF,YAAY,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAC7E,OAAO,EAAE,aAAa,EAAE,KAAK,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACxE,OAAO,EAAE,kBAAkB,EAAE,KAAK,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAC5E,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EACL,YAAY,EACZ,WAAW,EACX,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,GACtB,MAAM,cAAc,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cloudflare/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,YAAY,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAClD,OAAO,EAAE,YAAY,EAAE,KAAK,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AAC3E,OAAO,EAAE,QAAQ,EAAE,gBAAgB,EAAE,KAAK,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AACnF,YAAY,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAC7E,OAAO,EAAE,aAAa,EAAE,KAAK,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACxE,OAAO,EAAE,kBAAkB,EAAE,KAAK,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAC5E,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EACL,YAAY,EACZ,WAAW,EACX,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,GACtB,MAAM,cAAc,CAAC"}
@@ -2,32 +2,11 @@
2
2
  import { Unowned } from "alchemy/AdoptPolicy";
3
3
  import * as Provider from "alchemy/Provider";
4
4
  import { Resource } from "alchemy/Resource";
5
- import { NotFoundError } from "cloudflare";
6
- import * as Effect2 from "effect/Effect";
7
-
8
- // src/cloudflare/client.ts
9
- import Cloudflare from "cloudflare";
10
- import * as Config from "effect/Config";
5
+ import { Credentials } from "@distilled.cloud/cloudflare/Credentials";
6
+ import * as r2 from "@distilled.cloud/cloudflare/r2";
11
7
  import * as Context from "effect/Context";
12
8
  import * as Effect from "effect/Effect";
13
- import * as Layer from "effect/Layer";
14
- import * as Redacted2 from "effect/Redacted";
15
-
16
- class CloudflareApi extends Context.Service()("homeflare/CloudflareApi") {
17
- }
18
- var makeClient = (options) => new Cloudflare({
19
- apiToken: options.apiToken,
20
- ...options.fetch === undefined ? {} : { fetch: options.fetch }
21
- });
22
- var MISSING_TOKEN = "CLOUDFLARE_API_TOKEN is not set. Mint a short-lived, scoped token and export it before " + "role named in estate.stack.ts; running `alchemy` directly skips that and there is no default.";
23
- var EMPTY_TOKEN = "CLOUDFLARE_API_TOKEN is set but EMPTY. That is a denied or missing OpenBao grant on the " + "stack’s role, not a rotated token — re-mint it rather than rotating anything at Cloudflare.";
24
- var CloudflareApiLive = Layer.effect(CloudflareApi, Effect.gen(function* () {
25
- const token = yield* Config.Redacted("CLOUDFLARE_API_TOKEN").pipe(Effect.catchTag("ConfigError", () => Effect.die(new Error(MISSING_TOKEN))));
26
- const value2 = Redacted2.value(token);
27
- if (value2.length === 0)
28
- return yield* Effect.die(new Error(EMPTY_TOKEN));
29
- return makeClient({ apiToken: value2 });
30
- }));
9
+ import * as HttpClient from "effect/unstable/http/HttpClient";
31
10
 
32
11
  // src/cloudflare/lock-rules.ts
33
12
  var jurisdictionOf = (props) => props.jurisdiction ?? "default";
@@ -60,15 +39,9 @@ var toBody = (rules) => ({
60
39
  var R2BucketLock = Resource("Cloudflare.R2BucketLock", {
61
40
  defaultRemovalPolicy: "retain"
62
41
  });
63
- var readLock = (client, props, written) => Effect2.gen(function* () {
42
+ var readLock = (props, written) => Effect.gen(function* () {
64
43
  const jurisdiction = jurisdictionOf(props);
65
- const live = yield* Effect2.tryPromise({
66
- try: () => client.r2.buckets.locks.get(props.bucketName, {
67
- account_id: props.accountId,
68
- jurisdiction
69
- }),
70
- catch: (cause) => cause
71
- }).pipe(Effect2.catchIf((cause) => cause instanceof NotFoundError, () => Effect2.succeed(undefined)), Effect2.orDie);
44
+ const live = yield* r2.getBucketLock({ accountId: props.accountId, bucketName: props.bucketName, jurisdiction }).pipe(Effect.catchTag("NoSuchBucket", () => Effect.succeed(undefined)), Effect.orDie);
72
45
  if (live === undefined)
73
46
  return;
74
47
  const rules = live.rules ?? [];
@@ -81,53 +54,56 @@ var readLock = (client, props, written) => Effect2.gen(function* () {
81
54
  };
82
55
  return written ? attributes : Unowned(attributes);
83
56
  });
84
- var reconcileLock = (client, news, output) => Effect2.gen(function* () {
57
+ var reconcileLock = (news, output) => Effect.gen(function* () {
85
58
  const jurisdiction = jurisdictionOf(news);
86
59
  if (output !== undefined && rulesEqual(output.rules, news.rules))
87
60
  return output;
88
- yield* Effect2.promise(() => client.r2.buckets.locks.update(news.bucketName, {
89
- account_id: news.accountId,
61
+ yield* r2.putBucketLock({
62
+ accountId: news.accountId,
63
+ bucketName: news.bucketName,
90
64
  jurisdiction,
91
65
  ...toBody(news.rules)
92
- }));
66
+ }).pipe(Effect.orDie);
93
67
  return { bucketName: news.bucketName, jurisdiction, rules: news.rules };
94
68
  });
95
- var deleteLock = (client, output, accountId) => Effect2.gen(function* () {
96
- yield* Effect2.promise(() => client.r2.buckets.locks.update(output.bucketName, {
97
- account_id: accountId,
69
+ var deleteLock = (output, accountId) => Effect.gen(function* () {
70
+ yield* r2.putBucketLock({
71
+ accountId,
72
+ bucketName: output.bucketName,
98
73
  jurisdiction: output.jurisdiction,
99
74
  rules: []
100
- }));
75
+ }).pipe(Effect.orDie);
101
76
  });
102
- var R2BucketLockProvider = () => Provider.effect(R2BucketLock, Effect2.gen(function* () {
103
- const client = yield* CloudflareApi;
77
+ var R2BucketLockProvider = () => Provider.effect(R2BucketLock, Effect.gen(function* () {
78
+ const services = Context.make(Credentials, yield* Credentials).pipe(Context.add(HttpClient.HttpClient, yield* HttpClient.HttpClient));
79
+ const run = (effect2) => Effect.provideContext(effect2, services);
104
80
  return R2BucketLock.Provider.of({
105
- list: () => Effect2.succeed([]),
81
+ list: () => Effect.succeed([]),
106
82
  nuke: { singleton: true },
107
- read: Effect2.fn(function* ({ olds, output }) {
83
+ read: Effect.fn(function* ({ olds, output }) {
108
84
  const props = olds ?? undefined;
109
85
  if (props === undefined)
110
86
  return;
111
- return yield* readLock(client, props, output !== undefined);
87
+ return yield* run(readLock(props, output !== undefined));
112
88
  }),
113
- reconcile: Effect2.fn(({ news, output }) => reconcileLock(client, news, output)),
114
- delete: Effect2.fn(({ olds, output }) => deleteLock(client, output, olds.accountId))
89
+ reconcile: Effect.fn(({ news, output }) => run(reconcileLock(news, output))),
90
+ delete: Effect.fn(({ olds, output }) => run(deleteLock(output, olds.accountId)))
115
91
  });
116
92
  }));
117
93
  // src/cloudflare/mesh-node.ts
118
- import { Credentials } from "@distilled.cloud/cloudflare/Credentials";
119
- import * as Cloudflare2 from "alchemy/Cloudflare";
94
+ import { Credentials as Credentials2 } from "@distilled.cloud/cloudflare/Credentials";
95
+ import * as Cloudflare from "alchemy/Cloudflare";
120
96
  import { isResolved } from "alchemy/Diff";
121
97
  import * as Provider2 from "alchemy/Provider";
122
98
  import { Resource as Resource2 } from "alchemy/Resource";
123
99
  import * as Context2 from "effect/Context";
124
- import * as Effect5 from "effect/Effect";
125
- import * as HttpClient from "effect/unstable/http/HttpClient";
100
+ import * as Effect4 from "effect/Effect";
101
+ import * as HttpClient3 from "effect/unstable/http/HttpClient";
126
102
 
127
103
  // src/cloudflare/mesh-node-api.ts
128
104
  import * as zeroTrust from "@distilled.cloud/cloudflare/zero-trust";
129
105
  import * as Data from "effect/Data";
130
- import * as Effect3 from "effect/Effect";
106
+ import * as Effect2 from "effect/Effect";
131
107
  import * as Stream from "effect/Stream";
132
108
 
133
109
  class MeshNodeError extends Data.TaggedError("MeshNodeError") {
@@ -141,22 +117,22 @@ var observe = (raw) => {
141
117
  const status = typeof raw.status === "string" && STATUSES.includes(raw.status) ? raw.status : undefined;
142
118
  return { id: raw.id, name: raw.name ?? "", status };
143
119
  };
144
- var getNode = (accountId, nodeId) => zeroTrust.getTunnelWarpConnector({ accountId, tunnelId: nodeId }).pipe(Effect3.map(observe), Effect3.catchTag("TunnelNotFound", () => Effect3.succeed(undefined)));
145
- var findNodeByName = (accountId, name) => zeroTrust.listTunnelWarpConnectors.items({ accountId, name, isDeleted: false }).pipe(Stream.map(observe), Stream.filter((node) => node !== undefined && node.name === name), Stream.runCollect, Effect3.flatMap((chunk) => {
120
+ var getNode = (accountId, nodeId) => zeroTrust.getTunnelWarpConnector({ accountId, tunnelId: nodeId }).pipe(Effect2.map(observe), Effect2.catchTag("TunnelNotFound", () => Effect2.succeed(undefined)));
121
+ var findNodeByName = (accountId, name) => zeroTrust.listTunnelWarpConnectors.items({ accountId, name, isDeleted: false }).pipe(Stream.map(observe), Stream.filter((node) => node !== undefined && node.name === name), Stream.runCollect, Effect2.flatMap((chunk) => {
146
122
  const matches = Array.from(chunk);
147
123
  if (matches.length <= 1)
148
- return Effect3.succeed(matches[0]);
124
+ return Effect2.succeed(matches[0]);
149
125
  const ids = matches.map((node) => node.id).join(", ");
150
- return Effect3.fail(new MeshNodeError({
126
+ return Effect2.fail(new MeshNodeError({
151
127
  message: `Mesh node name "${name}" matches ${matches.length} live nodes (${ids}). Names are unique per account, so refusing to guess which one is meant.`
152
128
  }));
153
129
  }));
154
- var createNode = (accountId, name, ha) => zeroTrust.createTunnelWarpConnector({ accountId, name, ha }).pipe(Effect3.flatMap((raw) => {
130
+ var createNode = (accountId, name, ha) => zeroTrust.createTunnelWarpConnector({ accountId, name, ha }).pipe(Effect2.flatMap((raw) => {
155
131
  const node = observe(raw);
156
- return node === undefined ? Effect3.fail(new MeshNodeError({ message: `Creating Mesh node "${name}" returned no id.` })) : Effect3.succeed(node);
132
+ return node === undefined ? Effect2.fail(new MeshNodeError({ message: `Creating Mesh node "${name}" returned no id.` })) : Effect2.succeed(node);
157
133
  }));
158
- var renameNode = (accountId, nodeId, name) => zeroTrust.patchTunnelWarpConnector({ accountId, tunnelId: nodeId, name }).pipe(Effect3.map((raw) => observe(raw) ?? { id: nodeId, name, status: undefined }));
159
- var deleteNode = (accountId, nodeId) => zeroTrust.deleteTunnelWarpConnector({ accountId, tunnelId: nodeId }).pipe(Effect3.asVoid, Effect3.catchTag("TunnelNotFound", () => Effect3.void));
134
+ var renameNode = (accountId, nodeId, name) => zeroTrust.patchTunnelWarpConnector({ accountId, tunnelId: nodeId, name }).pipe(Effect2.map((raw) => observe(raw) ?? { id: nodeId, name, status: undefined }));
135
+ var deleteNode = (accountId, nodeId) => zeroTrust.deleteTunnelWarpConnector({ accountId, tunnelId: nodeId }).pipe(Effect2.asVoid, Effect2.catchTag("TunnelNotFound", () => Effect2.void));
160
136
 
161
137
  // src/cloudflare/mesh-node-form.ts
162
138
  var validateMeshNode = (props) => {
@@ -193,7 +169,7 @@ var diffMeshNode = (news, output, accountId) => {
193
169
 
194
170
  // src/cloudflare/mesh-node-lifecycle.ts
195
171
  import { Unowned as Unowned2 } from "alchemy/AdoptPolicy";
196
- import * as Effect4 from "effect/Effect";
172
+ import * as Effect3 from "effect/Effect";
197
173
  var attributesOf = (node, accountId, ha) => ({
198
174
  id: node.id,
199
175
  accountId,
@@ -201,7 +177,7 @@ var attributesOf = (node, accountId, ha) => ({
201
177
  status: node.status,
202
178
  ha
203
179
  });
204
- var readMeshNode = (accountId, olds, output) => Effect4.gen(function* () {
180
+ var readMeshNode = (accountId, olds, output) => Effect3.gen(function* () {
205
181
  const account = output?.accountId ?? accountId;
206
182
  if (output !== undefined) {
207
183
  const node = yield* getNode(account, output.id);
@@ -222,32 +198,32 @@ var REOWN = "drop this resource's state row (alchemy state rm, which leaves the
222
198
  var oldNodeHoldsName = (name, holder) => new MeshNodeError({
223
199
  message: `Mesh node "${name}" already exists (${holder}) and is not the node this resource tracks, ` + "so it is left alone: names are unique per account. If this deploy changed `ha` and kept " + "the name, it is this resource's old node, kept by MeshNode's default " + "RemovalPolicy.retain because HA cannot change in place. To replace it (a new id, token and " + "Mesh IP per replica), deploy once with .pipe(RemovalPolicy.destroy()), which deletes it " + `first, or delete it by hand. To keep it, reverting \`ha\` is not enough: ${REOWN}, with ` + "the original `ha`. Otherwise it is another node: do not delete it. Choose another name, " + `or, if it is meant to be this one, check its HA badge, then ${REOWN}.`
224
200
  });
225
- var raced = (accountId, name) => findNodeByName(accountId, name).pipe(Effect4.flatMap((holder) => Effect4.fail(holder === undefined ? nameTaken(name, "not a live Mesh node: another tunnel type, or a just-deleted node") : new MeshNodeError({
201
+ var raced = (accountId, name) => findNodeByName(accountId, name).pipe(Effect3.flatMap((holder) => Effect3.fail(holder === undefined ? nameTaken(name, "not a live Mesh node: another tunnel type, or a just-deleted node") : new MeshNodeError({
226
202
  message: `Mesh node "${name}" (${holder.id}) appeared between this deploy's lookup and its ` + "create. The likeliest cause is this deploy itself: the SDK retries a create whose " + "response was lost. If so, check its HA badge matches the declared `ha`, then " + "adopt it: adopt(true) after a first create, but after a replace " + `${REOWN}, because adopt(true) alone does not act on a replace's row. ` + "Otherwise another creator raced this one."
227
203
  }))));
228
- var createFresh = (accountId, news) => Effect4.gen(function* () {
204
+ var createFresh = (accountId, news) => Effect3.gen(function* () {
229
205
  const existing = yield* findNodeByName(accountId, news.name);
230
206
  if (existing !== undefined)
231
- return yield* Effect4.fail(oldNodeHoldsName(news.name, existing.id));
232
- const node = yield* createNode(accountId, news.name, news.ha).pipe(Effect4.catchTag("DuplicateTunnelName", () => raced(accountId, news.name)));
207
+ return yield* Effect3.fail(oldNodeHoldsName(news.name, existing.id));
208
+ const node = yield* createNode(accountId, news.name, news.ha).pipe(Effect3.catchTag("DuplicateTunnelName", () => raced(accountId, news.name)));
233
209
  return attributesOf(node, accountId, news.ha);
234
210
  });
235
- var reconcileMeshNode = (accountId, news, output) => Effect4.gen(function* () {
211
+ var reconcileMeshNode = (accountId, news, output) => Effect3.gen(function* () {
236
212
  const invalid = validateMeshNode(news);
237
213
  if (invalid !== undefined)
238
- return yield* Effect4.fail(invalid);
214
+ return yield* Effect3.fail(invalid);
239
215
  const owned = output !== undefined && output.accountId === accountId ? output : undefined;
240
216
  const live = owned === undefined ? undefined : yield* getNode(accountId, owned.id);
241
217
  if (owned === undefined || live === undefined)
242
218
  return yield* createFresh(accountId, news);
243
219
  if (owned.ha !== news.ha) {
244
- return yield* Effect4.fail(new MeshNodeError({
220
+ return yield* Effect3.fail(new MeshNodeError({
245
221
  message: `MeshNode "${news.name}": \`ha\` is create-only (stored ${owned.ha}, declared ${news.ha}). Plan again so the change is a replace.`
246
222
  }));
247
223
  }
248
224
  if (live.name === news.name)
249
225
  return attributesOf(live, accountId, owned.ha);
250
- const renamed = yield* renameNode(accountId, live.id, news.name).pipe(Effect4.catchTag("DuplicateTunnelName", () => Effect4.fail(nameTaken(news.name, "held by another tunnel"))));
226
+ const renamed = yield* renameNode(accountId, live.id, news.name).pipe(Effect3.catchTag("DuplicateTunnelName", () => Effect3.fail(nameTaken(news.name, "held by another tunnel"))));
251
227
  return attributesOf(renamed, accountId, owned.ha);
252
228
  });
253
229
  var deleteMeshNode = (output) => deleteNode(output.accountId, output.id);
@@ -256,80 +232,80 @@ var deleteMeshNode = (output) => deleteNode(output.accountId, output.id);
256
232
  var MeshNode = Resource2("Cloudflare.MeshNode", {
257
233
  defaultRemovalPolicy: "retain"
258
234
  });
259
- var accountId = Effect5.gen(function* () {
260
- const resolved = yield* yield* Cloudflare2.CloudflareEnvironment;
235
+ var accountId = Effect4.gen(function* () {
236
+ const resolved = yield* yield* Cloudflare.CloudflareEnvironment;
261
237
  return resolved.accountId;
262
238
  });
263
- var MeshNodeProvider = () => Provider2.effect(MeshNode, Effect5.gen(function* () {
264
- const services = Context2.make(Cloudflare2.CloudflareEnvironment, yield* Cloudflare2.CloudflareEnvironment).pipe(Context2.add(Credentials, yield* Credentials), Context2.add(HttpClient.HttpClient, yield* HttpClient.HttpClient));
265
- const run = (effect4) => Effect5.provideContext(effect4, services);
239
+ var MeshNodeProvider = () => Provider2.effect(MeshNode, Effect4.gen(function* () {
240
+ const services = Context2.make(Cloudflare.CloudflareEnvironment, yield* Cloudflare.CloudflareEnvironment).pipe(Context2.add(Credentials2, yield* Credentials2), Context2.add(HttpClient3.HttpClient, yield* HttpClient3.HttpClient));
241
+ const run = (effect3) => Effect4.provideContext(effect3, services);
266
242
  return MeshNode.Provider.of({
267
243
  stables: ["id", "accountId", "ha"],
268
- list: () => Effect5.succeed([]),
244
+ list: () => Effect4.succeed([]),
269
245
  nuke: { skip: true },
270
- diff: ({ news, output }) => run(Effect5.gen(function* () {
246
+ diff: ({ news, output }) => run(Effect4.gen(function* () {
271
247
  const invalid = isResolved(news) ? validateMeshNode(news) : undefined;
272
248
  if (invalid !== undefined)
273
- return yield* Effect5.fail(invalid);
249
+ return yield* Effect4.fail(invalid);
274
250
  return diffMeshNode(news, output, yield* accountId);
275
251
  })),
276
- read: ({ olds, output }) => run(Effect5.flatMap(accountId, (account) => readMeshNode(account, olds, output))),
277
- reconcile: ({ news, output }) => run(Effect5.flatMap(accountId, (account) => reconcileMeshNode(account, news, output))),
252
+ read: ({ olds, output }) => run(Effect4.flatMap(accountId, (account) => readMeshNode(account, olds, output))),
253
+ reconcile: ({ news, output }) => run(Effect4.flatMap(accountId, (account) => reconcileMeshNode(account, news, output))),
278
254
  delete: ({ output }) => run(deleteMeshNode(output))
279
255
  });
280
256
  }));
281
257
  // src/cloudflare/mesh-node-token.ts
282
- import { Credentials as Credentials2 } from "@distilled.cloud/cloudflare/Credentials";
258
+ import { Credentials as Credentials3 } from "@distilled.cloud/cloudflare/Credentials";
283
259
  import * as zeroTrust2 from "@distilled.cloud/cloudflare/zero-trust";
284
- import * as Effect6 from "effect/Effect";
285
- import * as Redacted3 from "effect/Redacted";
286
- var refuseUnsafe = Effect6.gen(function* () {
260
+ import * as Effect5 from "effect/Effect";
261
+ import * as Redacted from "effect/Redacted";
262
+ var refuseUnsafe = Effect5.gen(function* () {
287
263
  if ((globalThis.process?.env?.["DISTILLED_DEBUG_HTTP"] ?? "") !== "") {
288
- return yield* Effect6.fail(new MeshNodeError({
264
+ return yield* Effect5.fail(new MeshNodeError({
289
265
  message: "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."
290
266
  }));
291
267
  }
292
- const credentials = yield* yield* Credentials2;
268
+ const credentials = yield* yield* Credentials3;
293
269
  if (credentials.type === "apiKey") {
294
- return yield* Effect6.fail(new MeshNodeError({
270
+ return yield* Effect5.fail(new MeshNodeError({
295
271
  message: '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.'
296
272
  }));
297
273
  }
298
- if (credentials.type === "apiToken" && Redacted3.value(credentials.apiToken).length === 0) {
299
- return yield* Effect6.fail(new MeshNodeError({
274
+ if (credentials.type === "apiToken" && Redacted.value(credentials.apiToken).length === 0) {
275
+ return yield* Effect5.fail(new MeshNodeError({
300
276
  message: "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."
301
277
  }));
302
278
  }
303
279
  });
304
280
  var resolveId = (ref) => {
305
281
  if (ref.id !== undefined)
306
- return Effect6.succeed(ref.id);
282
+ return Effect5.succeed(ref.id);
307
283
  const name = ref.name;
308
- return Effect6.flatMap(findNodeByName(ref.accountId, name), (node) => node === undefined ? Effect6.fail(new MeshNodeError({ message: `No live Mesh node named "${name}".` })) : Effect6.succeed(node.id));
284
+ return Effect5.flatMap(findNodeByName(ref.accountId, name), (node) => node === undefined ? Effect5.fail(new MeshNodeError({ message: `No live Mesh node named "${name}".` })) : Effect5.succeed(node.id));
309
285
  };
310
- var fetchMeshNodeToken = (ref) => Effect6.gen(function* () {
286
+ var fetchMeshNodeToken = (ref) => Effect5.gen(function* () {
311
287
  yield* refuseUnsafe;
312
288
  const nodeId = yield* resolveId(ref);
313
- const token = yield* zeroTrust2.getTunnelWarpConnectorToken({ accountId: ref.accountId, tunnelId: nodeId }).pipe(Effect6.catchTag("TunnelNotFound", () => Effect6.fail(new MeshNodeError({
289
+ const token = yield* zeroTrust2.getTunnelWarpConnectorToken({ accountId: ref.accountId, tunnelId: nodeId }).pipe(Effect5.catchTag("TunnelNotFound", () => Effect5.fail(new MeshNodeError({
314
290
  message: `Mesh node ${nodeId} does not exist in account ${ref.accountId}.`
315
- }))), Effect6.catchTag("Forbidden", () => Effect6.fail(new MeshNodeError({
291
+ }))), Effect5.catchTag("Forbidden", () => Effect5.fail(new MeshNodeError({
316
292
  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}.`
317
293
  }))));
318
294
  if (typeof token !== "string" || token.length === 0) {
319
- return yield* Effect6.fail(new MeshNodeError({ message: `Mesh node ${nodeId} returned an empty token.` }));
295
+ return yield* Effect5.fail(new MeshNodeError({ message: `Mesh node ${nodeId} returned an empty token.` }));
320
296
  }
321
- return Redacted3.make(token);
297
+ return Redacted.make(token);
322
298
  });
323
299
  // src/cloudflare/providers.ts
324
- import * as Cloudflare3 from "alchemy/Cloudflare";
300
+ import * as Cloudflare2 from "alchemy/Cloudflare";
325
301
  import * as Provider3 from "alchemy/Provider";
326
- import * as Layer2 from "effect/Layer";
302
+ import * as Layer from "effect/Layer";
327
303
  class Providers extends Provider3.ProviderCollection()("HomeflareCloudflare") {
328
304
  }
329
- var providers = () => Layer2.effect(Providers, Provider3.collection([R2BucketLock, MeshNode])).pipe(Layer2.provide(Layer2.mergeAll(R2BucketLockProvider(), MeshNodeProvider().pipe(Layer2.provide(Cloudflare3.CloudflareApiLive()), Layer2.orDie))), Layer2.provideMerge(CloudflareApiLive));
305
+ var providers = () => Layer.effect(Providers, Provider3.collection([R2BucketLock, MeshNode])).pipe(Layer.provide(Layer.mergeAll(R2BucketLockProvider().pipe(Layer.provide(Cloudflare2.CloudflareApiLive()), Layer.orDie), MeshNodeProvider().pipe(Layer.provide(Cloudflare2.CloudflareApiLive()), Layer.orDie))));
330
306
  // src/cloudflare/website.ts
331
307
  import * as AdoptPolicy from "alchemy/AdoptPolicy";
332
- import * as Cloudflare4 from "alchemy/Cloudflare";
308
+ import * as Cloudflare3 from "alchemy/Cloudflare";
333
309
  import * as RemovalPolicy from "alchemy/RemovalPolicy";
334
310
  var ASTRO_FLAGS = ["nodejs_compat", "disable_nodejs_process_v2"];
335
311
  function astroWebsite(name, props) {
@@ -340,11 +316,11 @@ function astroWebsite(name, props) {
340
316
  sessionKVBindingName: props.sessionKVBindingName ?? false,
341
317
  compatibility: { ...props.compatibility, flags: [...flags] }
342
318
  };
343
- return Cloudflare4.Website.Astro(name, merged).pipe(AdoptPolicy.adopt(true), RemovalPolicy.retain());
319
+ return Cloudflare3.Website.Astro(name, merged).pipe(AdoptPolicy.adopt(true), RemovalPolicy.retain());
344
320
  }
345
321
  function viteWebsite(name, props) {
346
322
  const merged = { ...props, name: props?.name ?? name };
347
- return Cloudflare4.Website.Vite(name, merged).pipe(AdoptPolicy.adopt(true), RemovalPolicy.retain());
323
+ return Cloudflare3.Website.Vite(name, merged).pipe(AdoptPolicy.adopt(true), RemovalPolicy.retain());
348
324
  }
349
325
  export {
350
326
  MeshNode,
@@ -357,5 +333,5 @@ export {
357
333
  viteWebsite
358
334
  };
359
335
 
360
- //# debugId=8A155CE09F54FFB164756E2164756E21
336
+ //# debugId=AB671D282C098EA364756E2164756E21
361
337
  //# sourceMappingURL=index.js.map