@homeflare/alchemy 0.48.1 → 0.49.0

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 (80) hide show
  1. package/README.md +11 -0
  2. package/dist/cloudflare/cloudflared-tunnel-api.d.ts +87 -0
  3. package/dist/cloudflare/cloudflared-tunnel-api.d.ts.map +1 -0
  4. package/dist/cloudflare/cloudflared-tunnel-form.d.ts +49 -0
  5. package/dist/cloudflare/cloudflared-tunnel-form.d.ts.map +1 -0
  6. package/dist/cloudflare/cloudflared-tunnel-harness.d.ts +45 -0
  7. package/dist/cloudflare/cloudflared-tunnel-harness.d.ts.map +1 -0
  8. package/dist/cloudflare/cloudflared-tunnel-lifecycle.d.ts +21 -0
  9. package/dist/cloudflare/cloudflared-tunnel-lifecycle.d.ts.map +1 -0
  10. package/dist/cloudflare/cloudflared-tunnel.d.ts +39 -0
  11. package/dist/cloudflare/cloudflared-tunnel.d.ts.map +1 -0
  12. package/dist/cloudflare/fake-access.d.ts +40 -0
  13. package/dist/cloudflare/fake-access.d.ts.map +1 -0
  14. package/dist/cloudflare/fake-mesh.d.ts +7 -2
  15. package/dist/cloudflare/fake-mesh.d.ts.map +1 -1
  16. package/dist/cloudflare/fake-tunnel.d.ts +48 -0
  17. package/dist/cloudflare/fake-tunnel.d.ts.map +1 -0
  18. package/dist/cloudflare/index.d.ts +15 -4
  19. package/dist/cloudflare/index.d.ts.map +1 -1
  20. package/dist/cloudflare/index.js +518 -7
  21. package/dist/cloudflare/index.js.map +15 -4
  22. package/dist/cloudflare/providers.d.ts +1 -1
  23. package/dist/cloudflare/providers.d.ts.map +1 -1
  24. package/dist/cloudflare/saas-oidc-api.d.ts +88 -0
  25. package/dist/cloudflare/saas-oidc-api.d.ts.map +1 -0
  26. package/dist/cloudflare/saas-oidc-consumer.d.ts +22 -0
  27. package/dist/cloudflare/saas-oidc-consumer.d.ts.map +1 -0
  28. package/dist/cloudflare/saas-oidc-error.d.ts +22 -0
  29. package/dist/cloudflare/saas-oidc-error.d.ts.map +1 -0
  30. package/dist/cloudflare/saas-oidc-form.d.ts +48 -0
  31. package/dist/cloudflare/saas-oidc-form.d.ts.map +1 -0
  32. package/dist/cloudflare/saas-oidc-harness.d.ts +50 -0
  33. package/dist/cloudflare/saas-oidc-harness.d.ts.map +1 -0
  34. package/dist/cloudflare/saas-oidc-lifecycle.d.ts +14 -0
  35. package/dist/cloudflare/saas-oidc-lifecycle.d.ts.map +1 -0
  36. package/dist/cloudflare/saas-oidc-props.d.ts +139 -0
  37. package/dist/cloudflare/saas-oidc-props.d.ts.map +1 -0
  38. package/dist/cloudflare/saas-oidc-team.d.ts +9 -0
  39. package/dist/cloudflare/saas-oidc-team.d.ts.map +1 -0
  40. package/dist/cloudflare/saas-oidc-wire.d.ts +58 -0
  41. package/dist/cloudflare/saas-oidc-wire.d.ts.map +1 -0
  42. package/dist/cloudflare/saas-oidc.d.ts +36 -0
  43. package/dist/cloudflare/saas-oidc.d.ts.map +1 -0
  44. package/docs/cloudflared-tunnel.md +136 -0
  45. package/docs/credentials.md +1 -1
  46. package/docs/ownership.md +3 -2
  47. package/docs/saas-oidc.md +185 -0
  48. package/docs/upstream-conformance-families.md +1 -1
  49. package/docs/upstream-conformance.md +3 -0
  50. package/package.json +1 -1
  51. package/src/cloudflare/cloudflared-tunnel-api.ts +150 -0
  52. package/src/cloudflare/cloudflared-tunnel-edges.test.ts +179 -0
  53. package/src/cloudflare/cloudflared-tunnel-form.ts +78 -0
  54. package/src/cloudflare/cloudflared-tunnel-harness.ts +126 -0
  55. package/src/cloudflare/cloudflared-tunnel-lifecycle.ts +146 -0
  56. package/src/cloudflare/cloudflared-tunnel-state.test.ts +104 -0
  57. package/src/cloudflare/cloudflared-tunnel.test.ts +164 -0
  58. package/src/cloudflare/cloudflared-tunnel.ts +116 -0
  59. package/src/cloudflare/fake-access.ts +196 -0
  60. package/src/cloudflare/fake-mesh.ts +6 -3
  61. package/src/cloudflare/fake-tunnel.ts +148 -0
  62. package/src/cloudflare/index.ts +27 -4
  63. package/src/cloudflare/providers.ts +16 -3
  64. package/src/cloudflare/saas-oidc-api.ts +224 -0
  65. package/src/cloudflare/saas-oidc-consumer.ts +35 -0
  66. package/src/cloudflare/saas-oidc-error.ts +52 -0
  67. package/src/cloudflare/saas-oidc-form.test.ts +227 -0
  68. package/src/cloudflare/saas-oidc-form.ts +190 -0
  69. package/src/cloudflare/saas-oidc-harness.ts +140 -0
  70. package/src/cloudflare/saas-oidc-lifecycle.ts +150 -0
  71. package/src/cloudflare/saas-oidc-parse-error.test.ts +180 -0
  72. package/src/cloudflare/saas-oidc-props.ts +147 -0
  73. package/src/cloudflare/saas-oidc-read.test.ts +85 -0
  74. package/src/cloudflare/saas-oidc-stable.test.ts +38 -0
  75. package/src/cloudflare/saas-oidc-state.test.ts +128 -0
  76. package/src/cloudflare/saas-oidc-team.test.ts +88 -0
  77. package/src/cloudflare/saas-oidc-team.ts +70 -0
  78. package/src/cloudflare/saas-oidc-wire.ts +116 -0
  79. package/src/cloudflare/saas-oidc.test.ts +201 -0
  80. package/src/cloudflare/saas-oidc.ts +107 -0
package/README.md CHANGED
@@ -69,6 +69,17 @@ const door = yield * MeshNode('vault-door', { name: 'door-a', ha: false });
69
69
 
70
70
  Guide, the enrolment step and every replace case: [docs/mesh-node.md](./docs/mesh-node.md).
71
71
 
