@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.
Files changed (78) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +87 -0
  3. package/dist/credentials.d.ts +41 -0
  4. package/dist/credentials.d.ts.map +1 -0
  5. package/dist/credentials.js +67 -0
  6. package/dist/credentials.js.map +1 -0
  7. package/dist/errors.d.ts +128 -0
  8. package/dist/errors.d.ts.map +1 -0
  9. package/dist/errors.js +91 -0
  10. package/dist/errors.js.map +1 -0
  11. package/dist/index.d.ts +30 -0
  12. package/dist/index.d.ts.map +1 -0
  13. package/dist/index.js +30 -0
  14. package/dist/index.js.map +1 -0
  15. package/dist/protocol.d.ts +18 -0
  16. package/dist/protocol.d.ts.map +1 -0
  17. package/dist/protocol.js +167 -0
  18. package/dist/protocol.js.map +1 -0
  19. package/dist/protocol.test.d.ts +2 -0
  20. package/dist/protocol.test.d.ts.map +1 -0
  21. package/dist/protocol.test.js +64 -0
  22. package/dist/protocol.test.js.map +1 -0
  23. package/dist/retry.d.ts +50 -0
  24. package/dist/retry.d.ts.map +1 -0
  25. package/dist/retry.js +43 -0
  26. package/dist/retry.js.map +1 -0
  27. package/dist/services/access.d.ts +975 -0
  28. package/dist/services/access.d.ts.map +1 -0
  29. package/dist/services/access.js +1208 -0
  30. package/dist/services/access.js.map +1 -0
  31. package/dist/services/cluster.d.ts +5940 -0
  32. package/dist/services/cluster.d.ts.map +1 -0
  33. package/dist/services/cluster.js +7405 -0
  34. package/dist/services/cluster.js.map +1 -0
  35. package/dist/services/index.d.ts +7 -0
  36. package/dist/services/index.d.ts.map +1 -0
  37. package/dist/services/index.js +8 -0
  38. package/dist/services/index.js.map +1 -0
  39. package/dist/services/nodes.d.ts +7887 -0
  40. package/dist/services/nodes.d.ts.map +1 -0
  41. package/dist/services/nodes.js +10253 -0
  42. package/dist/services/nodes.js.map +1 -0
  43. package/dist/services/pools.d.ts +133 -0
  44. package/dist/services/pools.d.ts.map +1 -0
  45. package/dist/services/pools.js +183 -0
  46. package/dist/services/pools.js.map +1 -0
  47. package/dist/services/storage.d.ts +317 -0
  48. package/dist/services/storage.d.ts.map +1 -0
  49. package/dist/services/storage.js +239 -0
  50. package/dist/services/storage.js.map +1 -0
  51. package/dist/services/version.d.ts +20 -0
  52. package/dist/services/version.d.ts.map +1 -0
  53. package/dist/services/version.js +27 -0
  54. package/dist/services/version.js.map +1 -0
  55. package/dist/task.d.ts +51 -0
  56. package/dist/task.d.ts.map +1 -0
  57. package/dist/task.js +60 -0
  58. package/dist/task.js.map +1 -0
  59. package/dist/traits.d.ts +12 -0
  60. package/dist/traits.d.ts.map +1 -0
  61. package/dist/traits.js +14 -0
  62. package/dist/traits.js.map +1 -0
  63. package/package.json +81 -0
  64. package/src/credentials.ts +101 -0
  65. package/src/errors.ts +152 -0
  66. package/src/index.ts +33 -0
  67. package/src/protocol.test.ts +101 -0
  68. package/src/protocol.ts +204 -0
  69. package/src/retry.ts +74 -0
  70. package/src/services/access.ts +2722 -0
  71. package/src/services/cluster.ts +18550 -0
  72. package/src/services/index.ts +7 -0
  73. package/src/services/nodes.ts +25537 -0
  74. package/src/services/pools.ts +397 -0
  75. package/src/services/storage.ts +711 -0
  76. package/src/services/version.ts +54 -0
  77. package/src/task.ts +87 -0
  78. package/src/traits.ts +45 -0
@@ -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"}
@@ -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,2 @@
1
+ export {};
2
+ //# sourceMappingURL=protocol.test.d.ts.map
@@ -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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"}
@@ -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"}