@homeflare/distilled-proxmox 0.2.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.
- package/LICENSE +201 -0
- package/README.md +87 -0
- package/dist/credentials.d.ts +41 -0
- package/dist/credentials.d.ts.map +1 -0
- package/dist/credentials.js +67 -0
- package/dist/credentials.js.map +1 -0
- package/dist/errors.d.ts +128 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +91 -0
- package/dist/errors.js.map +1 -0
- package/dist/index.d.ts +30 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +30 -0
- package/dist/index.js.map +1 -0
- package/dist/protocol.d.ts +18 -0
- package/dist/protocol.d.ts.map +1 -0
- package/dist/protocol.js +167 -0
- package/dist/protocol.js.map +1 -0
- package/dist/protocol.test.d.ts +2 -0
- package/dist/protocol.test.d.ts.map +1 -0
- package/dist/protocol.test.js +64 -0
- package/dist/protocol.test.js.map +1 -0
- package/dist/retry.d.ts +50 -0
- package/dist/retry.d.ts.map +1 -0
- package/dist/retry.js +43 -0
- package/dist/retry.js.map +1 -0
- package/dist/services/access.d.ts +975 -0
- package/dist/services/access.d.ts.map +1 -0
- package/dist/services/access.js +1208 -0
- package/dist/services/access.js.map +1 -0
- package/dist/services/cluster.d.ts +5940 -0
- package/dist/services/cluster.d.ts.map +1 -0
- package/dist/services/cluster.js +7405 -0
- package/dist/services/cluster.js.map +1 -0
- package/dist/services/index.d.ts +7 -0
- package/dist/services/index.d.ts.map +1 -0
- package/dist/services/index.js +8 -0
- package/dist/services/index.js.map +1 -0
- package/dist/services/nodes.d.ts +7887 -0
- package/dist/services/nodes.d.ts.map +1 -0
- package/dist/services/nodes.js +10253 -0
- package/dist/services/nodes.js.map +1 -0
- package/dist/services/pools.d.ts +133 -0
- package/dist/services/pools.d.ts.map +1 -0
- package/dist/services/pools.js +183 -0
- package/dist/services/pools.js.map +1 -0
- package/dist/services/storage.d.ts +317 -0
- package/dist/services/storage.d.ts.map +1 -0
- package/dist/services/storage.js +239 -0
- package/dist/services/storage.js.map +1 -0
- package/dist/services/version.d.ts +20 -0
- package/dist/services/version.d.ts.map +1 -0
- package/dist/services/version.js +27 -0
- package/dist/services/version.js.map +1 -0
- package/dist/task.d.ts +51 -0
- package/dist/task.d.ts.map +1 -0
- package/dist/task.js +60 -0
- package/dist/task.js.map +1 -0
- package/dist/traits.d.ts +12 -0
- package/dist/traits.d.ts.map +1 -0
- package/dist/traits.js +14 -0
- package/dist/traits.js.map +1 -0
- package/package.json +81 -0
- package/src/credentials.ts +101 -0
- package/src/errors.ts +152 -0
- package/src/index.ts +33 -0
- package/src/protocol.test.ts +101 -0
- package/src/protocol.ts +204 -0
- package/src/retry.ts +74 -0
- package/src/services/access.ts +2722 -0
- package/src/services/cluster.ts +18550 -0
- package/src/services/index.ts +7 -0
- package/src/services/nodes.ts +25537 -0
- package/src/services/pools.ts +397 -0
- package/src/services/storage.ts +711 -0
- package/src/services/version.ts +54 -0
- package/src/task.ts +87 -0
- package/src/traits.ts +45 -0
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @distilled.cloud/proxmox — Proxmox VE API SDK for Effect.
|
|
3
|
+
*
|
|
4
|
+
* `./services` is generated by `scripts/generate.ts` from the Smithy models
|
|
5
|
+
* in `.generated-specs` (written by `scripts/convert.ts` from PVE's own
|
|
6
|
+
* `apidata.js`, mirrored under `specs/`). Everything else in this folder is
|
|
7
|
+
* hand-written.
|
|
8
|
+
*
|
|
9
|
+
* One module per PVE top-level path segment — `access`, `cluster`,
|
|
10
|
+
* `nodes`, `pools`, `storage`, `version` — the same split the vendor's own
|
|
11
|
+
* api-viewer uses.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```ts
|
|
15
|
+
* import * as Proxmox from "@distilled.cloud/proxmox";
|
|
16
|
+
*
|
|
17
|
+
* const status = yield* Proxmox.Services.nodes.getNodeTaskStatus({
|
|
18
|
+
* node: "pve1",
|
|
19
|
+
* upid: "UPID:pve1:00001234:...",
|
|
20
|
+
* });
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
export * from "./credentials.ts";
|
|
24
|
+
export * from "./errors.ts";
|
|
25
|
+
export * as T from "./traits.ts";
|
|
26
|
+
export { ProxmoxProtocol, type ProxmoxOpError, type ProxmoxOpContext, } from "./protocol.ts";
|
|
27
|
+
export * as Retry from "./retry.ts";
|
|
28
|
+
export * as Services from "./services/index.ts";
|
|
29
|
+
export { awaitTask, type TaskRef, type AwaitTaskOptions } from "./task.ts";
|
|
30
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,cAAc,kBAAkB,CAAC;AACjC,cAAc,aAAa,CAAC;AAC5B,OAAO,KAAK,CAAC,MAAM,aAAa,CAAC;AACjC,OAAO,EACL,eAAe,EACf,KAAK,cAAc,EACnB,KAAK,gBAAgB,GACtB,MAAM,eAAe,CAAC;AACvB,OAAO,KAAK,KAAK,MAAM,YAAY,CAAC;AACpC,OAAO,KAAK,QAAQ,MAAM,qBAAqB,CAAC;AAChD,OAAO,EAAE,SAAS,EAAE,KAAK,OAAO,EAAE,KAAK,gBAAgB,EAAE,MAAM,WAAW,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @distilled.cloud/proxmox — Proxmox VE API SDK for Effect.
|
|
3
|
+
*
|
|
4
|
+
* `./services` is generated by `scripts/generate.ts` from the Smithy models
|
|
5
|
+
* in `.generated-specs` (written by `scripts/convert.ts` from PVE's own
|
|
6
|
+
* `apidata.js`, mirrored under `specs/`). Everything else in this folder is
|
|
7
|
+
* hand-written.
|
|
8
|
+
*
|
|
9
|
+
* One module per PVE top-level path segment — `access`, `cluster`,
|
|
10
|
+
* `nodes`, `pools`, `storage`, `version` — the same split the vendor's own
|
|
11
|
+
* api-viewer uses.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```ts
|
|
15
|
+
* import * as Proxmox from "@distilled.cloud/proxmox";
|
|
16
|
+
*
|
|
17
|
+
* const status = yield* Proxmox.Services.nodes.getNodeTaskStatus({
|
|
18
|
+
* node: "pve1",
|
|
19
|
+
* upid: "UPID:pve1:00001234:...",
|
|
20
|
+
* });
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
export * from "./credentials.js";
|
|
24
|
+
export * from "./errors.js";
|
|
25
|
+
export * as T from "./traits.js";
|
|
26
|
+
export { ProxmoxProtocol, } from "./protocol.js";
|
|
27
|
+
export * as Retry from "./retry.js";
|
|
28
|
+
export * as Services from "./services/index.js";
|
|
29
|
+
export { awaitTask } from "./task.js";
|
|
30
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,cAAc,kBAAkB,CAAC;AACjC,cAAc,aAAa,CAAC;AAC5B,OAAO,KAAK,CAAC,MAAM,aAAa,CAAC;AACjC,OAAO,EACL,eAAe,GAGhB,MAAM,eAAe,CAAC;AACvB,OAAO,KAAK,KAAK,MAAM,YAAY,CAAC;AACpC,OAAO,KAAK,QAAQ,MAAM,qBAAqB,CAAC;AAChD,OAAO,EAAE,SAAS,EAAuC,MAAM,WAAW,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type * as Layer from "effect/Layer";
|
|
2
|
+
import type * as HttpClient from "effect/unstable/http/HttpClient";
|
|
3
|
+
import type * as HttpClientError from "effect/unstable/http/HttpClientError";
|
|
4
|
+
import type * as API from "@distilled.cloud/core/api";
|
|
5
|
+
import { type ConfigError } from "@distilled.cloud/core/errors";
|
|
6
|
+
import { Credentials } from "./credentials.ts";
|
|
7
|
+
import { type DefaultErrors } from "./errors.ts";
|
|
8
|
+
/**
|
|
9
|
+
* Error channel shared by every generated Proxmox operation. Generated
|
|
10
|
+
* service files annotate operations with `API.OperationMethod<I, O,
|
|
11
|
+
* ProxmoxOpError, ProxmoxOpContext>` explicitly so the compiler never
|
|
12
|
+
* infers these back out of the schema generics.
|
|
13
|
+
*/
|
|
14
|
+
export type ProxmoxOpError = DefaultErrors | ConfigError | HttpClientError.HttpClientError;
|
|
15
|
+
/** Context (requirements) shared by every generated Proxmox operation. */
|
|
16
|
+
export type ProxmoxOpContext = Credentials | HttpClient.HttpClient;
|
|
17
|
+
export declare const ProxmoxProtocol: Layer.Layer<API.Protocol>;
|
|
18
|
+
//# sourceMappingURL=protocol.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"protocol.d.ts","sourceRoot":"","sources":["../src/protocol.ts"],"names":[],"mappings":"AAkEA,OAAO,KAAK,KAAK,KAAK,MAAM,cAAc,CAAC;AAE3C,OAAO,KAAK,KAAK,UAAU,MAAM,iCAAiC,CAAC;AACnE,OAAO,KAAK,KAAK,eAAe,MAAM,sCAAsC,CAAC;AAC7E,OAAO,KAAK,KAAK,GAAG,MAAM,2BAA2B,CAAC;AACtD,OAAO,EAGL,KAAK,WAAW,EACjB,MAAM,8BAA8B,CAAC;AAKtC,OAAO,EAAE,WAAW,EAAe,MAAM,kBAAkB,CAAC;AAC5D,OAAO,EAGL,KAAK,aAAa,EACnB,MAAM,aAAa,CAAC;AAErB;;;;;GAKG;AACH,MAAM,MAAM,cAAc,GACtB,aAAa,GACb,WAAW,GACX,eAAe,CAAC,eAAe,CAAC;AAEpC,0EAA0E;AAC1E,MAAM,MAAM,gBAAgB,GAAG,WAAW,GAAG,UAAU,CAAC,UAAU,CAAC;AA6BnE,eAAO,MAAM,eAAe,EAAE,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,CA2ElD,CAAC"}
|
package/dist/protocol.js
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ProxmoxProtocol — the shared bearer-REST protocol instantiated for
|
|
3
|
+
* Proxmox VE. Hand-written; `@distilled.cloud/core` is never changed for a
|
|
4
|
+
* provider's own quirks — they all live here.
|
|
5
|
+
*
|
|
6
|
+
* ## The envelope
|
|
7
|
+
*
|
|
8
|
+
* Every PVE response — success AND failure — is wrapped `{"data": …}`
|
|
9
|
+
* (measured, and already relied on by `packages/alchemy/src/proxmox/
|
|
10
|
+
* client.ts` in `taslabs-net/homeflare-kit`: "EVERY PVE ANSWER IS WRAPPED
|
|
11
|
+
* IN `{"data": ...}`, errors included — a failed call can still be HTTP
|
|
12
|
+
* 200 with `{"data": null}`"). `transformResponse` below unwraps it before
|
|
13
|
+
* `core/protocol-rest`'s schema-driven decode ever sees a payload, so a
|
|
14
|
+
* generated operation's output type is the PAYLOAD, not the envelope.
|
|
15
|
+
*
|
|
16
|
+
* ## Authentication
|
|
17
|
+
*
|
|
18
|
+
* The API-token scheme: `Authorization: PVEAPIToken=<user>@<realm>!
|
|
19
|
+
* <tokenid>=<secret>` (no `Bearer`, and the token id is embedded in the
|
|
20
|
+
* header value itself, not just the credential).
|
|
21
|
+
*
|
|
22
|
+
* ## THE THREE 200-TRAPS
|
|
23
|
+
*
|
|
24
|
+
* (a) **Async POST/PUT/DELETE answer 200 with a UPID and can fail later.**
|
|
25
|
+
* PVE hands back a bare task id string (`"UPID:node:...);"`) for any
|
|
26
|
+
* long-running action — a VM start, a backup, a storage scan — and the
|
|
27
|
+
* 200 means "the task was QUEUED", not "the task succeeded". Handled
|
|
28
|
+
* with real code: `src/task.ts`'s `awaitTask`, which polls
|
|
29
|
+
* `GetNodeTaskStatus` (this package's own generated operation) until
|
|
30
|
+
* `exitstatus` is present, then fails with the typed
|
|
31
|
+
* `ProxmoxTaskFailed` unless it is EXACTLY `"OK"` — PVE also answers
|
|
32
|
+
* `"OK (warnings)"`, which is not a bare-prefix match on purpose (see
|
|
33
|
+
* that file).
|
|
34
|
+
*
|
|
35
|
+
* (b) **Some PUTs answer 200 `{"data":null}` even when nothing changed.**
|
|
36
|
+
* PVE's update handlers commonly return `null` unconditionally on
|
|
37
|
+
* success, whether or not any field actually differed from what was
|
|
38
|
+
* already stored — there is no wire signal that distinguishes "applied"
|
|
39
|
+
* from "already exactly this". THIS IS NOT SOMETHING A PROTOCOL LAYER
|
|
40
|
+
* CAN DETECT OR FIX — it is a statement about business semantics no
|
|
41
|
+
* amount of parsing recovers. Documented here so it travels with the
|
|
42
|
+
* protocol rather than being rediscovered per resource: an update
|
|
43
|
+
* operation's caller (a future kit `Resource`) MUST read the value back
|
|
44
|
+
* with a follow-up GET rather than trust a 200 as proof anything
|
|
45
|
+
* changed.
|
|
46
|
+
*
|
|
47
|
+
* (c) **Permission-filtered lists return 200 + `[]` instead of 403.** A
|
|
48
|
+
* list endpoint called with a token that has no visibility into any
|
|
49
|
+
* member of the collection answers an EMPTY array, not a permission
|
|
50
|
+
* error — indistinguishable, on the wire, from "the collection is
|
|
51
|
+
* genuinely empty". Also undetectable here for the same reason as (b):
|
|
52
|
+
* documented so a caller treats an empty list from a narrowly-scoped
|
|
53
|
+
* token as "unknown", never as "confirmed absent".
|
|
54
|
+
*
|
|
55
|
+
* ## Self-signed TLS
|
|
56
|
+
*
|
|
57
|
+
* A fresh PVE node serves its own self-signed certificate. This is
|
|
58
|
+
* EXPLICITLY NOT handled here, or anywhere in this package: the custom
|
|
59
|
+
* CA / certificate trust decision belongs to the `HttpClient.HttpClient`
|
|
60
|
+
* layer the CALLER provides (Effect's platform HTTP client layers take a
|
|
61
|
+
* `NodeHttpClient.layerConfig` / custom `Agent` for this), never to a
|
|
62
|
+
* distilled protocol module, which only ever builds and decodes requests
|
|
63
|
+
* against whatever transport it is handed. Wiring a custom CA at the
|
|
64
|
+
* `HttpClient` layer is a caller decision to prototype and own.
|
|
65
|
+
*/
|
|
66
|
+
import * as Effect from "effect/Effect";
|
|
67
|
+
import * as Redacted from "effect/Redacted";
|
|
68
|
+
import { BadRequest, HTTP_STATUS_MAP, } from "@distilled.cloud/core/errors";
|
|
69
|
+
import { makeRestProtocol, } from "@distilled.cloud/core/protocol-rest";
|
|
70
|
+
import { Credentials } from "./credentials.js";
|
|
71
|
+
import { ParameterVerificationFailed, UnknownProxmoxError, } from "./errors.js";
|
|
72
|
+
/**
|
|
73
|
+
* PVE's failure envelope is `{"data": null, "message": "...", "errors"?:
|
|
74
|
+
* {…}}` — `message` is the only field every failure carries (the `errors`
|
|
75
|
+
* sub-object, PVE's parameter-verification detail, is read straight from
|
|
76
|
+
* the body in `unknownError` below, not here — `RestErrorEnvelope` has no
|
|
77
|
+
* room for a nested object and `errorEnvelope` only feeds per-op typed-error
|
|
78
|
+
* MATCHING, never the classes themselves).
|
|
79
|
+
*/
|
|
80
|
+
const errorEnvelope = (body) => {
|
|
81
|
+
if (body === null || typeof body !== "object")
|
|
82
|
+
return undefined;
|
|
83
|
+
const b = body;
|
|
84
|
+
return { message: typeof b.message === "string" ? b.message : undefined };
|
|
85
|
+
};
|
|
86
|
+
/**
|
|
87
|
+
* `HTTP_STATUS_MAP` minus 400: PVE's 400 is not one shape (a generic
|
|
88
|
+
* "bad request" some other providers mean by it) — it is SPECIFICALLY the
|
|
89
|
+
* parameter-verification failure, and answering it with the generic
|
|
90
|
+
* `BadRequest` would throw away the per-field `errors` object the vendor
|
|
91
|
+
* always attaches. Dropping the entry here is what lets a 400 fall through
|
|
92
|
+
* to `unknownError`, which reads that object.
|
|
93
|
+
*/
|
|
94
|
+
const PROXMOX_STATUS_MAP = {
|
|
95
|
+
...HTTP_STATUS_MAP,
|
|
96
|
+
};
|
|
97
|
+
delete PROXMOX_STATUS_MAP[400];
|
|
98
|
+
export const ProxmoxProtocol = makeRestProtocol({
|
|
99
|
+
// Resolved on the CALLING fiber per request (the layer is memoized per
|
|
100
|
+
// process); the Credentials service holds an effect, so a token rotated
|
|
101
|
+
// between calls is picked up without rebuilding the layer.
|
|
102
|
+
credentials: Effect.gen(function* () {
|
|
103
|
+
const resolve = yield* Credentials;
|
|
104
|
+
return yield* resolve;
|
|
105
|
+
}),
|
|
106
|
+
baseUrl: (creds) => creds.apiBaseUrl,
|
|
107
|
+
headers: (creds) => ({
|
|
108
|
+
Authorization: `PVEAPIToken=${creds.tokenId}=${Redacted.value(creds.secret)}`,
|
|
109
|
+
Accept: "application/json",
|
|
110
|
+
}),
|
|
111
|
+
errorEnvelope,
|
|
112
|
+
statusMap: PROXMOX_STATUS_MAP,
|
|
113
|
+
// Unwrap PVE's `{"data": …}` envelope BEFORE the schema-driven decode —
|
|
114
|
+
// see the module header. `data` is `null`/absent for a Unit-output
|
|
115
|
+
// operation; `?? {}` is core's own convention for "no body".
|
|
116
|
+
transformResponse: (body) => {
|
|
117
|
+
if (body !== null &&
|
|
118
|
+
typeof body === "object" &&
|
|
119
|
+
"data" in body) {
|
|
120
|
+
return body.data ?? {};
|
|
121
|
+
}
|
|
122
|
+
return body;
|
|
123
|
+
},
|
|
124
|
+
// Reached for a 400 (see PROXMOX_STATUS_MAP above), an operation-declared
|
|
125
|
+
// typed error that didn't match (`ClusterNodeUnreachable`'s 595 — see
|
|
126
|
+
// `patches/nodes/task-polling.json` — when the status is right but the
|
|
127
|
+
// matcher's other fields aren't), or a status with no core mapping at
|
|
128
|
+
// all. `body` is the FULL parsed failure envelope (unlike
|
|
129
|
+
// `errorEnvelope`, which only ever sees `{code?, message?}`), so this is
|
|
130
|
+
// where PVE's per-field `errors` object is actually read.
|
|
131
|
+
//
|
|
132
|
+
// ⛔ A BARE 400 WITHOUT THE `errors` OBJECT MUST NOT FALL INTO
|
|
133
|
+
// `UnknownProxmoxError`. That class is `.pipe(Category.withServerError)`
|
|
134
|
+
// (matching every other provider's Unknown*Error fallback here), and
|
|
135
|
+
// `ServerError` is one of the categories `core/category.ts`'s
|
|
136
|
+
// `isTransientError` retries AUTOMATICALLY — with no caller opt-in —
|
|
137
|
+
// via `makeDefault` (`core/api.ts`: `Option.isSome(opt) ? opt.value :
|
|
138
|
+
// makeDefault` when no retry policy is in context). A malformed
|
|
139
|
+
// request is permanent; retrying it up to `makeDefault`'s cap before
|
|
140
|
+
// finally surfacing would silently multiply every truly-bad call by
|
|
141
|
+
// several attempts and several seconds of backoff for no chance of a
|
|
142
|
+
// different outcome. `BadRequest` (`Category.withBadRequestError`,
|
|
143
|
+
// not retryable) is the honest answer for "PVE said 400 and gave no
|
|
144
|
+
// further structure" — `UnknownProxmoxError` stays for statuses this
|
|
145
|
+
// package genuinely has no mapping for at all (never 400).
|
|
146
|
+
unknownError: ({ status, message, body }) => {
|
|
147
|
+
const b = body !== null && typeof body === "object"
|
|
148
|
+
? body
|
|
149
|
+
: undefined;
|
|
150
|
+
const fieldErrors = b?.errors;
|
|
151
|
+
if (status === 400) {
|
|
152
|
+
if (fieldErrors !== null &&
|
|
153
|
+
typeof fieldErrors === "object" &&
|
|
154
|
+
!Array.isArray(fieldErrors)) {
|
|
155
|
+
const errors = {};
|
|
156
|
+
for (const [k, v] of Object.entries(fieldErrors)) {
|
|
157
|
+
if (typeof v === "string")
|
|
158
|
+
errors[k] = v;
|
|
159
|
+
}
|
|
160
|
+
return new ParameterVerificationFailed({ message, errors });
|
|
161
|
+
}
|
|
162
|
+
return new BadRequest({ message });
|
|
163
|
+
}
|
|
164
|
+
return new UnknownProxmoxError({ status, message, body });
|
|
165
|
+
},
|
|
166
|
+
});
|
|
167
|
+
//# sourceMappingURL=protocol.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"protocol.js","sourceRoot":"","sources":["../src/protocol.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"protocol.test.d.ts","sourceRoot":"","sources":["../src/protocol.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Proves the exact bug an adversarial review of this package caught and
|
|
3
|
+
* this file exists to stop recurring: a bare PVE 400 (no `errors` object)
|
|
4
|
+
* must decode to the non-retryable `BadRequest`, never the
|
|
5
|
+
* `Category.withServerError`-tagged `UnknownProxmoxError` — core's default
|
|
6
|
+
* retry policy treats `ServerError` as transient, so the wrong class would
|
|
7
|
+
* have silently retried a permanent, caller-caused failure several times
|
|
8
|
+
* before surfacing it. See `protocol.ts`'s `unknownError` and
|
|
9
|
+
* `errors.ts`'s module header for the full account.
|
|
10
|
+
*/
|
|
11
|
+
import { describe, expect, test } from "bun:test";
|
|
12
|
+
import * as Effect from "effect/Effect";
|
|
13
|
+
import * as Layer from "effect/Layer";
|
|
14
|
+
import * as HttpClient from "effect/unstable/http/HttpClient";
|
|
15
|
+
import * as HttpClientResponse from "effect/unstable/http/HttpClientResponse";
|
|
16
|
+
import { credentials } from "./credentials.js";
|
|
17
|
+
import { BadRequest, ParameterVerificationFailed, UnknownProxmoxError, } from "./errors.js";
|
|
18
|
+
import * as Retry from "./retry.js";
|
|
19
|
+
import { getNodeTime } from "./services/nodes.js";
|
|
20
|
+
const testCredentials = credentials({
|
|
21
|
+
tokenId: "root@pam!test",
|
|
22
|
+
secret: "test-secret",
|
|
23
|
+
baseUrl: "https://pve.test:8006",
|
|
24
|
+
});
|
|
25
|
+
const fakePve = (status, body) => Layer.succeed(HttpClient.HttpClient, HttpClient.make((request) => Effect.sync(() => HttpClientResponse.fromWeb(request, new Response(JSON.stringify(body), { status })))));
|
|
26
|
+
const call = (status, body) => Effect.runPromise(getNodeTime({ node: "pve1" }).pipe(Retry.none, Effect.provide(Layer.mergeAll(fakePve(status, body), testCredentials)), Effect.flip));
|
|
27
|
+
describe("protocol.ts error decoding", () => {
|
|
28
|
+
test("a bare 400 (no `errors` object) is BadRequest, never UnknownProxmoxError", async () => {
|
|
29
|
+
const error = await call(400, { data: null, message: "invalid parameter" });
|
|
30
|
+
expect(error).toBeInstanceOf(BadRequest);
|
|
31
|
+
expect(error).not.toBeInstanceOf(UnknownProxmoxError);
|
|
32
|
+
expect(error).toMatchObject({ message: "invalid parameter" });
|
|
33
|
+
});
|
|
34
|
+
test("a 400 with an `errors` object is ParameterVerificationFailed, carrying the per-field messages", async () => {
|
|
35
|
+
const error = await call(400, {
|
|
36
|
+
data: null,
|
|
37
|
+
message: "Parameter verification failed.",
|
|
38
|
+
errors: { comment: "value may only be 128 characters long" },
|
|
39
|
+
});
|
|
40
|
+
expect(error).toBeInstanceOf(ParameterVerificationFailed);
|
|
41
|
+
expect(error).toMatchObject({
|
|
42
|
+
message: "Parameter verification failed.",
|
|
43
|
+
errors: { comment: "value may only be 128 characters long" },
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
// 418 is neither in PROXMOX_STATUS_MAP nor >= 500 (the unmapped-5xx
|
|
47
|
+
// fallback in core's makeRestProtocol answers a bare `InternalServerError`
|
|
48
|
+
// for those before `unknownError` is ever reached — see that decode step).
|
|
49
|
+
test("a genuinely unmapped, non-5xx status still falls back to UnknownProxmoxError", async () => {
|
|
50
|
+
const error = await call(418, { data: null, message: "weird" });
|
|
51
|
+
expect(error).toBeInstanceOf(UnknownProxmoxError);
|
|
52
|
+
});
|
|
53
|
+
test('the {"data": ...} envelope is unwrapped before decode', async () => {
|
|
54
|
+
const result = await Effect.runPromise(getNodeTime({ node: "pve1" }).pipe(Effect.provide(Layer.mergeAll(fakePve(200, {
|
|
55
|
+
data: {
|
|
56
|
+
time: 1700000000,
|
|
57
|
+
timezone: "UTC",
|
|
58
|
+
localtime: 1700000000,
|
|
59
|
+
},
|
|
60
|
+
}), testCredentials))));
|
|
61
|
+
expect(result).toMatchObject({ time: 1700000000, timezone: "UTC" });
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
//# sourceMappingURL=protocol.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"protocol.test.js","sourceRoot":"","sources":["../src/protocol.test.ts"],"names":[],"mappings":"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"}
|
package/dist/retry.d.ts
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Proxmox retry surface — a veneer over `@distilled.cloud/core/retry`.
|
|
3
|
+
*
|
|
4
|
+
* The `Retry` service tag is threaded into every generated operation via
|
|
5
|
+
* `API.make({ retry: Retry })`, so a caller-installed policy applies to all
|
|
6
|
+
* Proxmox calls below it and core's `makeDefault` is the fallback when none
|
|
7
|
+
* is provided.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```ts
|
|
11
|
+
* import * as Proxmox from "@distilled.cloud/proxmox";
|
|
12
|
+
*
|
|
13
|
+
* myEffect.pipe(Proxmox.Retry.transient);
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
import * as Context from "effect/Context";
|
|
17
|
+
import * as Effect from "effect/Effect";
|
|
18
|
+
import * as Retries from "@distilled.cloud/core/retry";
|
|
19
|
+
export type Options = Retries.Options;
|
|
20
|
+
export type Factory = Retries.Factory;
|
|
21
|
+
export type Policy = Retries.Policy;
|
|
22
|
+
declare const Retry_base: Context.ServiceClass<Retry, "ProxmoxRetry", Retries.Policy>;
|
|
23
|
+
/** Context tag for configuring retry behavior of Proxmox API calls. */
|
|
24
|
+
export declare class Retry extends Retry_base {
|
|
25
|
+
}
|
|
26
|
+
/** Provides a custom retry policy to every Proxmox API call below it. */
|
|
27
|
+
export declare const policy: {
|
|
28
|
+
(options: Options): <A, E, R>(effect: Effect.Effect<A, E, R>) => Effect.Effect<A, E, Exclude<R, Retry>>;
|
|
29
|
+
(factory: Factory): <A, E, R>(effect: Effect.Effect<A, E, R>) => Effect.Effect<A, E, Exclude<R, Retry>>;
|
|
30
|
+
};
|
|
31
|
+
/** Disables all automatic retries. */
|
|
32
|
+
export declare const none: <A, E, R>(effect: Effect.Effect<A, E, R>) => Effect.Effect<A, E, Exclude<R, Retry>>;
|
|
33
|
+
/**
|
|
34
|
+
* The default retry policy (core's): transient/throttling/retryable errors,
|
|
35
|
+
* capped exponential backoff with jitter, server `retryAfter` hints honored
|
|
36
|
+
* with precedence.
|
|
37
|
+
*/
|
|
38
|
+
export declare const makeDefault: Factory;
|
|
39
|
+
export declare const jittered: <Error, Env>(self: import("effect/Schedule").Schedule<unknown, unknown, Error, Env>) => import("effect/Schedule").Schedule<unknown, unknown, Error, Env>;
|
|
40
|
+
export declare const capped: (max: import("effect/Duration").Duration) => <Error, Env>(self: import("effect/Schedule").Schedule<unknown, unknown, Error, Env>) => import("effect/Schedule").Schedule<unknown, unknown, Error, Env>;
|
|
41
|
+
/** Retry options that retry all throttling errors indefinitely. */
|
|
42
|
+
export declare const throttlingOptions: Options;
|
|
43
|
+
/** Retries all throttling errors indefinitely (honoring server hints). */
|
|
44
|
+
export declare const throttling: <A, E, R>(effect: Effect.Effect<A, E, R>) => Effect.Effect<A, E, Exclude<R, Retry>>;
|
|
45
|
+
/** Retry options that retry all transient errors indefinitely. */
|
|
46
|
+
export declare const transientOptions: Options;
|
|
47
|
+
/** Retries all transient errors indefinitely (honoring server hints). */
|
|
48
|
+
export declare const transient: <A, E, R>(effect: Effect.Effect<A, E, R>) => Effect.Effect<A, E, Exclude<R, Retry>>;
|
|
49
|
+
export {};
|
|
50
|
+
//# sourceMappingURL=retry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"retry.d.ts","sourceRoot":"","sources":["../src/retry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,KAAK,OAAO,MAAM,gBAAgB,CAAC;AAC1C,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AAExC,OAAO,KAAK,OAAO,MAAM,6BAA6B,CAAC;AAEvD,MAAM,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;AACtC,MAAM,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC;AACtC,MAAM,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;;AAEpC,uEAAuE;AACvE,qBAAa,KAAM,SAAQ,UAAgD;CAAG;AAE9E,yEAAyE;AACzE,eAAO,MAAM,MAAM,EAAE;IACnB,CACE,OAAO,EAAE,OAAO,GACf,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EACT,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,KAC3B,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,OAAO,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC;IAC5C,CACE,OAAO,EAAE,OAAO,GACf,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EACT,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,KAC3B,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,OAAO,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC;CAEU,CAAC;AAEzD,sCAAsC;AACtC,eAAO,MAAM,IAAI,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EACzB,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,KAC3B,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,OAAO,CAAC,CAAC,EAAE,KAAK,CAAC,CAEzC,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,WAAW,EAAE,OAA6B,CAAC;AAExD,eAAO,MAAM,QAAQ,0JAAmB,CAAC;AACzC,eAAO,MAAM,MAAM,uMAAiB,CAAC;AAErC,mEAAmE;AACnE,eAAO,MAAM,iBAAiB,EAAE,OAAmC,CAAC;AAEpE,0EAA0E;AAC1E,eAAO,MAAM,UAAU,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAC/B,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,KAC3B,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,OAAO,CAAC,CAAC,EAAE,KAAK,CAAC,CAAqC,CAAC;AAEhF,kEAAkE;AAClE,eAAO,MAAM,gBAAgB,EAAE,OAAkC,CAAC;AAElE,yEAAyE;AACzE,eAAO,MAAM,SAAS,EAAE,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAC9B,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,KAC3B,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,CAAC,EAAE,OAAO,CAAC,CAAC,EAAE,KAAK,CAAC,CAAoC,CAAC"}
|
package/dist/retry.js
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Proxmox retry surface — a veneer over `@distilled.cloud/core/retry`.
|
|
3
|
+
*
|
|
4
|
+
* The `Retry` service tag is threaded into every generated operation via
|
|
5
|
+
* `API.make({ retry: Retry })`, so a caller-installed policy applies to all
|
|
6
|
+
* Proxmox calls below it and core's `makeDefault` is the fallback when none
|
|
7
|
+
* is provided.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```ts
|
|
11
|
+
* import * as Proxmox from "@distilled.cloud/proxmox";
|
|
12
|
+
*
|
|
13
|
+
* myEffect.pipe(Proxmox.Retry.transient);
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
import * as Context from "effect/Context";
|
|
17
|
+
import * as Effect from "effect/Effect";
|
|
18
|
+
import * as Layer from "effect/Layer";
|
|
19
|
+
import * as Retries from "@distilled.cloud/core/retry";
|
|
20
|
+
/** Context tag for configuring retry behavior of Proxmox API calls. */
|
|
21
|
+
export class Retry extends Context.Service()("ProxmoxRetry") {
|
|
22
|
+
}
|
|
23
|
+
/** Provides a custom retry policy to every Proxmox API call below it. */
|
|
24
|
+
export const policy = (optionsOrFactory) => Effect.provide(Layer.succeed(Retry, optionsOrFactory));
|
|
25
|
+
/** Disables all automatic retries. */
|
|
26
|
+
export const none = Effect.provide(Layer.succeed(Retry, { while: () => false }));
|
|
27
|
+
/**
|
|
28
|
+
* The default retry policy (core's): transient/throttling/retryable errors,
|
|
29
|
+
* capped exponential backoff with jitter, server `retryAfter` hints honored
|
|
30
|
+
* with precedence.
|
|
31
|
+
*/
|
|
32
|
+
export const makeDefault = Retries.makeDefault;
|
|
33
|
+
export const jittered = Retries.jittered;
|
|
34
|
+
export const capped = Retries.capped;
|
|
35
|
+
/** Retry options that retry all throttling errors indefinitely. */
|
|
36
|
+
export const throttlingOptions = Retries.throttlingOptions;
|
|
37
|
+
/** Retries all throttling errors indefinitely (honoring server hints). */
|
|
38
|
+
export const throttling = policy(Retries.throttlingFactory);
|
|
39
|
+
/** Retry options that retry all transient errors indefinitely. */
|
|
40
|
+
export const transientOptions = Retries.transientOptions;
|
|
41
|
+
/** Retries all transient errors indefinitely (honoring server hints). */
|
|
42
|
+
export const transient = policy(Retries.transientFactory);
|
|
43
|
+
//# sourceMappingURL=retry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"retry.js","sourceRoot":"","sources":["../src/retry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,KAAK,OAAO,MAAM,gBAAgB,CAAC;AAC1C,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AACxC,OAAO,KAAK,KAAK,MAAM,cAAc,CAAC;AACtC,OAAO,KAAK,OAAO,MAAM,6BAA6B,CAAC;AAMvD,uEAAuE;AACvE,MAAM,OAAO,KAAM,SAAQ,OAAO,CAAC,OAAO,EAAiB,CAAC,cAAc,CAAC;CAAG;AAE9E,yEAAyE;AACzE,MAAM,CAAC,MAAM,MAAM,GAWf,CAAC,gBAAmC,EAAE,EAAE,CAC1C,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,EAAE,gBAAgB,CAAC,CAAC,CAAC;AAEzD,sCAAsC;AACtC,MAAM,CAAC,MAAM,IAAI,GAE6B,MAAM,CAAC,OAAO,CAC1D,KAAK,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,KAAK,EAAE,CAAC,CAC7C,CAAC;AAEF;;;;GAIG;AACH,MAAM,CAAC,MAAM,WAAW,GAAY,OAAO,CAAC,WAAW,CAAC;AAExD,MAAM,CAAC,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC;AACzC,MAAM,CAAC,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;AAErC,mEAAmE;AACnE,MAAM,CAAC,MAAM,iBAAiB,GAAY,OAAO,CAAC,iBAAiB,CAAC;AAEpE,0EAA0E;AAC1E,MAAM,CAAC,MAAM,UAAU,GAEuB,MAAM,CAAC,OAAO,CAAC,iBAAiB,CAAC,CAAC;AAEhF,kEAAkE;AAClE,MAAM,CAAC,MAAM,gBAAgB,GAAY,OAAO,CAAC,gBAAgB,CAAC;AAElE,yEAAyE;AACzE,MAAM,CAAC,MAAM,SAAS,GAEwB,MAAM,CAAC,OAAO,CAAC,gBAAgB,CAAC,CAAC"}
|