@homeflare/distilled-proxmox 0.2.0 → 0.3.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 (53) hide show
  1. package/dist/delete-query-binding.test.d.ts +2 -0
  2. package/dist/delete-query-binding.test.d.ts.map +1 -0
  3. package/dist/delete-query-binding.test.js +49 -0
  4. package/dist/delete-query-binding.test.js.map +1 -0
  5. package/dist/errors.d.ts +12 -5
  6. package/dist/errors.d.ts.map +1 -1
  7. package/dist/errors.js +12 -5
  8. package/dist/errors.js.map +1 -1
  9. package/dist/index.d.ts +0 -1
  10. package/dist/index.d.ts.map +1 -1
  11. package/dist/index.js +0 -1
  12. package/dist/index.js.map +1 -1
  13. package/dist/not-found-errors.test.d.ts +2 -0
  14. package/dist/not-found-errors.test.d.ts.map +1 -0
  15. package/dist/not-found-errors.test.js +59 -0
  16. package/dist/not-found-errors.test.js.map +1 -0
  17. package/dist/protocol.d.ts.map +1 -1
  18. package/dist/protocol.js +9 -7
  19. package/dist/protocol.js.map +1 -1
  20. package/dist/services/access.d.ts +22 -2
  21. package/dist/services/access.d.ts.map +1 -1
  22. package/dist/services/access.js +17 -9
  23. package/dist/services/access.js.map +1 -1
  24. package/dist/services/cluster.d.ts.map +1 -1
  25. package/dist/services/cluster.js +30 -77
  26. package/dist/services/cluster.js.map +1 -1
  27. package/dist/services/nodes.d.ts +22 -2
  28. package/dist/services/nodes.d.ts.map +1 -1
  29. package/dist/services/nodes.js +60 -72
  30. package/dist/services/nodes.js.map +1 -1
  31. package/dist/services/pools.d.ts.map +1 -1
  32. package/dist/services/pools.js +2 -7
  33. package/dist/services/pools.js.map +1 -1
  34. package/dist/services/storage.d.ts +11 -1
  35. package/dist/services/storage.d.ts.map +1 -1
  36. package/dist/services/storage.js +8 -1
  37. package/dist/services/storage.js.map +1 -1
  38. package/package.json +1 -7
  39. package/src/delete-query-binding.test.ts +65 -0
  40. package/src/errors.ts +12 -5
  41. package/src/index.ts +0 -1
  42. package/src/not-found-errors.test.ts +112 -0
  43. package/src/protocol.ts +9 -7
  44. package/src/services/access.ts +25 -11
  45. package/src/services/cluster.ts +30 -79
  46. package/src/services/nodes.ts +71 -74
  47. package/src/services/pools.ts +2 -9
  48. package/src/services/storage.ts +12 -2
  49. package/dist/task.d.ts +0 -51
  50. package/dist/task.d.ts.map +0 -1
  51. package/dist/task.js +0 -60
  52. package/dist/task.js.map +0 -1
  53. package/src/task.ts +0 -87
@@ -12,6 +12,32 @@ import * as Retry from "../retry.ts";
12
12
 
13
13
  export type { ProxmoxOpError, ProxmoxOpContext };
14
14
 
15
+ /** The Ceph filesystem does not exist. PVE's `destroyfs` dies synchronously with `no such cephfs '<name>'` when the named filesystem is already gone (an async task is never even queued, so this surfaces directly from the DELETE call, not from polling its UPID). This shape states the honest status (404) while matching the wire's real one — 500, the same class PVE answers for a missing user/group/storage/pool. Traced to source, not paraphrased (correcting an earlier draft of this patch, which carried forward the kit's own paraphrase, 'no such cephfs' with no filesystem name — the kit comments it cited, `ceph-fs-wire.ts:203-206` and `ceph-fs.ts:230-233`, never claimed to quote the wire text verbatim either): `pve-manager` (commit f6997e698c7933ea8e62319e2bf1bf7262daa56a, "bump version to 9.2.11") `PVE/API2/Ceph/FS.pm:327`, inside the `DELETE .../ceph/fs/{name}` handler's `code => sub`: `die "no such cephfs '$fs_name'\n" if !$fs;` — reached only when `$fs_name` (the `name` path label) is not found in `PVE::Ceph::Tools::ls_fs()`'s list. No live DELETE was run to confirm this byte-for-byte (per this pass's read-only-GET constraint); the regex is anchored on the verified-from-source prefix and object-kind qualifier only, deliberately not asserting the exact trailing punctuation/newline. */
16
+ export class CephFsNotFound
17
+ extends /*@__PURE__*/ T.applyErrorMatchers(
18
+ /*@__PURE__*/ S.TaggedError<CephFsNotFound>()("CephFsNotFound", {
19
+ message: S.String,
20
+ }).pipe(C.withBadRequestError),
21
+ [{ status: 500, message: { matches: "^no such cephfs '[^']*'" } }],
22
+ ) {}
23
+
24
+ /** The Ceph pool does not exist. PVE shells out to `ceph osd pool get` and reports its failure verbatim as HTTP 500 with `{"data":null,"message":"error with 'osd pool get': mon_cmd failed - unrecognized pool '<name>'\n"}` — a generic command failure, not a parsed field, but distinctively named ('unrecognized pool'), unlike ZfsPool's equivalent 'exit code 1' shape (deliberately left untyped — see zfs-pool-read-failure.test.ts: that message names no object kind at all, so typing it would risk folding an unrelated zpool-command failure into 'absent'). This shape states the honest status (404) while matching the wire's real one. Call site traced to source: `pve-manager` (git.proxmox.com/git/pve-manager.git, commit f6997e698c7933ea8e62319e2bf1bf7262daa56a, "bump version to 9.2.11" — the SDK's own pinned version, matching `pve-docs`'s own repoid for this snapshot) `PVE/API2/Ceph/Pool.pm` (~line 850), inside the `status` GET handler: `$rados->mon_command({ prefix => 'osd pool get', pool => "$pool", var => 'all' })`. `mon_cmd failed - unrecognized pool '<name>'` is Ceph's/librados's own wire text for a missing pool, not a PVE-authored string, so it was not independently re-derived from Perl source (there is none to read — it crosses into Ceph's own C++ `mon_command` implementation, out of scope for this pass). Measured: `taslabs-net/homeflare-kit`'s `packages/alchemy/src/proxmox/ceph-pool-write.test.ts:5-14`, live cluster TB4 `n2`, `GET /nodes/{node}/ceph/pool/{name}/status`, 2026-09-24, read-role, read-only probe. */
25
+ export class CephPoolNotFound
26
+ extends /*@__PURE__*/ T.applyErrorMatchers(
27
+ /*@__PURE__*/ S.TaggedError<CephPoolNotFound>()("CephPoolNotFound", {
28
+ message: S.String,
29
+ }).pipe(C.withBadRequestError),
30
+ [
31
+ {
32
+ status: 500,
33
+ message: {
34
+ matches:
35
+ "^error with 'osd pool get': mon_cmd failed - unrecognized pool",
36
+ },
37
+ },
38
+ ],
39
+ ) {}
40
+
15
41
  /** PVE forwarded this call to another cluster member and could not reach it (pveproxy/pvedaemon's own connection-failure status, not a standard HTTP code). Transient — the target node may be mid-reboot or between cluster-membership changes. */
