@homeflare/distilled-proxmox-backup 0.2.0 → 0.3.1

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/README.md +19 -17
  2. package/dist/errors.d.ts +6 -5
  3. package/dist/errors.d.ts.map +1 -1
  4. package/dist/errors.js.map +1 -1
  5. package/dist/missing-resource.test.d.ts +2 -0
  6. package/dist/missing-resource.test.d.ts.map +1 -0
  7. package/dist/missing-resource.test.js +62 -0
  8. package/dist/missing-resource.test.js.map +1 -0
  9. package/dist/protocol-boundaries.test.d.ts +2 -0
  10. package/dist/protocol-boundaries.test.d.ts.map +1 -0
  11. package/dist/protocol-boundaries.test.js +50 -0
  12. package/dist/protocol-boundaries.test.js.map +1 -0
  13. package/dist/protocol-codecs.d.ts +23 -0
  14. package/dist/protocol-codecs.d.ts.map +1 -0
  15. package/dist/protocol-codecs.js +49 -0
  16. package/dist/protocol-codecs.js.map +1 -0
  17. package/dist/protocol-codecs.test.d.ts +2 -0
  18. package/dist/protocol-codecs.test.d.ts.map +1 -0
  19. package/dist/protocol-codecs.test.js +58 -0
  20. package/dist/protocol-codecs.test.js.map +1 -0
  21. package/dist/protocol-raw-root.test.d.ts +2 -0
  22. package/dist/protocol-raw-root.test.d.ts.map +1 -0
  23. package/dist/protocol-raw-root.test.js +48 -0
  24. package/dist/protocol-raw-root.test.js.map +1 -0
  25. package/dist/protocol.d.ts +1 -0
  26. package/dist/protocol.d.ts.map +1 -1
  27. package/dist/protocol.js +4 -1
  28. package/dist/protocol.js.map +1 -1
  29. package/dist/services/access.d.ts.map +1 -1
  30. package/dist/services/access.js +5 -16
  31. package/dist/services/access.js.map +1 -1
  32. package/dist/services/admin.d.ts.map +1 -1
  33. package/dist/services/admin.js +11 -14
  34. package/dist/services/admin.js.map +1 -1
  35. package/dist/services/config.d.ts +44 -4
  36. package/dist/services/config.d.ts.map +1 -1
  37. package/dist/services/config.js +56 -83
  38. package/dist/services/config.js.map +1 -1
  39. package/dist/services/nodes.d.ts.map +1 -1
  40. package/dist/services/nodes.js +2 -4
  41. package/dist/services/nodes.js.map +1 -1
  42. package/package.json +1 -1
  43. package/src/errors.ts +7 -11
  44. package/src/missing-resource.test.ts +98 -0
  45. package/src/protocol-boundaries.test.ts +60 -0
  46. package/src/protocol-codecs.test.ts +112 -0
  47. package/src/protocol-codecs.ts +75 -0
  48. package/src/protocol-raw-root.test.ts +84 -0
  49. package/src/protocol.ts +62 -58
  50. package/src/services/access.ts +5 -16
  51. package/src/services/admin.ts +13 -14
  52. package/src/services/config.ts +72 -95
  53. package/src/services/nodes.ts +2 -4