72
+ ## CloudflaredTunnel
73
+
74
+ A remotely configured Cloudflare Tunnel declared **without its connector token in state**
75
+ (`Cloudflare.Tunnel.Tunnel` stores it in plaintext): [docs/cloudflared-tunnel.md](./docs/cloudflared-tunnel.md).
76
+
77
+ ## SaasOidcApplication
78
+
79
+ A Cloudflare Access SaaS application of auth type `oidc` (Access as the OpenID Connect provider for
80
+ one relying party), exposing `issuer`, `clientId` and `jwksEndpoint`. Guide, the client-secret
81
+ rules, adoption and drift: [docs/saas-oidc.md](./docs/saas-oidc.md).
82
+
72
83
  ## Website.Astro / Website.Vite
73
84
 
74
85
  House flags on Alchemy's own stacks — the two this estate actually ships.
@@ -0,0 +1,87 @@
1
+ /**
2
+ * The `cfd_tunnel` calls a Cloudflare Tunnel (the cloudflared kind) needs, and nothing else.
3
+ *
4
+ * ★ OVER `@distilled.cloud/cloudflare`, THE SDK ALCHEMY'S OWN CLOUDFLARE PROVIDERS USE, the same
5
+ * one mesh-node-api.ts calls. The operations are `createTunnelCloudflared` (POST
6
+ * `/accounts/{account_id}/cfd_tunnel`), `getTunnelCloudflared`, `patchTunnelCloudflared`,
7
+ * `deleteTunnelCloudflared` and the account-wide `listTunnels` (GET `/accounts/{id}/tunnels`,
8
+ * filtered to `cfd_tunnel`), all in distilled `1.0.0-rc.13` `src/services/zero_trust.ts`
9
+ * (read 2026-10-09).
10
+ *
11
+ * ⛔ NO FUNCTION IN THIS FILE CALLS `getTunnelCloudflaredToken`, AND `observe` COPIES NAMED FIELDS
12
+ * ONLY. Alchemy's own `Cloudflare.Tunnel.Tunnel` (beta.81, Tunnel/Tunnel.ts `reconcile`, `read`
13
+ * and `list`) fetches the token on every call and stores it as a `Redacted` attribute, which
14
+ * `encodeState` (State/StateEncoding.ts) writes into state as `{"__redacted__": "<token>"}`:
15
+ * plaintext, and the token runs the tunnel. This package never holds it, so there is nothing to
16
+ * leak. See cloudflared-tunnel-form.ts.
17
+ * ⚠️ `createTunnelCloudflared` takes no `tunnelSecret` here either: the schema calls the field the
18
+ * password for a locally-managed tunnel and marks it optional (distilled rc.13 zero_trust.ts,
19
+ * `CreateTunnelCloudflaredRequest`); that Cloudflare generates one when it is omitted is
20
+ * UNVERIFIED, and a remotely managed tunnel needs no secret on our side.
21
+ */
22
+ import * as zeroTrust from '@distilled.cloud/cloudflare/zero-trust';
23
+ import * as Effect from 'effect/Effect';
24
+ /** A live tunnel as this package sees it. ⛔ There is no token or secret field, on purpose. */
25
+ export interface ObservedTunnel {
26
+ readonly id: string;
27
+ readonly name: string;
28
+ /** `undefined` when the response omitted it; only an explicit `local` is refused (lifecycle). */
29
+ readonly configSrc: 'cloudflare' | 'local' | undefined;
30
+ }
31
+ declare const CloudflaredTunnelError_base: new <A extends Record<string, any> = {}>(args: import("effect/Types").VoidIfEmpty<{ readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }>) => import("effect/Cause").YieldableError & {
32
+ readonly _tag: "CloudflaredTunnelError";
33
+ } & Readonly<A>;
34
+ /** A refusal or failure, carrying the sentence an operator needs. Never a token. */
35
+ export declare class CloudflaredTunnelError extends CloudflaredTunnelError_base<{
36
+ readonly message: string;
37
+ }> {
38
+ }
39
+ type Raw = {
40
+ readonly id?: string | null;
41
+ readonly name?: string | null;
42
+ readonly configSrc?: string | null;
43
+ readonly tunType?: string | null;
44
+ readonly deletedAt?: string | null;
45
+ };
46
+ /**
47
+ * ⚠️ A DELETED TUNNEL IS NOT A TUNNEL. Cloudflare keeps deleted tunnels (every response carries
48
+ * `deleted_at`), so a `GET` by id can answer for one; adopting that tombstone would PATCH it.
49
+ * ⚠️ NOR IS ANOTHER TUNNEL TYPE. `/tunnels` lists every type, and a `warp_connector` (a Mesh node)
50
+ * shares the account's name space. An item whose `tun_type` is anything but `cfd_tunnel` is not
51
+ * ours even when the name matches. A response with no `tun_type` is accepted: it came from a
52
+ * `cfd_tunnel`-scoped call.
53
+ */
54
+ export declare const observe: (raw: Raw) => ObservedTunnel | undefined;
55
+ /** The tunnel with this id, or `undefined` when it is gone (404 / code 1002, or soft-deleted). */
56
+ export declare const getTunnel: (accountId: string, tunnelId: string) => Effect.Effect<ObservedTunnel | undefined, zeroTrust.CloudflareOpError, zeroTrust.CloudflareOpContext>;
57
+ /**
58
+ * The live cloudflared tunnel with exactly this name, or `undefined`.
59
+ *
60
+ * ★ `name` and `tunTypes` are sent as filters, but the match is re-checked here: the filter's
61
+ * exactness is not documented, and a prefix match would adopt the wrong tunnel.
62
+ * ⛔ Two live matches is refused rather than resolved. Names are unique per account (the create
63
+ * answers code 1013 otherwise), so two means something this package does not understand.
64
+ */
65
+ export declare const findTunnelByName: (accountId: string, name: string) => Effect.Effect<ObservedTunnel | undefined, CloudflaredTunnelError | zeroTrust.CloudflareOpError, zeroTrust.CloudflareOpContext>;
66
+ /**
67
+ * ⛔ ALWAYS `configSrc: 'cloudflare'` (remotely managed), NEVER OMITTED. The dashboard and the
68
+ * ingress rules a consumer later PUTs are the remote configuration; a `local` tunnel ignores
69
+ * them. The SDK types the field as optional with two values (zero_trust.ts
70
+ * `CreateTunnelCloudflaredRequest`) and does not say which is the default, so it is sent
71
+ * explicitly. Create-only (the PATCH body has `name` and `tunnelSecret` only).
72
+ */
73
+ export declare const createTunnel: (accountId: string, name: string) => Effect.Effect<ObservedTunnel, CloudflaredTunnelError | zeroTrust.CreateTunnelCloudflaredError, zeroTrust.CloudflareOpContext>;
74
+ /** ★ A rename is a PATCH: same id, same connectors, same `<id>.cfargotunnel.com` target. */
75
+ export declare const renameTunnel: (accountId: string, tunnelId: string, name: string) => Effect.Effect<ObservedTunnel, zeroTrust.PatchTunnelCloudflaredError, zeroTrust.CloudflareOpContext>;
76
+ /**
77
+ * ⛔ DELETE BY THE STORED ID ONLY, AFTER A READ BY THAT ID. Never by name: a name can have been
78
+ * re-used by another tunnel since this one was written, and a delete cannot be taken back. An
79
+ * empty id is refused before any request (a blank path segment would address the collection).
80
+ * ★ Idempotent: a tunnel already gone or soft-deleted is a successful delete, including one that
81
+ * vanishes between the read and the DELETE (see `isNotFound`). The API refuses a
82
+ * delete while connectors are attached ("The tunnel must have no active connections", SDK doc on
83
+ * `deleteTunnelCloudflared`); that error passes through, since the operator must stop cloudflared.
84
+ */
85
+ export declare const deleteTunnel: (accountId: string, tunnelId: string) => Effect.Effect<undefined, CloudflaredTunnelError | zeroTrust.CloudflareOpError, zeroTrust.CloudflareOpContext>;
86
+ export {};
87
+ //# sourceMappingURL=cloudflared-tunnel-api.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cloudflared-tunnel-api.d.ts","sourceRoot":"","sources":["../../src/cloudflare/cloudflared-tunnel-api.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,OAAO,KAAK,SAAS,MAAM,wCAAwC,CAAC;AAEpE,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AAGxC,8FAA8F;AAC9F,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,iGAAiG;IACjG,QAAQ,CAAC,SAAS,EAAE,YAAY,GAAG,OAAO,GAAG,SAAS,CAAC;CACxD;;;;AAED,oFAAoF;AACpF,qBAAa,sBAAuB,SAAQ,4BAA2C;IACrF,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,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACnC,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACpC,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,OAAO,QAAS,GAAG,KAAG,cAAc,GAAG,SAOnD,CAAC;AAEF,kGAAkG;AAClG,eAAO,MAAM,SAAS,cAAe,MAAM,YAAY,MAAM,0GAI1D,CAAC;AAEJ;;;;;;;GAOG;AACH,eAAO,MAAM,gBAAgB,cAAe,MAAM,QAAQ,MAAM,mIAe7D,CAAC;AAEJ;;;;;;GAMG;AACH,eAAO,MAAM,YAAY,cAAe,MAAM,QAAQ,MAAM,kIAUzD,CAAC;AAEJ,4FAA4F;AAC5F,eAAO,MAAM,YAAY,cAAe,MAAM,YAAY,MAAM,QAAQ,MAAM,wGAGc,CAAC;AAS7F;;;;;;;;GAQG;AACH,eAAO,MAAM,YAAY,cAAe,MAAM,YAAY,MAAM,kHAY5D,CAAC"}
@@ -0,0 +1,49 @@
1
+ /**
2
+ * `Cloudflare.CloudflaredTunnel` — its props, its attributes, and the pure `diff`.
3
+ *
4
+ * ⛔ THE ATTRIBUTES NEVER HOLD A TOKEN OR A SECRET. That is the reason this resource exists next to
5
+ * Alchemy's `Cloudflare.Tunnel.Tunnel` (beta.81): its `reconcile`, `read` and `list` all call
6
+ * `getTunnelCloudflaredToken` and keep the result as `token: Redacted<string>`, which
7
+ * `encodeState` persists as `{"__redacted__": "<token>"}` in the (unencrypted) state store. That
8
+ * token is what `cloudflared tunnel run --token` accepts: whoever reads state can run a connector
9
+ * for the tunnel and receive its traffic. It also makes every PLAN need a token with a Write
10
+ * permission. This resource never calls the endpoint, so a plan runs on a read token.
11
+ * ★ THE CONNECTOR'S TOKEN IS A RUNTIME CREDENTIAL, NOT INFRASTRUCTURE STATE. Whatever installs the
12
+ * connector fetches it at that moment and hands it straight to the workload's secret store.
13
+ * Nothing here does, and nothing here exports a helper that would.
14
+ * ★ SCOPE: the tunnel object only. No ingress rules, no routes, no DNS, no connector Deployment:
15
+ * those are separate declarations that name this tunnel's `id`.
16
+ */
17
+ import type { Diff } from 'alchemy/Diff';
18
+ import type { Input } from 'alchemy/Input';
19
+ import { CloudflaredTunnelError } from './cloudflared-tunnel-api.ts';
20
+ export interface CloudflaredTunnelProps {
21
+ /**
22
+ * The tunnel's name in the account — and its identity when adopting (names are unique per
23
+ * account). ★ Changing it RENAMES the tunnel in place (`PATCH`): the id, the connectors and the
24
+ * `<id>.cfargotunnel.com` target survive.
25
+ */
26
+ readonly name: string;
27
+ }
28
+ export interface CloudflaredTunnelAttributes {
29
+ /** The tunnel's UUID: the `<id>.cfargotunnel.com` target and the key for ingress rules. */
30
+ readonly id: string;
31
+ /**
32
+ * The account the provider environment resolved when the tunnel was written. Not a secret; it
33
+ * is what `delete` and `read` address, so they never act on the current environment's account.
34
+ */
35
+ readonly accountId: string;
36
+ readonly name: string;
37
+ }
38
+ /** ⛔ Fail closed on a declaration the API would accept but nobody meant. */
39
+ export declare const validateCloudflaredTunnel: (props: CloudflaredTunnelProps) => CloudflaredTunnelError | undefined;
40
+ /**
41
+ * update or replace, from the declaration and what was last written.
42
+ *
43
+ * - Account changed → `replace`, create-first: names are unique per ACCOUNT, so the two
44
+ * generations cannot collide.
45
+ * - Name changed → `update` (a PATCH rename).
46
+ * - Otherwise → no opinion; the engine compares props.
47
+ */
48
+ export declare const diffCloudflaredTunnel: (news: Input<CloudflaredTunnelProps>, output: CloudflaredTunnelAttributes | undefined, accountId: string) => Diff | undefined;
49
+ //# sourceMappingURL=cloudflared-tunnel-form.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cloudflared-tunnel-form.d.ts","sourceRoot":"","sources":["../../src/cloudflare/cloudflared-tunnel-form.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,cAAc,CAAC;AACzC,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AAC3C,OAAO,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AAErE,MAAM,WAAW,sBAAsB;IACrC;;;;OAIG;IACH,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,2BAA2B;IAC1C,2FAA2F;IAC3F,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB;;;OAGG;IACH,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CAGvB;AAED,4EAA4E;AAC5E,eAAO,MAAM,yBAAyB,UAC7B,sBAAsB,KAC5B,sBAAsB,GAAG,SAY3B,CAAC;AAEF;;;;;;;GAOG;AACH,eAAO,MAAM,qBAAqB,SAC1B,KAAK,CAAC,sBAAsB,CAAC,UAC3B,2BAA2B,GAAG,SAAS,aACpC,MAAM,KAChB,IAAI,GAAG,SAMT,CAAC"}
@@ -0,0 +1,45 @@
1
+ import * as State from 'alchemy/State';
2
+ import * as Effect from 'effect/Effect';
3
+ import * as Exit from 'effect/Exit';
4
+ import { CloudflaredTunnel } from './cloudflared-tunnel.ts';
5
+ import type { CloudflaredTunnelAttributes, CloudflaredTunnelProps } from './cloudflared-tunnel-form.ts';
6
+ import type { FakeTunnels } from './fake-tunnel.ts';
7
+ export declare const ids: {
8
+ fqn: string;
9
+ id: string;
10
+ instanceId: string;
11
+ };
12
+ export declare const extra: {
13
+ bindings: never;
14
+ session: never;
15
+ };
16
+ export type P = Effect.Success<typeof CloudflaredTunnel.Provider>;
17
+ export declare const handler: <F>(name: string, fn: F | undefined) => F;
18
+ /** Build the provider over the fake (as `accountId`'s environment) and hand it to `use`. */
19
+ export declare const run: <A, E>(fake: FakeTunnels, use: (p: P) => Effect.Effect<A, E>, accountId?: string) => Promise<A>;
20
+ export declare const reconcile: (p: P, news: CloudflaredTunnelProps, output?: CloudflaredTunnelAttributes) => Effect.Effect<CloudflaredTunnelAttributes, any, never>;
21
+ export declare const read: (p: P, olds: CloudflaredTunnelProps, output?: CloudflaredTunnelAttributes) => Effect.Effect<CloudflaredTunnelAttributes | undefined, any, never>;
22
+ export declare const diff: (p: P, news: CloudflaredTunnelProps, olds: CloudflaredTunnelProps, output: CloudflaredTunnelAttributes) => Effect.Effect<void | import("alchemy").Diff, any, never>;
23
+ /** A stored attribute set for a tunnel that may or may not exist in the fake. */
24
+ export declare const stored: (accountId: string, over?: Partial<CloudflaredTunnelAttributes>) => CloudflaredTunnelAttributes;
25
+ /** Requests the fake saw for any `/token` path. The provider must never make one. */
26
+ export declare const tokenReads: (fake: FakeTunnels) => number;
27
+ /** The writes the fake saw, in order: `['POST', 'DELETE', …]`. */
28
+ export declare const writes: (fake: FakeTunnels) => string[];
29
+ type Rows = Record<string, Record<string, Record<string, State.ResourceState>>>;
30
+ /**
31
+ * ★ THE REAL ENGINE, NOT A MODEL OF IT: Alchemy's own `Plan.make` + `apply` over an in-memory
32
+ * state store (the same construction as mesh-node-harness.ts, which says why
33
+ * `FetchHttpClient.Fetch` is provided around the whole deploy). `rows` is the store itself, so a
34
+ * test can serialize exactly what Alchemy would persist.
35
+ */
36
+ export declare const engine: (fake: FakeTunnels) => {
37
+ deploy: <A, E, R>(declare: Effect.Effect<A, E, R>) => Promise<Exit.Exit<unknown, unknown>>;
38
+ rows: Rows;
39
+ /** The stored row's status (`created`, `replacing`, …), or `undefined` when there is none. */
40
+ status: (fqn: string) => "created" | "creating" | "deleting" | "replaced" | "replacing" | "updated" | "updating" | undefined;
41
+ };
42
+ /** The failure sentence of a deploy, or `''` when it succeeded. */
43
+ export declare const failureOf: (exit: Exit.Exit<unknown, unknown>) => string;
44
+ export {};
45
+ //# sourceMappingURL=cloudflared-tunnel-harness.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cloudflared-tunnel-harness.d.ts","sourceRoot":"","sources":["../../src/cloudflare/cloudflared-tunnel-harness.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,KAAK,MAAM,eAAe,CAAC;AAEvC,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AACxC,OAAO,KAAK,IAAI,MAAM,aAAa,CAAC;AAGpC,OAAO,EAAE,iBAAiB,EAA6B,MAAM,yBAAyB,CAAC;AACvF,OAAO,KAAK,EACV,2BAA2B,EAC3B,sBAAsB,EACvB,MAAM,8BAA8B,CAAC;AAEtC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAEpD,eAAO,MAAM,GAAG;IAAK,GAAG;IAAiB,EAAE;IAAW,UAAU;CAAS,CAAC;AAC1E,eAAO,MAAM,KAAK;IAAK,QAAQ,EAAQ,KAAK;IAAE,OAAO,EAAe,KAAK;CAAE,CAAC;AAE5E,MAAM,MAAM,CAAC,GAAG,MAAM,CAAC,OAAO,CAAC,OAAO,iBAAiB,CAAC,QAAQ,CAAC,CAAC;AAElE,eAAO,MAAM,OAAO,GAAI,CAAC,QAAQ,MAAM,MAAM,CAAC,GAAG,SAAS,KAAG,CAG5D,CAAC;AAEF,4FAA4F;AAC5F,eAAO,MAAM,GAAG,GAAI,CAAC,EAAE,CAAC,QAChB,WAAW,OACZ,CAAC,CAAC,EAAE,CAAC,KAAK,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,cACtB,MAAM,eAUjB,CAAC;AAEJ,eAAO,MAAM,SAAS,MACjB,CAAC,QACE,sBAAsB,WACnB,2BAA2B,2DAC+B,CAAC;AAEtE,eAAO,MAAM,IAAI,MAAO,CAAC,QAAQ,sBAAsB,WAAW,2BAA2B,uEAC1C,CAAC;AAEpD,eAAO,MAAM,IAAI,MACZ,CAAC,QACE,sBAAsB,QACtB,sBAAsB,UACpB,2BAA2B,6DAKkD,CAAC;AAExF,iFAAiF;AACjF,eAAO,MAAM,MAAM,cACN,MAAM,SACX,OAAO,CAAC,2BAA2B,CAAC,KACzC,2BAKD,CAAC;AAEH,qFAAqF;AACrF,eAAO,MAAM,UAAU,SAAU,WAAW,WACe,CAAC;AAE5D,kEAAkE;AAClE,eAAO,MAAM,MAAM,SAAU,WAAW,aAC0B,CAAC;AAEnE,KAAK,IAAI,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC;AAEhF;;;;;GAKG;AACH,eAAO,MAAM,MAAM,SAAU,WAAW;aAItB,CAAC,EAAE,CAAC,EAAE,CAAC,WAAW,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC;;IAetD,8FAA8F;kBAChF,MAAM;CAEvB,CAAC;AAEF,mEAAmE;AACnE,eAAO,MAAM,SAAS,SAAU,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,OAAO,CAAC,WACG,CAAC"}
@@ -0,0 +1,21 @@
1
+ import * as Effect from 'effect/Effect';
2
+ import { CloudflaredTunnelError } from './cloudflared-tunnel-api.ts';
3
+ import { type CloudflaredTunnelAttributes, type CloudflaredTunnelProps } from './cloudflared-tunnel-form.ts';
4
+ /**
5
+ * Owned: refresh by the stored id. Cold (no stored row): find the live tunnel by exact name and
6
+ * hand it back `Unowned`, so Alchemy refuses to take it over until the stack says `adopt(true)` —
7
+ * a `cfd_tunnel` carries no ownership marker to prove it was ours. Adoption is idempotent: the
8
+ * adopted row holds the id, so every later `read` takes the owned path and writes nothing.
9
+ *
10
+ * ⛔ A STORED ID THAT NO LONGER RESOLVES IS "MISSING", NEVER A NAME PROBE. Drift (alchemy beta.81
11
+ * src/Drift.ts) passes the row's output and treats whatever `read` returns as already ours
12
+ * (`stripUnowned`), then repairs by reconciling onto it. A by-name fallback there would write a
13
+ * same-named tunnel somebody else made into this row, bypassing `adopt(true)`; a later rename
14
+ * would PATCH it and `RemovalPolicy.destroy` would DELETE it. Returning `undefined` makes Drift
15
+ * report "missing" and recreate through `reconcile`, where `createFresh` names the holder.
16
+ */
17
+ export declare const readCloudflaredTunnel: (accountId: string, olds: CloudflaredTunnelProps | undefined, output: CloudflaredTunnelAttributes | undefined) => Effect.Effect<CloudflaredTunnelAttributes | undefined, CloudflaredTunnelError | import("@distilled.cloud/cloudflare/Protocol").CloudflareOpError, import("@distilled.cloud/cloudflare/Protocol").CloudflareOpContext>;
18
+ export declare const reconcileCloudflaredTunnel: (accountId: string, news: CloudflaredTunnelProps, output: CloudflaredTunnelAttributes | undefined) => Effect.Effect<CloudflaredTunnelAttributes, CloudflaredTunnelError | import("@distilled.cloud/cloudflare/zero-trust").TunnelNotFound | import("@distilled.cloud/cloudflare/Protocol").CloudflareOpError, import("@distilled.cloud/cloudflare/Protocol").CloudflareOpContext>;
19
+ /** By the stored id and account only (cloudflared-tunnel-api.ts, `deleteTunnel`). */
20
+ export declare const deleteCloudflaredTunnel: (output: CloudflaredTunnelAttributes) => Effect.Effect<undefined, CloudflaredTunnelError | import("@distilled.cloud/cloudflare/Protocol").CloudflareOpError, import("@distilled.cloud/cloudflare/Protocol").CloudflareOpContext>;
21
+ //# sourceMappingURL=cloudflared-tunnel-lifecycle.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cloudflared-tunnel-lifecycle.d.ts","sourceRoot":"","sources":["../../src/cloudflare/cloudflared-tunnel-lifecycle.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AACxC,OAAO,EACL,sBAAsB,EAOvB,MAAM,6BAA6B,CAAC;AACrC,OAAO,EACL,KAAK,2BAA2B,EAChC,KAAK,sBAAsB,EAE5B,MAAM,8BAA8B,CAAC;AAQtC;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,qBAAqB,cACrB,MAAM,QACX,sBAAsB,GAAG,SAAS,UAChC,2BAA2B,GAAG,SAAS,0NAY7C,CAAC;AAkEL,eAAO,MAAM,0BAA0B,cAC1B,MAAM,QACX,sBAAsB,UACpB,2BAA2B,GAAG,SAAS,gRAiB7C,CAAC;AAEL,qFAAqF;AACrF,eAAO,MAAM,uBAAuB,WAAY,2BAA2B,4LAChC,CAAC"}
@@ -0,0 +1,39 @@
1
+ /**
2
+ * `Cloudflare.CloudflaredTunnel` — a Cloudflare Tunnel (the cloudflared kind, `cfd_tunnel`),
3
+ * declared without its connector token ever touching Alchemy state. Usage: docs/cloudflared-tunnel.md.
4
+ *
5
+ * 🔴 WHY THIS EXISTS ALONGSIDE `Cloudflare.Tunnel.Tunnel`. Alchemy beta.81's resource stores the
6
+ * token as a `Redacted` attribute, and the state encoder writes it out in plaintext. See
7
+ * cloudflared-tunnel-form.ts, which also says what this resource leaves to other declarations.
8
+ *
9
+ * ★ REMOVAL POLICY: `retain` BY DEFAULT — see the ★ on `CloudflaredTunnel` below.
10
+ */
11
+ import { Credentials } from '@distilled.cloud/cloudflare/Credentials';
12
+ import * as Cloudflare from 'alchemy/Cloudflare';
13
+ import * as Provider from 'alchemy/Provider';
14
+ import { Resource, type ResourceClass } from 'alchemy/Resource';
15
+ import * as HttpClient from 'effect/http/HttpClient';
16
+ import { type CloudflaredTunnelAttributes, type CloudflaredTunnelProps } from './cloudflared-tunnel-form.ts';
17
+ import type { Providers } from './providers.ts';
18
+ export interface CloudflaredTunnel extends Resource<'Cloudflare.CloudflaredTunnel', CloudflaredTunnelProps, CloudflaredTunnelAttributes, never, Providers> {
19
+ }
20
+ /**
21
+ * ★ `defaultRemovalPolicy: 'retain'`, LIKE EVERY KIT RESOURCE WHOSE DELETION BREAKS ITS CONSUMERS
22
+ * (MeshNode, the `Bao.*` families, `R2BucketLock`; the reasoning is written once in
23
+ * proxmox/resource.ts). Deleting a tunnel drops its remote configuration (ingress rules) and
24
+ * orphans every DNS record that targets `<id>.cfargotunnel.com`, and a new tunnel has a new id
25
+ * and a new token. So dropping the declaration, or `alchemy destroy`, leaves the tunnel live.
26
+ * ⚠️ IT ALSO SKIPS THE OLD TUNNEL'S DELETE IN AN ACCOUNT-CHANGE REPLACE (create-first), which
27
+ * leaves the old tunnel live and unmanaged (Apply.ts, `deleteOldGenerations`, beta.81).
28
+ * ⛔ `delete` STAYS FULLY IMPLEMENTED. Retain is Terraform's `prevent_destroy`, not a stub: it runs
29
+ * the moment a declaration opts in with `.pipe(RemovalPolicy.destroy())`.
30
+ */
31
+ export declare const CloudflaredTunnel: ResourceClass<CloudflaredTunnel>;
32
+ /** What every call below needs: the account, credentials and HTTP — Alchemy's own trio. */
33
+ export type CloudflaredTunnelServices = Cloudflare.CloudflareEnvironment | Credentials | HttpClient.HttpClient;
34
+ /**
35
+ * ★ `Provider.effect`, CAPTURING THE THREE SERVICES AT BUILD, exactly as `MeshNodeProvider` does
36
+ * (mesh-node.ts says why): correct whether or not the stack also merges `Cloudflare.providers()`.
37
+ */
38
+ export declare const CloudflaredTunnelProvider: () => import("effect/Layer").Layer<Provider.Provider<CloudflaredTunnel>, never, Cloudflare.CloudflareEnvironment | Credentials | HttpClient.HttpClient>;
39
+ //# sourceMappingURL=cloudflared-tunnel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cloudflared-tunnel.d.ts","sourceRoot":"","sources":["../../src/cloudflare/cloudflared-tunnel.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EAAE,WAAW,EAAE,MAAM,yCAAyC,CAAC;AACtE,OAAO,KAAK,UAAU,MAAM,oBAAoB,CAAC;AAEjD,OAAO,KAAK,QAAQ,MAAM,kBAAkB,CAAC;AAC7C,OAAO,EAAE,QAAQ,EAAE,KAAK,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAGhE,OAAO,KAAK,UAAU,MAAM,wBAAwB,CAAC;AACrD,OAAO,EACL,KAAK,2BAA2B,EAChC,KAAK,sBAAsB,EAG5B,MAAM,8BAA8B,CAAC;AAMtC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAEhD,MAAM,WAAW,iBAAkB,SAAQ,QAAQ,CACjD,8BAA8B,EAC9B,sBAAsB,EACtB,2BAA2B,EAC3B,KAAK,EACL,SAAS,CACV;CAAG;AAEJ;;;;;;;;;;GAUG;AACH,eAAO,MAAM,iBAAiB,EAAE,aAAa,CAAC,iBAAiB,CAG9D,CAAC;AAEF,2FAA2F;AAC3F,MAAM,MAAM,yBAAyB,GACjC,UAAU,CAAC,qBAAqB,GAChC,WAAW,GACX,UAAU,CAAC,UAAU,CAAC;AAQ1B;;;GAGG;AACH,eAAO,MAAM,yBAAyB,yJA4CnC,CAAC"}
@@ -0,0 +1,40 @@
1
+ export declare const TEAM = "example.cloudflareaccess.com";
2
+ /** ⚠️ A placeholder, not a credential. Tests assert it is stored and logged nowhere. */
3
+ export declare const FAKE_CLIENT_SECRET = "fake-client-secret-0123456789abcdef";
4
+ export type FakeApp = {
5
+ id: string;
6
+ aud: string;
7
+ type: string;
8
+ name: string;
9
+ session_duration: string;
10
+ policies: string[];
11
+ saas_app?: Record<string, unknown> | undefined;
12
+ };
13
+ export type Seen = {
14
+ readonly method: string;
15
+ readonly path: string;
16
+ readonly body: unknown;
17
+ };
18
+ /** The calls `malformed` can corrupt: `get` is the raw read, the rest are SDK calls. */
19
+ export type FakeOp = 'get' | 'list' | 'delete' | 'org';
20
+ /** How the organization read answers; an object names the `auth_domain` it returns. */
21
+ export type FakeOrg = 'matching' | 'unauthorized' | 'forbidden' | 'not_found' | {
22
+ authDomain: string;
23
+ };
24
+ export type FakeOptions = {
25
+ readonly perPage?: number;
26
+ readonly team?: string;
27
+ readonly org?: FakeOrg;
28
+ readonly malformed?: ReadonlyArray<FakeOp>;
29
+ };
30
+ export declare const fakeAccess: (options?: FakeOptions) => {
31
+ apps: Map<string, FakeApp>;
32
+ fetch: typeof fetch;
33
+ seed: (app: Partial<FakeApp> & {
34
+ name: string;
35
+ }) => FakeApp;
36
+ seen: Seen[];
37
+ team: string;
38
+ };
39
+ export type FakeAccess = ReturnType<typeof fakeAccess>;
40
+ //# sourceMappingURL=fake-access.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fake-access.d.ts","sourceRoot":"","sources":["../../src/cloudflare/fake-access.ts"],"names":[],"mappings":"AA4BA,eAAO,MAAM,IAAI,iCAAiC,CAAC;AACnD,wFAAwF;AACxF,eAAO,MAAM,kBAAkB,wCAAwC,CAAC;AAExE,MAAM,MAAM,OAAO,GAAG;IACpB,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,gBAAgB,EAAE,MAAM,CAAC;IACzB,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC;CAChD,CAAC;AAEF,MAAM,MAAM,IAAI,GAAG;IAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAA;CAAE,CAAC;AAE9F,wFAAwF;AACxF,MAAM,MAAM,MAAM,GAAG,KAAK,GAAG,MAAM,GAAG,QAAQ,GAAG,KAAK,CAAC;AACvD,uFAAuF;AACvF,MAAM,MAAM,OAAO,GACf,UAAU,GACV,cAAc,GACd,WAAW,GACX,WAAW,GACX;IAAE,UAAU,EAAE,MAAM,CAAA;CAAE,CAAC;AAE3B,MAAM,MAAM,WAAW,GAAG;IACxB,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,GAAG,CAAC,EAAE,OAAO,CAAC;IACvB,QAAQ,CAAC,SAAS,CAAC,EAAE,aAAa,CAAC,MAAM,CAAC,CAAC;CAC5C,CAAC;AAuCF,eAAO,MAAM,UAAU,aAAa,WAAW;;;gBAO1B,OAAO,CAAC,OAAO,CAAC,GAAG;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,KAAG,OAAO;;;CAwFjE,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG,UAAU,CAAC,OAAO,UAAU,CAAC,CAAC"}
@@ -34,6 +34,7 @@ export type Seen = {
34
34
  readonly path: string;
35
35
  readonly body: unknown;
36
36
  };
