@homeflare/alchemy 0.27.1 → 0.27.3
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.
- package/README.md +3 -2
- package/dist/cloudflare/fake-r2-lock.d.ts +53 -0
- package/dist/cloudflare/fake-r2-lock.d.ts.map +1 -0
- package/dist/cloudflare/index.d.ts +4 -5
- package/dist/cloudflare/index.d.ts.map +1 -1
- package/dist/cloudflare/index.js +79 -103
- package/dist/cloudflare/index.js.map +6 -7
- package/dist/cloudflare/mesh-node-api.d.ts +3 -2
- package/dist/cloudflare/mesh-node-api.d.ts.map +1 -1
- package/dist/cloudflare/providers.d.ts +1 -1
- package/dist/cloudflare/providers.d.ts.map +1 -1
- package/dist/cloudflare/r2-bucket-lock.d.ts +28 -21
- package/dist/cloudflare/r2-bucket-lock.d.ts.map +1 -1
- package/dist/forgejo/branch-protection-form.d.ts +16 -5
- package/dist/forgejo/branch-protection-form.d.ts.map +1 -1
- package/dist/forgejo/branch-protection.d.ts +34 -3
- package/dist/forgejo/branch-protection.d.ts.map +1 -1
- package/dist/forgejo/fake-forgejo.d.ts +41 -0
- package/dist/forgejo/fake-forgejo.d.ts.map +1 -0
- package/dist/forgejo/index.js +220 -284
- package/dist/forgejo/index.js.map +14 -15
- package/dist/forgejo/org-actions-secrets.d.ts +42 -12
- package/dist/forgejo/org-actions-secrets.d.ts.map +1 -1
- package/dist/forgejo/org-label.d.ts +30 -3
- package/dist/forgejo/org-label.d.ts.map +1 -1
- package/dist/forgejo/org-team.d.ts +37 -4
- package/dist/forgejo/org-team.d.ts.map +1 -1
- package/dist/forgejo/repo-webhook-form.d.ts +9 -18
- package/dist/forgejo/repo-webhook-form.d.ts.map +1 -1
- package/dist/forgejo/repo-webhook.d.ts +35 -3
- package/dist/forgejo/repo-webhook.d.ts.map +1 -1
- package/dist/forgejo/repository.d.ts +47 -2
- package/dist/forgejo/repository.d.ts.map +1 -1
- package/dist/forgejo/resource.d.ts +37 -37
- package/dist/forgejo/resource.d.ts.map +1 -1
- package/dist/forgejo/team-member.d.ts +48 -4
- package/dist/forgejo/team-member.d.ts.map +1 -1
- package/dist/forgejo/values.d.ts +9 -5
- package/dist/forgejo/values.d.ts.map +1 -1
- package/docs/credentials.md +6 -0
- package/docs/distilled-interim.md +151 -0
- package/docs/peers.md +15 -6
- package/docs/upstream-conformance.md +46 -22
- package/package.json +5 -2
- package/src/cloudflare/fake-r2-lock.ts +120 -0
- package/src/cloudflare/index.ts +4 -5
- package/src/cloudflare/mesh-node-api.ts +3 -2
- package/src/cloudflare/providers.ts +13 -16
- package/src/cloudflare/r2-bucket-lock.test.ts +139 -0
- package/src/cloudflare/r2-bucket-lock.ts +59 -51
- package/src/forgejo/branch-protection-form.ts +12 -5
- package/src/forgejo/branch-protection.ts +58 -26
- package/src/forgejo/fake-forgejo.ts +54 -0
- package/src/forgejo/org-actions-secrets.ts +44 -36
- package/src/forgejo/org-label.test.ts +100 -0
- package/src/forgejo/org-label.ts +46 -36
- package/src/forgejo/org-team.ts +40 -27
- package/src/forgejo/repo-webhook-form.ts +10 -27
- package/src/forgejo/repo-webhook.ts +66 -35
- package/src/forgejo/repository.test.ts +125 -0
- package/src/forgejo/repository.ts +72 -52
- package/src/forgejo/resource.ts +80 -79
- package/src/forgejo/team-member.test.ts +97 -0
- package/src/forgejo/team-member.ts +56 -38
- package/src/forgejo/values.ts +9 -17
- package/src/proxmox/distilled-task-await.test.ts +124 -0
- package/dist/cloudflare/client.d.ts +0 -45
- package/dist/cloudflare/client.d.ts.map +0 -1
- package/dist/forgejo/client.d.ts +0 -37
- package/dist/forgejo/client.d.ts.map +0 -1
- package/src/cloudflare/client.ts +0 -76
- 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
|
|
8
|
-
@distilled.cloud/cloudflare@1.0.0-rc.12 @
|
|
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`
|
|
5
|
-
* SDK
|
|
6
|
-
* `
|
|
7
|
-
*
|
|
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
|
|
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"}
|
package/dist/cloudflare/index.js
CHANGED
|
@@ -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 {
|
|
6
|
-
import * as
|
|
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
|
|
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 = (
|
|
42
|
+
var readLock = (props, written) => Effect.gen(function* () {
|
|
64
43
|
const jurisdiction = jurisdictionOf(props);
|
|
65
|
-
const live = yield*
|
|
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 = (
|
|
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*
|
|
89
|
-
|
|
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 = (
|
|
96
|
-
yield*
|
|
97
|
-
|
|
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,
|
|
103
|
-
const
|
|
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: () =>
|
|
81
|
+
list: () => Effect.succeed([]),
|
|
106
82
|
nuke: { singleton: true },
|
|
107
|
-
read:
|
|
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(
|
|
87
|
+
return yield* run(readLock(props, output !== undefined));
|
|
112
88
|
}),
|
|
113
|
-
reconcile:
|
|
114
|
-
delete:
|
|
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
|
|
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
|
|
125
|
-
import * as
|
|
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
|
|
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(
|
|
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,
|
|
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
|
|
124
|
+
return Effect2.succeed(matches[0]);
|
|
149
125
|
const ids = matches.map((node) => node.id).join(", ");
|
|
150
|
-
return
|
|
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(
|
|
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 ?
|
|
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(
|
|
159
|
-
var deleteNode = (accountId, nodeId) => zeroTrust.deleteTunnelWarpConnector({ accountId, tunnelId: nodeId }).pipe(
|
|
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
|
|
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) =>
|
|
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(
|
|
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) =>
|
|
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*
|
|
232
|
-
const node = yield* createNode(accountId, news.name, news.ha).pipe(
|
|
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) =>
|
|
211
|
+
var reconcileMeshNode = (accountId, news, output) => Effect3.gen(function* () {
|
|
236
212
|
const invalid = validateMeshNode(news);
|
|
237
213
|
if (invalid !== undefined)
|
|
238
|
-
return yield*
|
|
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*
|
|
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(
|
|
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 =
|
|
260
|
-
const resolved = yield* yield*
|
|
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,
|
|
264
|
-
const services = Context2.make(
|
|
265
|
-
const run = (
|
|
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: () =>
|
|
244
|
+
list: () => Effect4.succeed([]),
|
|
269
245
|
nuke: { skip: true },
|
|
270
|
-
diff: ({ news, output }) => run(
|
|
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*
|
|
249
|
+
return yield* Effect4.fail(invalid);
|
|
274
250
|
return diffMeshNode(news, output, yield* accountId);
|
|
275
251
|
})),
|
|
276
|
-
read: ({ olds, output }) => run(
|
|
277
|
-
reconcile: ({ news, output }) => run(
|
|
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
|
|
258
|
+
import { Credentials as Credentials3 } from "@distilled.cloud/cloudflare/Credentials";
|
|
283
259
|
import * as zeroTrust2 from "@distilled.cloud/cloudflare/zero-trust";
|
|
284
|
-
import * as
|
|
285
|
-
import * as
|
|
286
|
-
var refuseUnsafe =
|
|
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*
|
|
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*
|
|
268
|
+
const credentials = yield* yield* Credentials3;
|
|
293
269
|
if (credentials.type === "apiKey") {
|
|
294
|
-
return yield*
|
|
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" &&
|
|
299
|
-
return yield*
|
|
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
|
|
282
|
+
return Effect5.succeed(ref.id);
|
|
307
283
|
const name = ref.name;
|
|
308
|
-
return
|
|
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) =>
|
|
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(
|
|
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
|
-
}))),
|
|
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*
|
|
295
|
+
return yield* Effect5.fail(new MeshNodeError({ message: `Mesh node ${nodeId} returned an empty token.` }));
|
|
320
296
|
}
|
|
321
|
-
return
|
|
297
|
+
return Redacted.make(token);
|
|
322
298
|
});
|
|
323
299
|
// src/cloudflare/providers.ts
|
|
324
|
-
import * as
|
|
300
|
+
import * as Cloudflare2 from "alchemy/Cloudflare";
|
|
325
301
|
import * as Provider3 from "alchemy/Provider";
|
|
326
|
-
import * as
|
|
302
|
+
import * as Layer from "effect/Layer";
|
|
327
303
|
class Providers extends Provider3.ProviderCollection()("HomeflareCloudflare") {
|
|
328
304
|
}
|
|
329
|
-
var providers = () =>
|
|
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
|
|
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
|
|
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
|
|
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=
|
|
336
|
+
//# debugId=AB671D282C098EA364756E2164756E21
|
|
361
337
|
//# sourceMappingURL=index.js.map
|