16
42
  export class ClusterNodeUnreachable
17
43
  extends /*@__PURE__*/ T.applyErrorMatchers(
@@ -3913,14 +3939,13 @@ export const DeleteNodeCephFsRequest = /*@__PURE__*/ S.suspend(() =>
3913
3939
  S.Struct({
3914
3940
  node: S.String.pipe(T.Label()),
3915
3941
  name: S.String.pipe(T.Label()),
3916
- remove_pools: S.optional(S.String.pipe(T.Body("remove-pools"))),
3917
- remove_storages: S.optional(S.String.pipe(T.Body("remove-storages"))),
3942
+ remove_pools: S.optional(S.String.pipe(T.Query("remove-pools"))),
3943
+ remove_storages: S.optional(S.String.pipe(T.Query("remove-storages"))),
3918
3944
  }).pipe(
3919
3945
  T.Http({
3920
3946
  method: "DELETE",
3921
3947
  uri: "/nodes/{node}/ceph/fs/{name}",
3922
3948
  code: 200,
3923
- contentType: "form-urlencoded",
3924
3949
  }),
3925
3950
  ),
3926
3951
  ).annotate({
@@ -4018,13 +4043,12 @@ export const DeleteNodeCephOsdRequest = /*@__PURE__*/ S.suspend(() =>
4018
4043
  S.Struct({
4019
4044
  node: S.String.pipe(T.Label()),
4020
4045
  osdid: S.String.pipe(T.Label()),
4021
- cleanup: S.optional(S.String),
4046
+ cleanup: S.optional(S.String.pipe(T.Query())),
4022
4047
  }).pipe(
4023
4048
  T.Http({
4024
4049
  method: "DELETE",
4025
4050
  uri: "/nodes/{node}/ceph/osd/{osdid}",
4026
4051
  code: 200,
4027
- contentType: "form-urlencoded",
4028
4052
  }),
4029
4053
  ),
4030
4054
  ).annotate({
@@ -4052,15 +4076,14 @@ export const DeleteNodeCephPoolRequest = /*@__PURE__*/ S.suspend(() =>
4052
4076
  S.Struct({
4053
4077
  node: S.String.pipe(T.Label()),
4054
4078
  name: S.String.pipe(T.Label()),
4055
- force: S.optional(S.String),
4056
- remove_ecprofile: S.optional(S.String),
4057
- remove_storages: S.optional(S.String),
4079
+ force: S.optional(S.String.pipe(T.Query())),
4080
+ remove_ecprofile: S.optional(S.String.pipe(T.Query())),
4081
+ remove_storages: S.optional(S.String.pipe(T.Query())),
4058
4082
  }).pipe(
4059
4083
  T.Http({
4060
4084
  method: "DELETE",
4061
4085
  uri: "/nodes/{node}/ceph/pool/{name}",
4062
4086
  code: 200,
4063
- contentType: "form-urlencoded",
4064
4087
  }),
4065
4088
  ),
4066
4089
  ).annotate({
@@ -4106,13 +4129,12 @@ export interface DeleteNodeCertificateCustomRequest {
4106
4129
  export const DeleteNodeCertificateCustomRequest = /*@__PURE__*/ S.suspend(() =>
4107
4130
  S.Struct({
4108
4131
  node: S.String.pipe(T.Label()),
4109
- restart: S.optional(S.String),
4132
+ restart: S.optional(S.String.pipe(T.Query())),
4110
4133
  }).pipe(
4111
4134
  T.Http({
4112
4135
  method: "DELETE",
4113
4136
  uri: "/nodes/{node}/certificates/custom",
4114
4137
  code: 200,
4115
- contentType: "form-urlencoded",
4116
4138
  }),
4117
4139
  ),
4118
4140
  ).annotate({
@@ -4138,14 +4160,13 @@ export const DeleteNodeDiskDirectoryRequest = /*@__PURE__*/ S.suspend(() =>
4138
4160
  S.Struct({
4139
4161
  node: S.String.pipe(T.Label()),
4140
4162
  name: S.String.pipe(T.Label()),
4141
- cleanup_config: S.optional(S.String.pipe(T.Body("cleanup-config"))),
4142
- cleanup_disks: S.optional(S.String.pipe(T.Body("cleanup-disks"))),
4163
+ cleanup_config: S.optional(S.String.pipe(T.Query("cleanup-config"))),
4164
+ cleanup_disks: S.optional(S.String.pipe(T.Query("cleanup-disks"))),
4143
4165
  }).pipe(
4144
4166
  T.Http({
4145
4167
  method: "DELETE",
4146
4168
  uri: "/nodes/{node}/disks/directory/{name}",
4147
4169
  code: 200,
4148
- contentType: "form-urlencoded",
4149
4170
  }),
4150
4171
  ),
4151
4172
  ).annotate({
@@ -4171,14 +4192,13 @@ export const DeleteNodeDiskLvmRequest = /*@__PURE__*/ S.suspend(() =>
4171
4192
  S.Struct({
4172
4193
  node: S.String.pipe(T.Label()),
4173
4194
  name: S.String.pipe(T.Label()),
4174
- cleanup_config: S.optional(S.String.pipe(T.Body("cleanup-config"))),
4175
- cleanup_disks: S.optional(S.String.pipe(T.Body("cleanup-disks"))),
4195
+ cleanup_config: S.optional(S.String.pipe(T.Query("cleanup-config"))),
4196
+ cleanup_disks: S.optional(S.String.pipe(T.Query("cleanup-disks"))),
4176
4197
  }).pipe(
4177
4198
  T.Http({
4178
4199
  method: "DELETE",
4179
4200
  uri: "/nodes/{node}/disks/lvm/{name}",
4180
4201
  code: 200,
4181
- contentType: "form-urlencoded",
4182
4202
  }),
4183
4203
  ),
4184
4204
  ).annotate({
@@ -4206,15 +4226,14 @@ export const DeleteNodeDiskLvmthinRequest = /*@__PURE__*/ S.suspend(() =>
4206
4226
  S.Struct({
4207
4227
  node: S.String.pipe(T.Label()),
4208
4228
  name: S.String.pipe(T.Label()),
4209
- cleanup_config: S.optional(S.String.pipe(T.Body("cleanup-config"))),
4210
- cleanup_disks: S.optional(S.String.pipe(T.Body("cleanup-disks"))),
4211
- volume_group: S.String.pipe(T.Body("volume-group")),
4229
+ cleanup_config: S.optional(S.String.pipe(T.Query("cleanup-config"))),
4230
+ cleanup_disks: S.optional(S.String.pipe(T.Query("cleanup-disks"))),
4231
+ volume_group: S.String.pipe(T.Query("volume-group")),
4212
4232
  }).pipe(
4213
4233
  T.Http({
4214
4234
  method: "DELETE",
4215
4235
  uri: "/nodes/{node}/disks/lvmthin/{name}",
4216
4236
  code: 200,
4217
- contentType: "form-urlencoded",
4218
4237
  }),
4219
4238
  ),
4220
4239
  ).annotate({
@@ -4240,14 +4259,13 @@ export const DeleteNodeDiskZfsRequest = /*@__PURE__*/ S.suspend(() =>
4240
4259
  S.Struct({
4241
4260
  node: S.String.pipe(T.Label()),
4242
4261
  name: S.String.pipe(T.Label()),
4243
- cleanup_config: S.optional(S.String.pipe(T.Body("cleanup-config"))),
4244
- cleanup_disks: S.optional(S.String.pipe(T.Body("cleanup-disks"))),
4262
+ cleanup_config: S.optional(S.String.pipe(T.Query("cleanup-config"))),
4263
+ cleanup_disks: S.optional(S.String.pipe(T.Query("cleanup-disks"))),
4245
4264
  }).pipe(
4246
4265
  T.Http({
4247
4266
  method: "DELETE",
4248
4267
  uri: "/nodes/{node}/disks/zfs/{name}",
4249
4268
  code: 200,
4250
- contentType: "form-urlencoded",
4251
4269
  }),
4252
4270
  ),
4253
4271
  ).annotate({
@@ -4271,13 +4289,12 @@ export const DeleteNodeFirewallRuleRequest = /*@__PURE__*/ S.suspend(() =>
4271
4289
  S.Struct({
4272
4290
  node: S.String.pipe(T.Label()),
4273
4291
  pos: S.String.pipe(T.Label()),
4274
- digest: S.optional(S.String),
4292
+ digest: S.optional(S.String.pipe(T.Query())),
4275
4293
  }).pipe(
4276
4294
  T.Http({
4277
4295
  method: "DELETE",
4278
4296
  uri: "/nodes/{node}/firewall/rules/{pos}",
4279
4297
  code: 200,
4280
- contentType: "form-urlencoded",
4281
4298
  }),
4282
4299
  ),
4283
4300
  ).annotate({
@@ -4306,17 +4323,12 @@ export const DeleteNodeLxcRequest = /*@__PURE__*/ S.suspend(() =>
4306
4323
  node: S.String.pipe(T.Label()),
4307
4324
  vmid: S.String.pipe(T.Label()),
4308
4325
  destroy_unreferenced_disks: S.optional(
4309
- S.String.pipe(T.Body("destroy-unreferenced-disks")),
4326
+ S.String.pipe(T.Query("destroy-unreferenced-disks")),
4310
4327
  ),
4311
- force: S.optional(S.String),
4312
- purge: S.optional(S.String),
4328
+ force: S.optional(S.String.pipe(T.Query())),
4329
+ purge: S.optional(S.String.pipe(T.Query())),
4313
4330
  }).pipe(
4314
- T.Http({
4315
- method: "DELETE",
4316
- uri: "/nodes/{node}/lxc/{vmid}",
4317
- code: 200,
4318
- contentType: "form-urlencoded",
4319
- }),
4331
+ T.Http({ method: "DELETE", uri: "/nodes/{node}/lxc/{vmid}", code: 200 }),
4320
4332
  ),
4321
4333
  ).annotate({
4322
4334
  identifier: "DeleteNodeLxcRequest",
@@ -4341,13 +4353,12 @@ export const DeleteNodeLxcFirewallAliasRequest = /*@__PURE__*/ S.suspend(() =>
4341
4353
  node: S.String.pipe(T.Label()),
4342
4354
  vmid: S.String.pipe(T.Label()),
4343
4355
  name: S.String.pipe(T.Label()),
4344
- digest: S.optional(S.String),
4356
+ digest: S.optional(S.String.pipe(T.Query())),
4345
4357
  }).pipe(
4346
4358
  T.Http({
4347
4359
  method: "DELETE",
4348
4360
  uri: "/nodes/{node}/lxc/{vmid}/firewall/aliases/{name}",
4349
4361
  code: 200,
4350
- contentType: "form-urlencoded",
4351
4362
  }),
4352
4363
  ),
4353
4364
  ).annotate({
@@ -4373,13 +4384,12 @@ export const DeleteNodeLxcFirewallIpsetRequest = /*@__PURE__*/ S.suspend(() =>
4373
4384
  node: S.String.pipe(T.Label()),
4374
4385
  vmid: S.String.pipe(T.Label()),
4375
4386
  name: S.String.pipe(T.Label()),
4376
- force: S.optional(S.String),
4387
+ force: S.optional(S.String.pipe(T.Query())),
4377
4388
  }).pipe(
4378
4389
  T.Http({
4379
4390
  method: "DELETE",
4380
4391
  uri: "/nodes/{node}/lxc/{vmid}/firewall/ipset/{name}",
4381
4392
  code: 200,
4382
- contentType: "form-urlencoded",
4383
4393
  }),
4384
4394
  ),
4385
4395
  ).annotate({
@@ -4407,13 +4417,12 @@ export const DeleteNodeLxcFirewallIpset2Request = /*@__PURE__*/ S.suspend(() =>
4407
4417
  vmid: S.String.pipe(T.Label()),
4408
4418
  name: S.String.pipe(T.Label()),
4409
4419
  cidr: S.String.pipe(T.Label()),
4410
- digest: S.optional(S.String),
4420
+ digest: S.optional(S.String.pipe(T.Query())),
4411
4421
  }).pipe(
4412
4422
  T.Http({
4413
4423
  method: "DELETE",
4414
4424
  uri: "/nodes/{node}/lxc/{vmid}/firewall/ipset/{name}/{cidr}",
4415
4425
  code: 200,
4416
- contentType: "form-urlencoded",
4417
4426
  }),
4418
4427
  ),
4419
4428
  ).annotate({
@@ -4439,13 +4448,12 @@ export const DeleteNodeLxcFirewallRuleRequest = /*@__PURE__*/ S.suspend(() =>
4439
4448
  node: S.String.pipe(T.Label()),
4440
4449
  vmid: S.String.pipe(T.Label()),
4441
4450
  pos: S.String.pipe(T.Label()),
4442
- digest: S.optional(S.String),
4451
+ digest: S.optional(S.String.pipe(T.Query())),
4443
4452
  }).pipe(
4444
4453
  T.Http({
4445
4454
  method: "DELETE",
4446
4455
  uri: "/nodes/{node}/lxc/{vmid}/firewall/rules/{pos}",
4447
4456
  code: 200,
4448
- contentType: "form-urlencoded",
4449
4457
  }),
4450
4458
  ),
4451
4459
  ).annotate({
@@ -4471,13 +4479,12 @@ export const DeleteNodeLxcSnapshotRequest = /*@__PURE__*/ S.suspend(() =>
4471
4479
  node: S.String.pipe(T.Label()),
4472
4480
  vmid: S.String.pipe(T.Label()),
4473
4481
  snapname: S.String.pipe(T.Label()),
4474
- force: S.optional(S.String),
4482
+ force: S.optional(S.String.pipe(T.Query())),
4475
4483
  }).pipe(
4476
4484
  T.Http({
4477
4485
  method: "DELETE",
4478
4486
  uri: "/nodes/{node}/lxc/{vmid}/snapshot/{snapname}",
4479
4487
  code: 200,
4480
- contentType: "form-urlencoded",
4481
4488
  }),
4482
4489
  ),
4483
4490
  ).annotate({
@@ -4552,17 +4559,12 @@ export const DeleteNodeQemuRequest = /*@__PURE__*/ S.suspend(() =>
4552
4559
  node: S.String.pipe(T.Label()),
4553
4560
  vmid: S.String.pipe(T.Label()),
4554
4561
  destroy_unreferenced_disks: S.optional(
4555
- S.String.pipe(T.Body("destroy-unreferenced-disks")),
4562
+ S.String.pipe(T.Query("destroy-unreferenced-disks")),
4556
4563
  ),
4557
- purge: S.optional(S.String),
4558
- skiplock: S.optional(S.String),
4564
+ purge: S.optional(S.String.pipe(T.Query())),
4565
+ skiplock: S.optional(S.String.pipe(T.Query())),
4559
4566
  }).pipe(
4560
- T.Http({
4561
- method: "DELETE",
4562
- uri: "/nodes/{node}/qemu/{vmid}",
4563
- code: 200,
4564
- contentType: "form-urlencoded",
4565
- }),
4567
+ T.Http({ method: "DELETE", uri: "/nodes/{node}/qemu/{vmid}", code: 200 }),
4566
4568
  ),
4567
4569
  ).annotate({
4568
4570
  identifier: "DeleteNodeQemuRequest",
@@ -4587,13 +4589,12 @@ export const DeleteNodeQemuFirewallAliasRequest = /*@__PURE__*/ S.suspend(() =>
4587
4589
  node: S.String.pipe(T.Label()),
4588
4590
  vmid: S.String.pipe(T.Label()),
4589
4591
  name: S.String.pipe(T.Label()),
4590
- digest: S.optional(S.String),
4592
+ digest: S.optional(S.String.pipe(T.Query())),
4591
4593
  }).pipe(
4592
4594
  T.Http({
4593
4595
  method: "DELETE",
4594
4596
  uri: "/nodes/{node}/qemu/{vmid}/firewall/aliases/{name}",
4595
4597
  code: 200,
4596
- contentType: "form-urlencoded",
4597
4598
  }),
4598
4599
  ),
4599
4600
  ).annotate({
@@ -4619,13 +4620,12 @@ export const DeleteNodeQemuFirewallIpsetRequest = /*@__PURE__*/ S.suspend(() =>
4619
4620
  node: S.String.pipe(T.Label()),
4620
4621
  vmid: S.String.pipe(T.Label()),
4621
4622
  name: S.String.pipe(T.Label()),
4622
- force: S.optional(S.String),
4623
+ force: S.optional(S.String.pipe(T.Query())),
4623
4624
  }).pipe(
4624
4625
  T.Http({
4625
4626
  method: "DELETE",
4626
4627
  uri: "/nodes/{node}/qemu/{vmid}/firewall/ipset/{name}",
4627
4628
  code: 200,
4628
- contentType: "form-urlencoded",
4629
4629
  }),
4630
4630
  ),
4631
4631
  ).annotate({
@@ -4653,13 +4653,12 @@ export const DeleteNodeQemuFirewallIpset2Request = /*@__PURE__*/ S.suspend(() =>
4653
4653
  vmid: S.String.pipe(T.Label()),
4654
4654
  name: S.String.pipe(T.Label()),
4655
4655
  cidr: S.String.pipe(T.Label()),
4656
- digest: S.optional(S.String),
4656
+ digest: S.optional(S.String.pipe(T.Query())),
4657
4657
  }).pipe(
4658
4658
  T.Http({
4659
4659
  method: "DELETE",
4660
4660
  uri: "/nodes/{node}/qemu/{vmid}/firewall/ipset/{name}/{cidr}",
4661
4661
  code: 200,
4662
- contentType: "form-urlencoded",
4663
4662
  }),
4664
4663
  ),
4665
4664
  ).annotate({
@@ -4685,13 +4684,12 @@ export const DeleteNodeQemuFirewallRuleRequest = /*@__PURE__*/ S.suspend(() =>
4685
4684
  node: S.String.pipe(T.Label()),
4686
4685
  vmid: S.String.pipe(T.Label()),
4687
4686
  pos: S.String.pipe(T.Label()),
4688
- digest: S.optional(S.String),
4687
+ digest: S.optional(S.String.pipe(T.Query())),
4689
4688
  }).pipe(
4690
4689
  T.Http({
4691
4690
  method: "DELETE",
4692
4691
  uri: "/nodes/{node}/qemu/{vmid}/firewall/rules/{pos}",
4693
4692
  code: 200,
4694
- contentType: "form-urlencoded",
4695
4693
  }),
4696
4694
  ),
4697
4695
  ).annotate({
@@ -4717,13 +4715,12 @@ export const DeleteNodeQemuSnapshotRequest = /*@__PURE__*/ S.suspend(() =>
4717
4715
  node: S.String.pipe(T.Label()),
4718
4716
  vmid: S.String.pipe(T.Label()),
4719
4717
  snapname: S.String.pipe(T.Label()),
4720
- force: S.optional(S.String),
4718
+ force: S.optional(S.String.pipe(T.Query())),
4721
4719
  }).pipe(
4722
4720
  T.Http({
4723
4721
  method: "DELETE",
4724
4722
  uri: "/nodes/{node}/qemu/{vmid}/snapshot/{snapname}",
4725
4723
  code: 200,
4726
- contentType: "form-urlencoded",
4727
4724
  }),
4728
4725
  ),
4729
4726
  ).annotate({
@@ -4749,13 +4746,12 @@ export const DeleteNodeStorageContentRequest = /*@__PURE__*/ S.suspend(() =>
4749
4746
  node: S.String.pipe(T.Label()),
4750
4747
  storage: S.String.pipe(T.Label()),
4751
4748
  volume: S.String.pipe(T.Label()),
4752
- delay: S.optional(S.String),
4749
+ delay: S.optional(S.String.pipe(T.Query())),
4753
4750
  }).pipe(
4754
4751
  T.Http({
4755
4752
  method: "DELETE",
4756
4753
  uri: "/nodes/{node}/storage/{storage}/content/{volume}",
4757
4754
  code: 200,
4758
- contentType: "form-urlencoded",
4759
4755
  }),
4760
4756
  ),
4761
4757
  ).annotate({
@@ -4787,15 +4783,16 @@ export const DeleteNodeStoragePrunebackupsRequest = /*@__PURE__*/ S.suspend(
4787
4783
  S.Struct({
4788
4784
  node: S.String.pipe(T.Label()),
4789
4785
  storage: S.String.pipe(T.Label()),
4790
- prune_backups: S.optional(S.String.pipe(T.Body("prune-backups"))),
4791
- type: S.optional(DeleteNodeStoragePrunebackupsRequestType),
4792
- vmid: S.optional(S.String),
4786
+ prune_backups: S.optional(S.String.pipe(T.Query("prune-backups"))),
4787
+ type: S.optional(
4788
+ DeleteNodeStoragePrunebackupsRequestType.pipe(T.Query()),
4789
+ ),
4790
+ vmid: S.optional(S.String.pipe(T.Query())),
4793
4791
  }).pipe(
4794
4792
  T.Http({
4795
4793
  method: "DELETE",
4796
4794
  uri: "/nodes/{node}/storage/{storage}/prunebackups",
4797
4795
  code: 200,
4798
- contentType: "form-urlencoded",
4799
4796
  }),
4800
4797
  ),
4801
4798
  ).annotate({
@@ -21206,7 +21203,7 @@ export const createNodeWakeonlan: API.OperationMethod<
21206
21203
  retry: Retry.Retry,
21207
21204
  }));
21208
21205
 
21209
- export type DeleteNodeCephFsError = ProxmoxOpError;
21206
+ export type DeleteNodeCephFsError = CephFsNotFound | ProxmoxOpError;
21210
21207
  /** Destroy a Ceph filesystem. Refuses if any PVE storage entry of type 'cephfs' still references the filesystem and is not disabled. Optionally also removes the storage entries and/or the underlying metadata and data pools. (root-privileged endpoint) */
21211
21208
  export const deleteNodeCephFs: API.OperationMethod<
21212
21209
  DeleteNodeCephFsRequest,
@@ -21216,7 +21213,7 @@ export const deleteNodeCephFs: API.OperationMethod<
21216
21213
  > = /*@__PURE__*/ API.make(() => ({
21217
21214
  input: DeleteNodeCephFsRequest,
21218
21215
  output: DeleteNodeCephFsResponse,
21219
- errors: [],
21216
+ errors: [CephFsNotFound],
21220
21217
  protocol: ProxmoxProtocol,
21221
21218
  retry: Retry.Retry,
21222
21219
  }));
@@ -21911,7 +21908,7 @@ export const getNodeCephPool: API.OperationMethod<
21911
21908
  retry: Retry.Retry,
21912
21909
  }));
21913
21910
 
21914
- export type GetNodeCephPoolStatusError = ProxmoxOpError;
21911
+ export type GetNodeCephPoolStatusError = CephPoolNotFound | ProxmoxOpError;
21915
21912
  /** Show the current pool status. (root-privileged endpoint) */
21916
21913
  export const getNodeCephPoolStatus: API.OperationMethod<
21917
21914
  GetNodeCephPoolStatusRequest,
@@ -21921,7 +21918,7 @@ export const getNodeCephPoolStatus: API.OperationMethod<
21921
21918
  > = /*@__PURE__*/ API.make(() => ({
21922
21919
  input: GetNodeCephPoolStatusRequest,
21923
21920
  output: GetNodeCephPoolStatusResponse,
21924
- errors: [],
21921
+ errors: [CephPoolNotFound],
21925
21922
  protocol: ProxmoxProtocol,
21926
21923
  retry: Retry.Retry,
21927
21924
  }));
@@ -61,15 +61,8 @@ export interface DeletePoolsRequest {
61
61
  }
62
62
  export const DeletePoolsRequest = /*@__PURE__*/ S.suspend(() =>
63
63
  S.Struct({
64
- poolid: S.String,
65
- }).pipe(
66
- T.Http({
67
- method: "DELETE",
68
- uri: "/pools",
69
- code: 200,
70
- contentType: "form-urlencoded",
71
- }),
72
- ),
64
+ poolid: S.String.pipe(T.Query()),
65
+ }).pipe(T.Http({ method: "DELETE", uri: "/pools", code: 200 })),
73
66
  ).annotate({
74
67
  identifier: "DeletePoolsRequest",
75
68
  }) as any as S.Schema<DeletePoolsRequest>;
@@ -1,6 +1,7 @@
1
1
  // AUTO-GENERATED by scripts/generate.ts from .generated-specs (apidata.js, via scripts/apidoc.ts — see its header). Do not edit.
2
2
  import * as S from "@distilled.cloud/core/schema";
3
3
  import * as API from "@distilled.cloud/core/api";
4
+ import * as C from "@distilled.cloud/core/category";
4
5
  import * as T from "../traits.ts";
5
6
  import {
6
7
  ProxmoxProtocol,
@@ -11,6 +12,15 @@ import * as Retry from "../retry.ts";
11
12
 
12
13
  export type { ProxmoxOpError, ProxmoxOpContext };
13
14
 
15
+ /** The storage ID does not exist. PVE answers this as HTTP 500 with `{"message":"storage '<id>' does not exist\n","data":null}` — this shape states the honest status (404) while matching the wire's real one. Traced to source, not paraphrased: `pve-storage` (git.proxmox.com/git/pve-storage.git, commit 97fbef3b13d0b686f016a4901b7a984d3b213967, package version 9.1.11) `src/PVE/Storage.pm:259`, `sub storage_config`: `die "storage '$storeid' does not exist\n" if (!$noerr && !$scfg);`. Reachability confirmed: `src/PVE/API2/Storage/Config.pm`'s `GET .../storage/{storage}` handler (`description => "Read storage configuration."`, matching this operation's own doc string exactly) calls `storage_config` via the `$api_storage_config` helper with no `$noerr`. Corroborated by live wire evidence: `taslabs-net/homeflare-kit`'s `packages/alchemy/src/proxmox/storage-wire.ts:78-84` and `storage-read-failure.test.ts:44-47`, live cluster TB4, `GET /storage/hf-measure-nonexistent-probe`, 2026-09-24, admin/read-only lane, exact wire body `{"message":"storage '<id>' does not exist\n","data":null}` at HTTP 500 — byte-for-byte what the source predicts. */
16
+ export class StorageNotFound
17
+ extends /*@__PURE__*/ T.applyErrorMatchers(
18
+ /*@__PURE__*/ S.TaggedError<StorageNotFound>()("StorageNotFound", {
19
+ message: S.String,
20
+ }).pipe(C.withBadRequestError),
21
+ [{ status: 500, message: { matches: "^storage '[^']*' does not exist" } }],
22
+ ) {}
23
+
14
24
  export type CreateStorageRequestFormat = "raw" | "qcow2" | "subvol" | "vmdk";
15
25
  export const CreateStorageRequestFormat = S.String;
16
26
 
@@ -665,7 +675,7 @@ export const deleteStorage: API.OperationMethod<
665
675
  retry: Retry.Retry,
666
676
  }));
667
677
 
668
- export type GetStorageError = ProxmoxOpError;
678
+ export type GetStorageError = StorageNotFound | ProxmoxOpError;
669
679
  /** Read storage configuration. */
670
680
  export const getStorage: API.OperationMethod<
671
681
  GetStorageRequest,
@@ -675,7 +685,7 @@ export const getStorage: API.OperationMethod<
675
685
  > = /*@__PURE__*/ API.make(() => ({
676
686
  input: GetStorageRequest,
677
687
  output: GetStorageResponse,
678
- errors: [],
688
+ errors: [StorageNotFound],
679
689
  protocol: ProxmoxProtocol,
680
690
  retry: Retry.Retry,
681
691
  }));
package/dist/task.d.ts DELETED
@@ -1,51 +0,0 @@
1
- /**
2
- * Awaiting a PVE task — trap (a) from `src/protocol.ts`'s header.
3
- *
4
- * PVE answers a long-running action (VM start, backup, migration, …) with
5
- * HTTP 200 and a bare UPID string the moment the task is QUEUED, not once
6
- * it finishes. The only way to know whether it actually succeeded is to
7
- * poll `GET /nodes/{node}/tasks/{upid}/status` — this package's generated
8
- * `Services.nodes.getNodeTaskStatus` — until the response carries an
9
- * `exitstatus`, and then check that value.
10
- *
11
- * ⛔ `exitstatus` IS COMPARED FOR EXACT EQUALITY TO `"OK"`, NEVER A PREFIX
12
- * OR SUBSTRING CHECK. PVE also answers `"OK (warnings)"` (the task
13
- * finished with recoverable warnings) — `startsWith("OK")` would treat
14
- * that as an ordinary success and swallow the warning entirely, and
15
- * `includes("OK")` is even looser. Anything other than the literal
16
- * string `"OK"` — `"OK (warnings)"` included — fails with
17
- * {@link ProxmoxTaskFailed}, carrying the vendor's own text verbatim so a
18
- * caller that wants to treat warnings as success can still do so by
19
- * matching on the message.
20
- *
21
- * `awaitTask` does not itself apply a deadline — wrap the call with
22
- * `Effect.timeout` (or `Retry.transient`/a caller-supplied policy) the way
23
- * any other Effect program composes one; a fixed timeout baked in here
24
- * would be wrong for a backup that legitimately runs for hours and right
25
- * for a config reload that should fail fast, and this module cannot know
26
- * which operation queued the UPID it was handed.
27
- */
28
- import * as Effect from "effect/Effect";
29
- import type * as Duration from "effect/Duration";
30
- import type { ProxmoxOpContext } from "./protocol.ts";
31
- import { ProxmoxTaskFailed } from "./errors.ts";
32
- import type { GetNodeTaskStatusError, GetNodeTaskStatusResponse } from "./services/nodes.ts";
33
- /** Identifies one PVE task. `node` is the cluster member that OWNS the task — see the module header. */
34
- export interface TaskRef {
35
- readonly node: string;
36
- readonly upid: string;
37
- }
38
- export interface AwaitTaskOptions {
39
- /** Delay between polls. Default 1 second. */
40
- readonly pollInterval?: Duration.Input;
41
- }
42
- /**
43
- * Poll a task to completion. Resolves with the FINAL status response (so a
44
- * caller can read `pid`/`starttime`/etc. without a second call) once
45
- * `exitstatus` is exactly `"OK"`; fails with {@link ProxmoxTaskFailed}
46
- * for any other `exitstatus`, or with whatever `getNodeTaskStatus` itself
47
- * failed with (a typed HTTP error, `ClusterNodeUnreachable` if the node
48
- * this task lives on drops out of the cluster mid-poll, …).
49
- */
50
- export declare const awaitTask: (ref: TaskRef, options?: AwaitTaskOptions) => Effect.Effect<GetNodeTaskStatusResponse, ProxmoxTaskFailed | GetNodeTaskStatusError, ProxmoxOpContext>;
51
- //# sourceMappingURL=task.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"task.d.ts","sourceRoot":"","sources":["../src/task.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AACxC,OAAO,KAAK,KAAK,QAAQ,MAAM,iBAAiB,CAAC;AACjD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACtD,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAEhD,OAAO,KAAK,EACV,sBAAsB,EACtB,yBAAyB,EAC1B,MAAM,qBAAqB,CAAC;AAE7B,wGAAwG;AACxG,MAAM,WAAW,OAAO;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,gBAAgB;IAC/B,6CAA6C;IAC7C,QAAQ,CAAC,YAAY,CAAC,EAAE,QAAQ,CAAC,KAAK,CAAC;CACxC;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,SAAS,QACf,OAAO,YACH,gBAAgB,KACxB,MAAM,CAAC,MAAM,CACd,yBAAyB,EACzB,iBAAiB,GAAG,sBAAsB,EAC1C,gBAAgB,CAwBd,CAAC"}
package/dist/task.js DELETED
@@ -1,60 +0,0 @@
1
- /**
2
- * Awaiting a PVE task — trap (a) from `src/protocol.ts`'s header.
3
- *
4
- * PVE answers a long-running action (VM start, backup, migration, …) with
5
- * HTTP 200 and a bare UPID string the moment the task is QUEUED, not once
6
- * it finishes. The only way to know whether it actually succeeded is to
7
- * poll `GET /nodes/{node}/tasks/{upid}/status` — this package's generated
8
- * `Services.nodes.getNodeTaskStatus` — until the response carries an
9
- * `exitstatus`, and then check that value.
10
- *
11
- * ⛔ `exitstatus` IS COMPARED FOR EXACT EQUALITY TO `"OK"`, NEVER A PREFIX
12
- * OR SUBSTRING CHECK. PVE also answers `"OK (warnings)"` (the task
13
- * finished with recoverable warnings) — `startsWith("OK")` would treat
14
- * that as an ordinary success and swallow the warning entirely, and
15
- * `includes("OK")` is even looser. Anything other than the literal
16
- * string `"OK"` — `"OK (warnings)"` included — fails with
17
- * {@link ProxmoxTaskFailed}, carrying the vendor's own text verbatim so a
18
- * caller that wants to treat warnings as success can still do so by
19
- * matching on the message.
20
- *
21
- * `awaitTask` does not itself apply a deadline — wrap the call with
22
- * `Effect.timeout` (or `Retry.transient`/a caller-supplied policy) the way
23
- * any other Effect program composes one; a fixed timeout baked in here
24
- * would be wrong for a backup that legitimately runs for hours and right
25
- * for a config reload that should fail fast, and this module cannot know
26
- * which operation queued the UPID it was handed.
27
- */
28
- import * as Effect from "effect/Effect";
29
- import { ProxmoxTaskFailed } from "./errors.js";
30
- import * as nodes from "./services/nodes.js";
31
- /**
32
- * Poll a task to completion. Resolves with the FINAL status response (so a
33
- * caller can read `pid`/`starttime`/etc. without a second call) once
34
- * `exitstatus` is exactly `"OK"`; fails with {@link ProxmoxTaskFailed}
35
- * for any other `exitstatus`, or with whatever `getNodeTaskStatus` itself
36
- * failed with (a typed HTTP error, `ClusterNodeUnreachable` if the node
37
- * this task lives on drops out of the cluster mid-poll, …).
38
- */
39
- export const awaitTask = (ref, options = {}) => Effect.gen(function* () {
40
- const pollInterval = options.pollInterval ?? "1 second";
41
- while (true) {
42
- const status = yield* nodes.getNodeTaskStatus({
43
- node: ref.node,
44
- upid: ref.upid,
45
- });
46
- if (status.exitstatus === undefined) {
47
- yield* Effect.sleep(pollInterval);
48
- continue;
49
- }
50
- if (status.exitstatus !== "OK") {
51
- return yield* Effect.fail(new ProxmoxTaskFailed({
52
- node: ref.node,
53
- upid: ref.upid,
54
- exitstatus: status.exitstatus,
55
- }));
56
- }
57
- return status;
58
- }
59
- });
60
- //# sourceMappingURL=task.js.map
package/dist/task.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"task.js","sourceRoot":"","sources":["../src/task.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,OAAO,KAAK,MAAM,MAAM,eAAe,CAAC;AAGxC,OAAO,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,KAAK,KAAK,MAAM,qBAAqB,CAAC;AAiB7C;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,SAAS,GAAG,CACvB,GAAY,EACZ,OAAO,GAAqB,EAAE,EAK9B,EAAE,CACF,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;IAClB,MAAM,YAAY,GAAG,OAAO,CAAC,YAAY,IAAI,UAAU,CAAC;IACxD,OAAO,IAAI,EAAE,CAAC;QACZ,MAAM,MAAM,GAAG,KAAK,CAAC,CAAC,KAAK,CAAC,iBAAiB,CAAC;YAC5C,IAAI,EAAE,GAAG,CAAC,IAAI;YACd,IAAI,EAAE,GAAG,CAAC,IAAI;SACf,CAAC,CAAC;QACH,IAAI,MAAM,CAAC,UAAU,KAAK,SAAS,EAAE,CAAC;YACpC,KAAK,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,YAAY,CAAC,CAAC;YAClC,SAAS;QACX,CAAC;QACD,IAAI,MAAM,CAAC,UAAU,KAAK,IAAI,EAAE,CAAC;YAC/B,OAAO,KAAK,CAAC,CAAC,MAAM,CAAC,IAAI,CACvB,IAAI,iBAAiB,CAAC;gBACpB,IAAI,EAAE,GAAG,CAAC,IAAI;gBACd,IAAI,EAAE,GAAG,CAAC,IAAI;gBACd,UAAU,EAAE,MAAM,CAAC,UAAU;aAC9B,CAAC,CACH,CAAC;QACJ,CAAC;QACD,OAAO,MAAM,CAAC;IAChB,CAAC;AACH,CAAC,CAAC,CAAC"}