37
+ export declare const ok: (result: unknown, extra?: Record<string, unknown>) => Response;
37
38
  export declare const fakeFailure: (status: number, code: number, message: string) => Response;
38
39
  export type FakeOptions = {
39
40
  readonly perPage?: number;
@@ -52,7 +53,11 @@ export declare const fakeMesh: (options?: FakeOptions) => {
52
53
  };
53
54
  export type FakeMesh = ReturnType<typeof fakeMesh>;
54
55
  /** distilled Credentials + the real FetchHttpClient over the fake — what a Bun script provides. */
55
- export declare const fakeClientLayer: (fake: FakeMesh, credentials?: Layer.Layer<Credentials>) => Layer.Layer<Credentials | import("effect/http/HttpClient").HttpClient, never, never>;
56
+ export declare const fakeClientLayer: (fake: {
57
+ readonly fetch: typeof globalThis.fetch;
58
+ }, credentials?: Layer.Layer<Credentials>) => Layer.Layer<Credentials | import("effect/http/HttpClient").HttpClient, never, never>;
56
59
  /** …plus the account, as Alchemy's CloudflareEnvironment hands it to a provider. */
57
- export declare const fakeProviderLayer: (fake: FakeMesh, accountId?: string) => Layer.Layer<Cloudflare.CloudflareEnvironment | Credentials | import("effect/http/HttpClient").HttpClient, never, never>;
60
+ export declare const fakeProviderLayer: (fake: {
61
+ readonly fetch: typeof globalThis.fetch;
62
+ }, accountId?: string) => Layer.Layer<Cloudflare.CloudflareEnvironment | Credentials | import("effect/http/HttpClient").HttpClient, never, never>;
58
63
  //# sourceMappingURL=fake-mesh.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"fake-mesh.d.ts","sourceRoot":"","sources":["../../src/cloudflare/fake-mesh.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,EAAE,KAAK,WAAW,EAAgB,MAAM,yCAAyC,CAAC;AACzF,OAAO,KAAK,UAAU,MAAM,oBAAoB,CAAC;AAEjD,OAAO,KAAK,KAAK,MAAM,cAAc,CAAC;AAItC,eAAO,MAAM,SAAS,sCAAsC,CAAC;AAC7D,eAAO,MAAM,YAAY,qCAAqC,CAAC;AAC/D,iFAAiF;AACjF,eAAO,MAAM,cAAc,0BAA0B,CAAC;AAEtD,MAAM,MAAM,QAAQ,GAAG;IACrB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,OAAO,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B,CAAC;AAEF,MAAM,MAAM,IAAI,GAAG;IAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAA;CAAE,CAAC;AAK9F,eAAO,MAAM,WAAW,WAAY,MAAM,QAAQ,MAAM,WAAW,MAAM,aAItE,CAAC;AAaJ,MAAM,MAAM,WAAW,GAAG;IACxB,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,+FAA+F;IAC/F,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,QAAQ,GAAG,IAAI,CAAC;CACvD,CAAC;AAEF,eAAO,MAAM,QAAQ,aAAa,WAAW;;;;;iBAWvB,OAAO,CAAC,QAAQ,CAAC,GAAG;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,KAAG,QAAQ;;CAyEpE,CAAC;AAEF,MAAM,MAAM,QAAQ,GAAG,UAAU,CAAC,OAAO,QAAQ,CAAC,CAAC;AAEnD,mGAAmG;AACnG,eAAO,MAAM,eAAe,SACpB,QAAQ,gBACD,KAAK,CAAC,KAAK,CAAC,WAAW,CAAC,yFASpC,CAAC;AAEJ,oFAAoF;AACpF,eAAO,MAAM,iBAAiB,SAAU,QAAQ,cAAa,MAAM,4HAYhE,CAAC"}
1
+ {"version":3,"file":"fake-mesh.d.ts","sourceRoot":"","sources":["../../src/cloudflare/fake-mesh.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AACH,OAAO,EAAE,KAAK,WAAW,EAAgB,MAAM,yCAAyC,CAAC;AACzF,OAAO,KAAK,UAAU,MAAM,oBAAoB,CAAC;AAEjD,OAAO,KAAK,KAAK,MAAM,cAAc,CAAC;AAItC,eAAO,MAAM,SAAS,sCAAsC,CAAC;AAC7D,eAAO,MAAM,YAAY,qCAAqC,CAAC;AAC/D,iFAAiF;AACjF,eAAO,MAAM,cAAc,0BAA0B,CAAC;AAEtD,MAAM,MAAM,QAAQ,GAAG;IACrB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,EAAE,EAAE,OAAO,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;CAC3B,CAAC;AAEF,MAAM,MAAM,IAAI,GAAG;IAAE,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAA;CAAE,CAAC;AAE9F,eAAO,MAAM,EAAE,WAAY,OAAO,UAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,aACY,CAAC;AAE/E,eAAO,MAAM,WAAW,WAAY,MAAM,QAAQ,MAAM,WAAW,MAAM,aAItE,CAAC;AAaJ,MAAM,MAAM,WAAW,GAAG;IACxB,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,+FAA+F;IAC/F,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,QAAQ,GAAG,IAAI,CAAC;CACvD,CAAC;AAEF,eAAO,MAAM,QAAQ,aAAa,WAAW;;;;;iBAWvB,OAAO,CAAC,QAAQ,CAAC,GAAG;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,KAAG,QAAQ;;CAyEpE,CAAC;AAEF,MAAM,MAAM,QAAQ,GAAG,UAAU,CAAC,OAAO,QAAQ,CAAC,CAAC;AAEnD,mGAAmG;AACnG,eAAO,MAAM,eAAe,SACpB;IAAE,QAAQ,CAAC,KAAK,EAAE,OAAO,UAAU,CAAC,KAAK,CAAA;CAAE,gBACpC,KAAK,CAAC,KAAK,CAAC,WAAW,CAAC,yFASpC,CAAC;AAEJ,oFAAoF;AACpF,eAAO,MAAM,iBAAiB,SACtB;IAAE,QAAQ,CAAC,KAAK,EAAE,OAAO,UAAU,CAAC,KAAK,CAAA;CAAE,cACtC,MAAM,4HAahB,CAAC"}
@@ -0,0 +1,48 @@
1
+ /**
2
+ * A fake Cloudflare `cfd_tunnel` API for the CloudflaredTunnel tests — an in-memory `fetch` handed
3
+ * to Effect's real FetchHttpClient, the same way fake-mesh.ts does it, so the tests exercise
4
+ * distilled's real path assembly, query and body encoding, envelope decoding and error matching.
5
+ *
6
+ * ⛔ TEST-ONLY, AND NO NETWORK. No provider imports this file. Any request outside
7
+ * `/accounts/<FAKE_ACCOUNT>/cfd_tunnel` or `/accounts/<FAKE_ACCOUNT>/tunnels` throws, so a
8
+ * mis-wired test fails instead of reaching a real API.
9
+ * ★ THE FAKE HANDS OUT A TOKEN ON EVERY SURFACE IT CAN: a `token` field on the create response and
10
+ * on every wire object (worst case: if Cloudflare ever adds one to the documented shape), and the
11
+ * `/token` endpoint. The tests then prove none of it reaches an attribute, a state row or a
12
+ * request log. The SDK's decoder drops fields outside its schema; the provider must not depend on
13
+ * that alone.
14
+ * ★ THE NAME FILTER IS A SUBSTRING MATCH, as in fake-mesh.ts, so the client's exact re-check is
15
+ * exercised. `/tunnels` honours `tun_types` and `is_deleted` as the SDK documents them, and
16
+ * answers pages of two by default.
17
+ */
18
+ import { type Seen } from './fake-mesh.ts';
19
+ export type FakeTunnel = {
20
+ id: string;
21
+ name: string;
22
+ tun_type: string;
23
+ config_src: string;
24
+ status: string;
25
+ token: string;
26
+ deleted_at: string | null;
27
+ /** The fake refuses a DELETE while this is set, as the SDK doc says it must ("no active connections"). */
28
+ connected: boolean;
29
+ };
30
+ export type FakeTunnelOptions = {
31
+ readonly perPage?: number;
32
+ /** Return a Response to answer the POST with it instead (e.g. `fakeFailure(409, 1013, …)`). */
33
+ readonly onCreate?: (name: string) => Response | void;
34
+ /** Return a Response to answer the DELETE with it instead (e.g. the tunnel vanishing mid-delete). */
35
+ readonly onDelete?: (id: string) => Response | void;
36
+ };
37
+ export declare const fakeTunnels: (options?: FakeTunnelOptions) => {
38
+ auth: string[];
39
+ fetch: typeof fetch;
40
+ live: () => FakeTunnel[];
41
+ tunnels: Map<string, FakeTunnel>;
42
+ seed: (tunnel: Partial<FakeTunnel> & {
43
+ name: string;
44
+ }) => FakeTunnel;
45
+ seen: Seen[];
46
+ };
47
+ export type FakeTunnels = ReturnType<typeof fakeTunnels>;
48
+ //# sourceMappingURL=fake-tunnel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fake-tunnel.d.ts","sourceRoot":"","sources":["../../src/cloudflare/fake-tunnel.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,OAAO,EAAgB,KAAK,IAAI,EAAmB,MAAM,gBAAgB,CAAC;AAE1E,MAAM,MAAM,UAAU,GAAG;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,0GAA0G;IAC1G,SAAS,EAAE,OAAO,CAAC;CACpB,CAAC;AAcF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,+FAA+F;IAC/F,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,QAAQ,GAAG,IAAI,CAAC;IACtD,qGAAqG;IACrG,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,QAAQ,GAAG,IAAI,CAAC;CACrD,CAAC;AAEF,eAAO,MAAM,WAAW,aAAa,iBAAiB;;;;;mBAO9B,OAAO,CAAC,UAAU,CAAC,GAAG;QAAE,IAAI,EAAE,MAAM,CAAA;KAAE,KAAG,UAAU;;CAuF1E,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG,UAAU,CAAC,OAAO,WAAW,CAAC,CAAC"}
@@ -1,10 +1,15 @@
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. 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.
4
+ * ★ Built on vendor SDKs, not hand-rolled HTTP. `R2BucketLock`, `MeshNode`, `CloudflaredTunnel` and
5
+ * `SaasOidcApplication` all call `@distilled.cloud/cloudflare`, the SDK Alchemy's own Cloudflare
6
+ * providers use — R2BucketLock over `/r2` (`getBucketLock`/`putBucketLock`), MeshNode over
7
+ * `/zero-trust`, because `cloudflare@4.5.0` cannot create an HA node (mesh-node-api.ts). None
8
+ * invents a path string.
9
+ * ⚠️ ONE EXCEPTION, A READ: `SaasOidcApplication` writes and looks up names through the SDK, but its
10
+ * responses drop `saas_app`, so `getApp` reads that one object from the wire JSON — the same
11
+ * `GET /accounts/{account_id}/access/apps/{app_id}` the SDK's get verb sends, over the SDK's own
12
+ * `HttpClient`, credentials and base URL (saas-oidc-api.ts).
8
13
  */
9
14
  export type { R2LockRule } from './lock-rules.ts';
10
15
  export { R2BucketLock, type R2BucketLockProps } from './r2-bucket-lock.ts';
@@ -12,6 +17,12 @@ export { MeshNode, MeshNodeProvider, type MeshNodeServices } from './mesh-node.t
12
17
  export type { MeshNodeAttributes, MeshNodeProps } from './mesh-node-form.ts';
13
18
  export { MeshNodeError, type MeshNodeStatus } from './mesh-node-api.ts';
14
19
  export { fetchMeshNodeToken, type MeshNodeRef } from './mesh-node-token.ts';
20
+ export { CloudflaredTunnel, CloudflaredTunnelProvider, type CloudflaredTunnelServices, } from './cloudflared-tunnel.ts';
21
+ export type { CloudflaredTunnelAttributes, CloudflaredTunnelProps, } from './cloudflared-tunnel-form.ts';
22
+ export { CloudflaredTunnelError } from './cloudflared-tunnel-api.ts';
23
+ export { SaasOidcApplication, SaasOidcApplicationProvider } from './saas-oidc.ts';
24
+ export type { OidcGrantType, OidcScope, SaasOidcApplicationAttributes, SaasOidcApplicationProps, } from './saas-oidc-form.ts';
25
+ export { SaasOidcError } from './saas-oidc-error.ts';
15
26
  export { providers } from './providers.ts';
16
27
  export { astroWebsite, viteWebsite, type AstroWebsiteProps, type ViteWebsiteProps, } from './website.ts';
17
28
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cloudflare/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;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,EACL,iBAAiB,EACjB,yBAAyB,EACzB,KAAK,yBAAyB,GAC/B,MAAM,yBAAyB,CAAC;AACjC,YAAY,EACV,2BAA2B,EAC3B,sBAAsB,GACvB,MAAM,8BAA8B,CAAC;AACtC,OAAO,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC;AACrE,OAAO,EAAE,mBAAmB,EAAE,2BAA2B,EAAE,MAAM,gBAAgB,CAAC;AAClF,YAAY,EACV,aAAa,EACb,SAAS,EACT,6BAA6B,EAC7B,wBAAwB,GACzB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EACL,YAAY,EACZ,WAAW,EACX,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,GACtB,MAAM,cAAc,CAAC"}