@@ -0,0 +1,112 @@
1
+ import { expect, test } from "bun:test";
2
+ import * as Effect from "effect/Effect";
3
+ import * as Layer from "effect/Layer";
4
+ import * as HttpClient from "effect/unstable/http/HttpClient";
5
+ import type { HttpClientRequest } from "effect/unstable/http/HttpClientRequest";
6
+ import * as HttpClientResponse from "effect/unstable/http/HttpClientResponse";
7
+ import { credentials } from "./credentials.ts";
8
+ import * as Retry from "./retry.ts";
9
+ import {
10
+ getConfigNotificationMatcher,
11
+ putConfigNotificationMatcher,
12
+ } from "./services/config.ts";
13
+
14
+ const credential = credentials({
15
+ baseUrl: "https://pbs.test:8007",
16
+ tokenId: "test@pbs!fake",
17
+ secret: "fake-not-real",
18
+ });
19
+
20
+ const fakePbs = (body: unknown, see?: (request: HttpClientRequest) => void) =>
21
+ Layer.succeed(
22
+ HttpClient.HttpClient,
23
+ HttpClient.make((request) =>
24
+ Effect.sync(() => {
25
+ see?.(request);
26
+ return HttpClientResponse.fromWeb(
27
+ request,
28
+ Response.json({ data: body }),
29
+ );
30
+ }),
31
+ ),
32
+ );
33
+
34
+ test("PBS arrays use repeated keys and Unit data:null remains a successful write", async () => {
35
+ let pairs: [string, string][] = [];
36
+ const client = fakePbs(null, (request) => {
37
+ expect(request.body._tag).toBe("Uint8Array");
38
+ if (request.body._tag === "Uint8Array") {
39
+ pairs = [
40
+ ...new URLSearchParams(new TextDecoder().decode(request.body.body)),
41
+ ];
42
+ }
43
+ });
44
+ await Effect.runPromise(
45
+ putConfigNotificationMatcher({
46
+ name: "pager",
47
+ target: ["mail-to-root", "hook"],
48
+ delete: ["match-field", "match-calendar"],
49
+ }).pipe(Retry.none, Effect.provide(Layer.mergeAll(client, credential))),
50
+ );
51
+ expect(pairs.filter(([key]) => key === "target")).toEqual([
52
+ ["target", "mail-to-root"],
53
+ ["target", "hook"],
54
+ ]);
55
+ expect(pairs.filter(([key]) => key === "delete")).toEqual([
56
+ ["delete", "match-field"],
57
+ ["delete", "match-calendar"],
58
+ ]);
59
+ });
60
+
61
+ for (const body of [null, { comment: "secret-like-text-must-not-appear" }]) {
62
+ test("incomplete resource output fails without exposing response contents", async () => {
63
+ const error = await Effect.runPromise(
64
+ getConfigNotificationMatcher({ name: "pager" }).pipe(
65
+ Retry.none,
66
+ Effect.flip,
67
+ Effect.provide(Layer.mergeAll(fakePbs(body), credential)),
68
+ ),
69
+ );
70
+ expect(error).toMatchObject({ _tag: "ProxmoxBackupParseError" });
71
+ expect(JSON.stringify(error)).not.toContain(
72
+ "secret-like-text-must-not-appear",
73
+ );
74
+ });
75
+ }
76
+
77
+ test("valid resource output preserves fields omitted from the vendor schema", async () => {
78
+ const result = await Effect.runPromise(
79
+ getConfigNotificationMatcher({ name: "pager" }).pipe(
80
+ Retry.none,
81
+ Effect.provide(
82
+ Layer.mergeAll(
83
+ fakePbs({ name: "pager", future_field: "keep-me" }),
84
+ credential,
85
+ ),
86
+ ),
87
+ ),
88
+ );
89
+ expect(result).toMatchObject({ name: "pager", future_field: "keep-me" });
90
+ });
91
+
92
+ test("mapped NotFound is in the operation's typed union, including a plain-text response", async () => {
93
+ const client = Layer.succeed(
94
+ HttpClient.HttpClient,
95
+ HttpClient.make((request) =>
96
+ Effect.succeed(
97
+ HttpClientResponse.fromWeb(
98
+ request,
99
+ new Response("matcher 'pager' not found", { status: 404 }),
100
+ ),
101
+ ),
102
+ ),
103
+ );
104
+ const result = await Effect.runPromise(
105
+ getConfigNotificationMatcher({ name: "pager" }).pipe(
106
+ Retry.none,
107
+ Effect.catchTag("NotFound", () => Effect.succeed(undefined)),
108
+ Effect.provide(Layer.mergeAll(client, credential)),
109
+ ),
110
+ );
111
+ expect(result).toBeUndefined();
112
+ });
@@ -0,0 +1,75 @@
1
+ /**
2
+ * PBS 4.2.6-1's form arrays and successful response validation.
3
+ *
4
+ * The vendor's array parameters are repeated keys (`target=a&target=b`), not the Stripe
5
+ * bracket-index encoding used by core's generic REST builder. Measured against the PBS
6
+ * notification provider's existing protocol fixtures, 2026-09-24: indexed headers/secrets
7
+ * disappear from its read-back, and matchers perpetually drift. Preserve core path/auth/key
8
+ * mapping and adapt only its top-level indexed form fields at this vendor boundary.
9
+ */
10
+ import * as API from "@distilled.cloud/core/api";
11
+ import * as Effect from "effect/Effect";
12
+ import * as Layer from "effect/Layer";
13
+ import * as Schema from "effect/Schema";
14
+ import * as HttpClientRequest from "effect/unstable/http/HttpClientRequest";
15
+ import { ProxmoxBackupParseError } from "./errors.ts";
16
+
17
+ const repeatedFormKeys = (request: HttpClientRequest.HttpClientRequest) => {
18
+ if (
19
+ request.body._tag !== "Uint8Array" ||
20
+ request.body.contentType !== "application/x-www-form-urlencoded"
21
+ )
22
+ return request;
23
+ const fields = new URLSearchParams(
24
+ new TextDecoder().decode(request.body.body),
25
+ );
26
+ const repeated: [string, string][] = [];
27
+ for (const [key, value] of fields) {
28
+ repeated.push([key.replace(/^([^\[\]]+)\[\d+\]$/, "$1"), value]);
29
+ }
30
+ return HttpClientRequest.bodyUrlParams(request, repeated);
31
+ };
32
+
33
+ /**
34
+ * Core REST decode maps wire keys but does not validate required fields. In particular,
35
+ * PBS data:null unwrapped to {} used to look like a real resource to a caller. Validate
36
+ * with the operation's generated schema: Unit operations still accept {}, while a GET
37
+ * missing its required name/id fails. Preserve the mapped payload after validation: vendor
38
+ * fields absent from the published schema must not silently disappear. Core has already
39
+ * decoded the payload, so validate the schema's Type side instead of re-decoding transforms.
40
+ * Keep server data out of the parse error: notification responses can contain header
41
+ * credentials, and a schema diagnostic can reproduce them.
42
+ */
43
+ export const withPbsCodecs = (
44
+ base: Layer.Layer<API.Protocol>,
45
+ ): Layer.Layer<API.Protocol> =>
46
+ Layer.effect(
47
+ API.Protocol,
48
+ Effect.gen(function* () {
49
+ const protocol = yield* API.Protocol;
50
+ return API.Protocol.of({
51
+ encode: (args) =>
52
+ protocol.encode(args).pipe(Effect.map(repeatedFormKeys)),
53
+ decode: (args) =>
54
+ protocol.decode(args).pipe(
55
+ Effect.flatMap((body) =>
56
+ args.config.output === undefined
57
+ ? Effect.succeed(body)
58
+ : (Schema.decodeUnknownEffect(
59
+ Schema.toType(args.config.output),
60
+ )(body).pipe(
61
+ Effect.as(body),
62
+ Effect.mapError(
63
+ () =>
64
+ new ProxmoxBackupParseError({
65
+ body: undefined,
66
+ cause:
67
+ "Response does not match the operation's declared output schema",
68
+ }),
69
+ ),
70
+ ) as Effect.Effect<unknown>),
71
+ ),
72
+ ),
73
+ });
74
+ }),
75
+ ).pipe(Layer.provide(base));
@@ -0,0 +1,84 @@
1
+ import { expect, test } from "bun:test";
2
+ import * as Effect from "effect/Effect";
3
+ import * as Layer from "effect/Layer";
4
+ import * as HttpClient from "effect/unstable/http/HttpClient";
5
+ import * as HttpClientResponse from "effect/unstable/http/HttpClientResponse";
6
+ import { credentials } from "./credentials.ts";
7
+ import type { ProxmoxBackupOpContext } from "./protocol.ts";
8
+ import * as Retry from "./retry.ts";
9
+ import {
10
+ deleteConfigDatastore,
11
+ getConfigAcmeTos,
12
+ listConfigDatastore,
13
+ } from "./services/config.ts";
14
+
15
+ const run = <A, E>(
16
+ operation: Effect.Effect<A, E, ProxmoxBackupOpContext>,
17
+ data: unknown,
18
+ ) =>
19
+ Effect.runPromise(
20
+ operation.pipe(
21
+ Retry.none,
22
+ Effect.provide(
23
+ Layer.mergeAll(
24
+ credentials({
25
+ baseUrl: "https://pbs.test:8007",
26
+ tokenId: "test@pbs!fake",
27
+ secret: "fake-not-real",
28
+ }),
29
+ Layer.succeed(
30
+ HttpClient.HttpClient,
31
+ HttpClient.make((request) =>
32
+ Effect.succeed(
33
+ HttpClientResponse.fromWeb(request, Response.json({ data })),
34
+ ),
35
+ ),
36
+ ),
37
+ ),
38
+ ),
39
+ ),
40
+ );
41
+
42
+ test("raw-root string responses retain task IDs and ACME terms URLs", async () => {
43
+ const upid =
44
+ "UPID:pbs:00000001:00000002:00000003:delete-datastore:test:root@pam:";
45
+ expect(await run(deleteConfigDatastore({ name: "test" }), upid)).toBe(upid);
46
+ const url = "https://acme.test/terms";
47
+ expect(await run(getConfigAcmeTos({}), url)).toBe(url);
48
+ });
49
+
50
+ test("raw-root lists retain empty lists, map wire keys, and preserve extra fields", async () => {
51
+ expect(await run(listConfigDatastore({}), [])).toEqual([]);
52
+ const expected = [
53
+ { name: "test", path: "/backup/test", keep_last: 2, future_field: "keep" },
54
+ ];
55
+ expect(
56
+ await run(listConfigDatastore({}), [
57
+ {
58
+ name: "test",
59
+ path: "/backup/test",
60
+ "keep-last": 2,
61
+ future_field: "keep",
62
+ },
63
+ ]),
64
+ ).toEqual(expected);
65
+ });
66
+
67
+ // These operations are not Unit responses: null must not become a valid empty object.
68
+ for (const data of [null, {}]) {
69
+ test(`raw-root operations reject ${JSON.stringify(data)} instead of accepting missing output`, async () => {
70
+ const operations: Effect.Effect<
71
+ unknown,
72
+ unknown,
73
+ ProxmoxBackupOpContext
74
+ >[] = [
75
+ deleteConfigDatastore({ name: "test" }),
76
+ getConfigAcmeTos({}),
77
+ listConfigDatastore({}),
78
+ ];
79
+ for (const operation of operations) {
80
+ const error = await run(Effect.flip(operation), data);
81
+ expect(error).toMatchObject({ _tag: "ProxmoxBackupParseError" });
82
+ }
83
+ });
84
+ }
package/src/protocol.ts CHANGED
@@ -89,6 +89,7 @@ import {
89
89
  type RestErrorEnvelope,
90
90
  } from "@distilled.cloud/core/protocol-rest";
