@homeflare/alchemy 0.2.0 → 0.2.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.
- package/README.md +3 -1
- package/dist/openbao/bao-http.d.ts.map +1 -1
- package/dist/openbao/index.d.ts +8 -3
- package/dist/openbao/index.d.ts.map +1 -1
- package/dist/openbao/index.js +432 -18
- package/dist/openbao/index.js.map +7 -4
- package/package.json +1 -1
- package/src/openbao/bao-http.ts +20 -4
- package/src/openbao/index.ts +12 -3
- package/src/proxmox/client.test.ts +7 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@homeflare/alchemy",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.2",
|
|
4
4
|
"description": "Custom Alchemy providers for Cloudflare, Proxmox, OpenBao, Forgejo and Talos.",
|
|
5
5
|
"homepage": "https://github.com/taslabs-net/homeflare-kit/tree/main/packages/alchemy#readme",
|
|
6
6
|
"bugs": "https://github.com/taslabs-net/homeflare-kit/issues",
|
package/src/openbao/bao-http.ts
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
import * as Context from 'effect/Context';
|
|
15
15
|
import * as Effect from 'effect/Effect';
|
|
16
16
|
import * as Option from 'effect/Option';
|
|
17
|
+
import * as Schedule from 'effect/Schedule';
|
|
17
18
|
import * as Semaphore from 'effect/Semaphore';
|
|
18
19
|
import * as FetchHttpClient from 'effect/unstable/http/FetchHttpClient';
|
|
19
20
|
import * as Headers from 'effect/unstable/http/Headers';
|
|
@@ -66,6 +67,19 @@ export const BaoGate = Context.Reference<Semaphore.Semaphore>('homeflare/openbao
|
|
|
66
67
|
*/
|
|
67
68
|
const TIMEOUT = '60 seconds';
|
|
68
69
|
|
|
70
|
+
/**
|
|
71
|
+
* Mesh in front of a remote OpenBao drops connections under Alchemy's unbounded
|
|
72
|
+
* fan-out. Status 0 is transport, not an OpenBao 4xx — retry twice. Measured
|
|
73
|
+
* 2026-09-16 against api.v.homeflare.dev: a 585-role plan died mid-diff with
|
|
74
|
+
* `no response: (no errors given)` while the vault stayed unsealed.
|
|
75
|
+
*/
|
|
76
|
+
const retryTransport = <A, R>(effect: Effect.Effect<A, BaoError, R>) =>
|
|
77
|
+
Effect.retry(effect, {
|
|
78
|
+
schedule: Schedule.spaced('750 millis'),
|
|
79
|
+
times: 2,
|
|
80
|
+
while: (error: BaoError) => error.status === 0,
|
|
81
|
+
});
|
|
82
|
+
|
|
69
83
|
/**
|
|
70
84
|
* ⛔ EFFECT RECORDS EVERY REQUEST HEADER ON THE CLIENT SPAN and redacts only the names in
|
|
71
85
|
* `Headers.CurrentRedactedNames` — by default authorization, cookie, set-cookie and x-api-key
|
|
@@ -142,10 +156,12 @@ export const baoCall = (
|
|
|
142
156
|
// ★ THE PERMIT WRAPS THE TIMEOUT, NOT THE REVERSE: a call queued behind the gate is not yet
|
|
143
157
|
// talking to OpenBao, so its wait must not count against the 60 seconds.
|
|
144
158
|
const gate = yield* BaoGate;
|
|
145
|
-
const { status, text } = yield*
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
159
|
+
const { status, text } = yield* retryTransport(
|
|
160
|
+
exchange.pipe(
|
|
161
|
+
overSocket(address.socket, method, path),
|
|
162
|
+
redactingToken,
|
|
163
|
+
Semaphore.withPermits(gate, 1),
|
|
164
|
+
),
|
|
149
165
|
);
|
|
150
166
|
const outcome = settle(intent, method, path, status, text);
|
|
151
167
|
return 'error' in outcome ? yield* Effect.fail(outcome.error) : outcome.body;
|
package/src/openbao/index.ts
CHANGED
|
@@ -8,9 +8,18 @@
|
|
|
8
8
|
* ★ Anything unlisted is still reachable by path if you genuinely need it — that is a
|
|
9
9
|
* deliberate, visible act rather than an accident of barrelling.
|
|
10
10
|
*/
|
|
11
|
-
export { BaoAuthRoleProvider } from './auth-role.ts';
|
|
12
|
-
export { BaoCloudflareRoleProvider } from './cloudflare-role.ts';
|
|
13
|
-
export {
|
|
11
|
+
export { BaoAuthRole, BaoAuthRoleProvider } from './auth-role.ts';
|
|
12
|
+
export { BaoCloudflareRole, BaoCloudflareRoleProvider } from './cloudflare-role.ts';
|
|
13
|
+
export {
|
|
14
|
+
CloudflarePermissionGroups,
|
|
15
|
+
permissionGroupsFromEngine,
|
|
16
|
+
permissionGroupsFromLiveRoles,
|
|
17
|
+
} from './cloudflare-permission-groups.ts';
|
|
18
|
+
export type { CloudflareZone, ExpandedRole } from './cloudflare-roles-expand.ts';
|
|
19
|
+
export { expandAccount, expandAll, mountName } from './cloudflare-roles-expand.ts';
|
|
20
|
+
export type { RolesConfig, SurfaceSpec } from './cloudflare-roles-config.ts';
|
|
21
|
+
export { parseRolesConfig } from './cloudflare-roles-config.ts';
|
|
22
|
+
export { BaoMount, BaoMountProvider } from './mount.ts';
|
|
14
23
|
export { BaoPkiRoleProvider } from './pki-role.ts';
|
|
15
24
|
export { BaoPolicy, BaoPolicyProvider } from './policy.ts';
|
|
16
25
|
export { BaoProxmoxRole, BaoProxmoxRoleProvider } from './proxmox-role.ts';
|
|
@@ -55,7 +55,13 @@ const remapFetch = (
|
|
|
55
55
|
url.port = String(port);
|
|
56
56
|
return underlying(url, init);
|
|
57
57
|
}
|
|
58
|
-
|
|
58
|
+
// ⛔ NEVER fall through to the real resolver. `.test` is reserved (RFC 2606) but
|
|
59
|
+
// GitHub's Linux runners sometimes hang on it until node:test's 5s timeout —
|
|
60
|
+
// measured 2026-09-16: release verify failed on "skips an unresolvable first
|
|
61
|
+
// member" while `ci` on the same SHA passed. The file header says no live hosts.
|
|
62
|
+
return Promise.reject(
|
|
63
|
+
Object.assign(new Error(`getaddrinfo ENOTFOUND ${url.hostname}`), { code: 'ENOTFOUND' }),
|
|
64
|
+
);
|
|
59
65
|
},
|
|
60
66
|
{ preconnect: globalThis.fetch.preconnect },
|
|
61
67
|
);
|