91
91
  import { Credentials, type Config } from "./credentials.ts";
92
+ import { withPbsCodecs } from "./protocol-codecs.ts";
92
93
  import {
93
94
  ParameterVerificationFailed,
94
95
  UnknownProxmoxBackupError,
@@ -132,65 +133,68 @@ const PROXMOX_BACKUP_STATUS_MAP: Record<number, new (args: any) => unknown> = {
132
133
  };
133
134
  delete (PROXMOX_BACKUP_STATUS_MAP as Record<number, unknown>)[400];
134
135
 
135
- export const ProxmoxBackupProtocol: Layer.Layer<API.Protocol> =
136
- makeRestProtocol<Config>({
137
- credentials: Effect.gen(function* () {
138
- const resolve = yield* Credentials;
139
- return yield* resolve;
140
- }),
141
- baseUrl: (creds) => creds.apiBaseUrl,
142
- headers: (creds) => ({
143
- // ⛔ COLON, NOT `=` — see the module header's Authentication section.
144
- Authorization: `PBSAPIToken=${creds.tokenId}:${Redacted.value(creds.secret)}`,
145
- Accept: "application/json",
146
- }),
147
- errorEnvelope,
148
- statusMap: PROXMOX_BACKUP_STATUS_MAP,
149
- // Unwrap PBS's `{"data": …}` envelope BEFORE the schema-driven decode —
150
- // see the module header. `data` is `null`/absent for a Unit-output
151
- // operation; `?? {}` is core's own convention for "no body".
152
- transformResponse: (body) => {
136
+ const baseProtocol: Layer.Layer<API.Protocol> = makeRestProtocol<Config>({
137
+ credentials: Effect.gen(function* () {
138
+ const resolve = yield* Credentials;
139
+ return yield* resolve;
140
+ }),
141
+ baseUrl: (creds) => creds.apiBaseUrl,
142
+ headers: (creds) => ({
143
+ // ⛔ COLON, NOT `=` — see the module header's Authentication section.
144
+ Authorization: `PBSAPIToken=${creds.tokenId}:${Redacted.value(creds.secret)}`,
145
+ Accept: "application/json",
146
+ }),
147
+ errorEnvelope,
148
+ statusMap: PROXMOX_BACKUP_STATUS_MAP,
149
+ // Unwrap PBS's `{"data": …}` envelope BEFORE the schema-driven decode —
150
+ // see the module header. `data` is `null`/absent for a Unit-output
151
+ // operation; `?? {}` is core's own convention for "no body".
152
+ transformResponse: (body) => {
153
+ if (
154
+ body !== null &&
155
+ typeof body === "object" &&
156
+ "data" in (body as Record<string, unknown>)
157
+ ) {
158
+ return (body as Record<string, unknown>).data ?? {};
159
+ }
160
+ return body;
161
+ },
162
+ // Reached for a 400 (see PROXMOX_BACKUP_STATUS_MAP above) or a status
163
+ // with no core mapping at all. `body` is the FULL parsed failure
164
+ // envelope, so this is where PBS's per-field `errors` object is read.
165
+ //
166
+ // ⛔ A BARE 400 WITHOUT THE `errors` OBJECT MUST NOT FALL INTO
167
+ // `UnknownProxmoxBackupError` — same retry-amplification hazard
168
+ // PVE's identical comment explains (`ServerError` is retried
169
+ // automatically; a malformed request is permanent). `BadRequest`
170
+ // (`Category.withBadRequestError`, not retryable) is the honest
171
+ // answer for "PBS said 400 and gave no further structure".
172
+ unknownError: ({ status, message, body }) => {
173
+ const b =
174
+ body !== null && typeof body === "object"
175
+ ? (body as Record<string, unknown>)
176
+ : undefined;
177
+ const fieldErrors = b?.errors;
178
+ if (status === 400) {
153
179
  if (
154
- body !== null &&
155
- typeof body === "object" &&
156
- "data" in (body as Record<string, unknown>)
180
+ fieldErrors !== null &&
181
+ typeof fieldErrors === "object" &&
182
+ !Array.isArray(fieldErrors)
157
183
  ) {
158
- return (body as Record<string, unknown>).data ?? {};
159
- }
160
- return body;
161
- },
162
- // Reached for a 400 (see PROXMOX_BACKUP_STATUS_MAP above) or a status
163
- // with no core mapping at all. `body` is the FULL parsed failure
164
- // envelope, so this is where PBS's per-field `errors` object is read.
165
- //
166
- // ⛔ A BARE 400 WITHOUT THE `errors` OBJECT MUST NOT FALL INTO
167
- // `UnknownProxmoxBackupError` — same retry-amplification hazard
168
- // PVE's identical comment explains (`ServerError` is retried
169
- // automatically; a malformed request is permanent). `BadRequest`
170
- // (`Category.withBadRequestError`, not retryable) is the honest
171
- // answer for "PBS said 400 and gave no further structure".
172
- unknownError: ({ status, message, body }) => {
173
- const b =
174
- body !== null && typeof body === "object"
175
- ? (body as Record<string, unknown>)
176
- : undefined;
177
- const fieldErrors = b?.errors;
178
- if (status === 400) {
179
- if (
180
- fieldErrors !== null &&
181
- typeof fieldErrors === "object" &&
182
- !Array.isArray(fieldErrors)
183
- ) {
184
- const errors: Record<string, string> = {};
185
- for (const [k, v] of Object.entries(
186
- fieldErrors as Record<string, unknown>,
187
- )) {
188
- if (typeof v === "string") errors[k] = v;
189
- }
190
- return new ParameterVerificationFailed({ message, errors });
184
+ const errors: Record<string, string> = {};
185
+ for (const [k, v] of Object.entries(
186
+ fieldErrors as Record<string, unknown>,
187
+ )) {
188
+ if (typeof v === "string") errors[k] = v;
191
189
  }
192
- return new BadRequest({ message });
190
+ return new ParameterVerificationFailed({ message, errors });
193
191
  }
194
- return new UnknownProxmoxBackupError({ status, message, body });
195
- },
196
- });
192
+ return new BadRequest({ message });
193
+ }
194
+ return new UnknownProxmoxBackupError({ status, message, body });
195
+ },
196
+ });
197
+
198
+ /** PBS-specific form encoding and schema validation wrap core without changing other vendors. */
199
+ export const ProxmoxBackupProtocol: Layer.Layer<API.Protocol> =
200
+ withPbsCodecs(baseProtocol);
@@ -189,14 +189,9 @@ export const DeleteAccessTfaRequest = /*@__PURE__*/ S.suspend(() =>
189
189
  S.Struct({
190
190
  userid: S.String.pipe(T.Label()),
191
191
  id: S.String.pipe(T.Label()),
192
- password: S.optional(S.String),
192
+ password: S.optional(S.String.pipe(T.Query())),
193
193
  }).pipe(
194
- T.Http({
195
- method: "DELETE",
196
- uri: "/access/tfa/{userid}/{id}",
197
- code: 200,
198
- contentType: "form-urlencoded",
199
- }),
194
+ T.Http({ method: "DELETE", uri: "/access/tfa/{userid}/{id}", code: 200 }),
200
195
  ),
201
196
  ).annotate({
202
197
  identifier: "DeleteAccessTfaRequest",
@@ -233,14 +228,9 @@ export interface DeleteAccessUserRequest {
233
228
  export const DeleteAccessUserRequest = /*@__PURE__*/ S.suspend(() =>
234
229
  S.Struct({
235
230
  userid: S.String.pipe(T.Label()),
236
- digest: S.optional(S.String),
231
+ digest: S.optional(S.String.pipe(T.Query())),
237
232
  }).pipe(
238
- T.Http({
239
- method: "DELETE",
240
- uri: "/access/users/{userid}",
241
- code: 200,
242
- contentType: "form-urlencoded",
243
- }),
233
+ T.Http({ method: "DELETE", uri: "/access/users/{userid}", code: 200 }),
244
234
  ),
245
235
  ).annotate({
246
236
  identifier: "DeleteAccessUserRequest",
@@ -263,13 +253,12 @@ export const DeleteAccessUserTokenRequest = /*@__PURE__*/ S.suspend(() =>
263
253
  S.Struct({
264
254
  userid: S.String.pipe(T.Label()),
265
255
  token_name: S.String.pipe(T.Label()),
266
- digest: S.optional(S.String),
256
+ digest: S.optional(S.String.pipe(T.Query())),
267
257
  }).pipe(
268
258
  T.Http({
269
259
  method: "DELETE",
270
260
  uri: "/access/users/{userid}/token/{token-name}",
271
261
  code: 200,
272
- contentType: "form-urlencoded",
273
262
  }),
274
263
  ),
275
264
  ).annotate({
@@ -324,18 +324,19 @@ export interface DeleteAdminDatastoreGroupsRequest {
324
324
  export const DeleteAdminDatastoreGroupsRequest = /*@__PURE__*/ S.suspend(() =>
325
325
  S.Struct({
326
326
  store: S.String.pipe(T.Label()),
327
- backup_id: S.String.pipe(T.Body("backup-id")),
327
+ backup_id: S.String.pipe(T.Query("backup-id")),
328
328
  backup_type: DeleteAdminDatastoreGroupsRequestBackupType.pipe(
329
- T.Body("backup-type"),
329
+ T.Query("backup-type"),
330
330
  ),
331
- error_on_protected: S.optional(S.String.pipe(T.Body("error-on-protected"))),
332
- ns: S.optional(S.String),
331
+ error_on_protected: S.optional(
332
+ S.String.pipe(T.Query("error-on-protected")),
333
+ ),
334
+ ns: S.optional(S.String.pipe(T.Query())),
333
335
  }).pipe(
334
336
  T.Http({
335
337
  method: "DELETE",
336
338
  uri: "/admin/datastore/{store}/groups",
337
339
  code: 200,
338
- contentType: "form-urlencoded",
339
340
  }),
340
341
  ),
341
342
  ).annotate({
@@ -372,17 +373,16 @@ export const DeleteAdminDatastoreNamespaceRequest = /*@__PURE__*/ S.suspend(
372
373
  () =>
373
374
  S.Struct({
374
375
  store: S.String.pipe(T.Label()),
375
- delete_groups: S.optional(S.String.pipe(T.Body("delete-groups"))),
376
+ delete_groups: S.optional(S.String.pipe(T.Query("delete-groups"))),
376
377
  error_on_protected: S.optional(
377
- S.String.pipe(T.Body("error-on-protected")),
378
+ S.String.pipe(T.Query("error-on-protected")),
378
379
  ),
379
- ns: S.String,
380
+ ns: S.String.pipe(T.Query()),
380
381
  }).pipe(
381
382
  T.Http({
382
383
  method: "DELETE",
383
384
  uri: "/admin/datastore/{store}/namespace",
384
385
  code: 200,
385
- contentType: "form-urlencoded",
386
386
  }),
387
387
  ),
388
388
  ).annotate({
@@ -417,18 +417,17 @@ export const DeleteAdminDatastoreSnapshotsRequest = /*@__PURE__*/ S.suspend(
417
417
  () =>
418
418
  S.Struct({
419
419
  store: S.String.pipe(T.Label()),
420
- backup_id: S.String.pipe(T.Body("backup-id")),
421
- backup_time: S.String.pipe(T.Body("backup-time")),
420
+ backup_id: S.String.pipe(T.Query("backup-id")),
421
+ backup_time: S.String.pipe(T.Query("backup-time")),
422
422
  backup_type: DeleteAdminDatastoreSnapshotsRequestBackupType.pipe(
423
- T.Body("backup-type"),
423
+ T.Query("backup-type"),
424
424
  ),
425
- ns: S.optional(S.String),
425
+ ns: S.optional(S.String.pipe(T.Query())),
426
426
  }).pipe(
427
427
  T.Http({
428
428
  method: "DELETE",
429
429
  uri: "/admin/datastore/{store}/snapshots",
430
430
  code: 200,
431
- contentType: "form-urlencoded",
432
431
  }),
433
432
  ),
434
433
  ).annotate({