@gjermundgaraba/clankerbox-sdk 0.16.0 → 0.18.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.
@@ -1,14 +1,14 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
2
2
  import * as Action from "@gjermundgaraba/effect-actions/Action";
3
3
  import * as ActionHttp from "@gjermundgaraba/effect-actions/ActionHttp";
4
- import { Context, Duration, Effect, Fiber, Layer, Match, Predicate, Result, Schema, SchemaIssue } from "effect";
4
+ import { Context, Duration, Effect, Fiber, Layer, Match, Predicate, Record, Result, Schema, SchemaIssue } from "effect";
5
5
  import { HttpClient, HttpClientRequest } from "effect/http";
6
6
  //#region src/errors.ts
7
- /** A host sends none of these; the client library sets them. */
8
7
  const located = {
9
8
  host: Schema.optionalKey(Schema.String),
10
9
  action: Schema.optionalKey(Schema.String),
11
- id: Schema.optionalKey(Schema.String)
10
+ id: Schema.optionalKey(Schema.String),
11
+ made: Schema.optionalKey(Schema.String)
12
12
  };
13
13
  /**
14
14
  * The request is malformed, or the host list is one a client can't route by, or a config,
@@ -18,49 +18,80 @@ var Invalid = class extends Schema.TaggedError()("Invalid", {
18
18
  message: Schema.String,
19
19
  ...located,
20
20
  file: Schema.optionalKey(Schema.String)
21
- }, { httpApiStatus: 400 }) {
22
- get retryable() {
23
- return false;
24
- }
25
- };
21
+ }, { httpApiStatus: 400 }) {};
26
22
  /** The machine or checkpoint doesn't exist. A `delete` that meets it is done. */
27
23
  var NotFound = class extends Schema.TaggedError()("NotFound", {
28
24
  message: Schema.String,
29
25
  ...located
30
- }, { httpApiStatus: 404 }) {
31
- get retryable() {
32
- return false;
33
- }
34
- };
26
+ }, { httpApiStatus: 404 }) {};
35
27
  const ConflictKind = Schema.Literals(["exists", "busy"]);
36
28
  /** The name is taken (`exists`), or another action holds the resource (`busy`). */
37
29
  var Conflict = class extends Schema.TaggedError()("Conflict", {
38
30
  message: Schema.String,
39
31
  kind: ConflictKind,
40
32
  ...located
41
- }, { httpApiStatus: 409 }) {
42
- get retryable() {
43
- return this.kind === "busy";
44
- }
45
- };
46
- /** The resource's state doesn't allow the action, or no host offers the requested base. */
47
- var Precondition = class extends Schema.TaggedError()("Precondition", {
33
+ }, { httpApiStatus: 409 }) {};
34
+ /** No host offers `base`: the host a create names, or any in the client's list. */
35
+ var NotOffered = class extends Schema.TaggedError()("NotOffered", {
36
+ message: Schema.String,
37
+ ...located,
38
+ base: Schema.String
39
+ }, { httpApiStatus: 412 }) {};
40
+ /** `machine`'s create, fork or restore failed before it was made: it can only be deleted. */
41
+ var NotMade = class extends Schema.TaggedError()("NotMade", {
42
+ message: Schema.String,
43
+ ...located,
44
+ machine: Schema.String
45
+ }, { httpApiStatus: 412 }) {};
46
+ /** The runtime no longer has `machine`: it can only be deleted. */
47
+ var Missing = class extends Schema.TaggedError()("Missing", {
48
+ message: Schema.String,
49
+ ...located,
50
+ machine: Schema.String
51
+ }, { httpApiStatus: 412 }) {};
52
+ /** `checkpoint`'s capture failed, so nothing restores from it: it can only be deleted. */
53
+ var NotReady = class extends Schema.TaggedError()("NotReady", {
54
+ message: Schema.String,
55
+ ...located,
56
+ checkpoint: Schema.String
57
+ }, { httpApiStatus: 412 }) {};
58
+ /** `checkpoint` holds RAM state saved under another runtime pin than the one the host runs. */
59
+ var PinMismatch = class extends Schema.TaggedError()("PinMismatch", {
60
+ message: Schema.String,
61
+ ...located,
62
+ checkpoint: Schema.String
63
+ }, { httpApiStatus: 412 }) {};
64
+ /** `machine` is in `state`, which the action doesn't act from. */
65
+ var WrongState = class extends Schema.TaggedError()("WrongState", {
66
+ message: Schema.String,
67
+ ...located,
68
+ machine: Schema.String,
69
+ state: Schema.String
70
+ }, { httpApiStatus: 412 }) {};
71
+ /** The runtime can't give the requested sizes, such as a disk below the base's. */
72
+ var Unsizable = class extends Schema.TaggedError()("Unsizable", {
48
73
  message: Schema.String,
49
74
  ...located
50
- }, { httpApiStatus: 412 }) {
51
- get retryable() {
52
- return false;
53
- }
54
- };
75
+ }, { httpApiStatus: 422 }) {};
76
+ /**
77
+ * A guest script exited non-zero or ran past its timeout; the message says which, with its
78
+ * output's tail. A failed `setup` leaves the machine unmade, a failed `preparation` made.
79
+ */
80
+ var ScriptFailed = class extends Schema.TaggedError()("ScriptFailed", {
81
+ message: Schema.String,
82
+ ...located,
83
+ script: Schema.Literals(["setup", "preparation"])
84
+ }, { httpApiStatus: 422 }) {};
85
+ /** The provider under the runtime refused the call for good, such as for its plan or API key. */
86
+ var Refused = class extends Schema.TaggedError()("Refused", {
87
+ message: Schema.String,
88
+ ...located
89
+ }, { httpApiStatus: 403 }) {};
55
90
  /** The host has no room now; room can free up, so a later call may succeed. */
56
91
  var Capacity = class extends Schema.TaggedError()("Capacity", {
57
92
  message: Schema.String,
58
93
  ...located
59
- }, { httpApiStatus: 503 }) {
60
- get retryable() {
61
- return true;
62
- }
63
- };
94
+ }, { httpApiStatus: 503 }) {};
64
95
  /**
65
96
  * No reply: only the client library makes it. A mutation's request may have reached the host
66
97
  * and run, so it is retryable only when `readOnly`.
@@ -69,46 +100,64 @@ var Unavailable = class extends Schema.TaggedError()("Unavailable", {
69
100
  message: Schema.String,
70
101
  readOnly: Schema.Boolean,
71
102
  ...located
72
- }) {
73
- get retryable() {
74
- return this.readOnly;
75
- }
76
- };
103
+ }) {};
77
104
  var Internal = class extends Schema.TaggedError()("Internal", {
78
105
  message: Schema.String,
79
106
  ...located
80
- }, { httpApiStatus: 500 }) {
81
- get retryable() {
82
- return false;
83
- }
84
- };
107
+ }, { httpApiStatus: 500 }) {};
85
108
  /** What a host may answer with; each action declares its own share. */
86
109
  const hostErrors = [
87
110
  Invalid,
88
111
  NotFound,
89
112
  Conflict,
90
- Precondition,
113
+ NotOffered,
114
+ NotMade,
115
+ Missing,
116
+ NotReady,
117
+ PinMismatch,
118
+ WrongState,
119
+ Unsizable,
120
+ ScriptFailed,
121
+ Refused,
91
122
  Capacity,
92
123
  Internal
93
124
  ];
125
+ const HostError = Schema.Union(hostErrors);
126
+ /**
127
+ * Each carries, where known: `host` and `action`, which the client sets; `id`, the resource the
128
+ * error is about, which a host sets on `NotFound` and `Conflict` and the client sets to the call's
129
+ * subject otherwise; and `made`, the resource a create, fork, restore or capture makes, which a
130
+ * failure may leave behind.
131
+ */
94
132
  const ClankerboxError = Schema.Union([...hostErrors, Unavailable]);
95
- const HostErrorTag = Schema.Literals(hostErrors.map((error) => error.fields._tag.schema.literal));
133
+ /**
134
+ * Whether the same call may succeed later: a busy resource or a full host may free up, and a read
135
+ * that got no reply may get one.
136
+ */
137
+ const retryable = (error) => Match.value(error).pipe(Match.tagsExhaustive({
138
+ Invalid: () => false,
139
+ NotFound: () => false,
140
+ Conflict: ({ kind }) => kind === "busy",
141
+ NotOffered: () => false,
142
+ NotMade: () => false,
143
+ Missing: () => false,
144
+ NotReady: () => false,
145
+ PinMismatch: () => false,
146
+ WrongState: () => false,
147
+ Unsizable: () => false,
148
+ ScriptFailed: () => false,
149
+ Refused: () => false,
150
+ Capacity: () => true,
151
+ Unavailable: ({ readOnly }) => readOnly,
152
+ Internal: () => false
153
+ }));
96
154
  //#endregion
97
155
  //#region src/ids.ts
98
156
  const separator = "_";
99
157
  const maxIdLength = 62;
100
- /** The ID is written to `/var/lib/clankerbox/machine-id`, and clankercreds accepts only this. */
101
158
  const idPattern = /^[A-Za-z0-9_-]{1,62}$/u;
102
- /**
103
- * boat's named snapshots are `cbx-<host>-<inst>`, and boat allows `^[a-z0-9][a-z0-9-]{0,62}$`:
104
- * a lowercase host ID of at most 32 characters keeps the name within 63.
105
- */
106
159
  const hostIdPattern = /^[a-z][a-z0-9-]{0,31}$/u;
107
160
  const hostIdRule = "a host ID is 1 to 32 lowercase letters, digits and '-', starting with a letter";
108
- /**
109
- * smolvm names a machine `<name>-<inst>` and refuses consecutive hyphens, so a name has no
110
- * `--` and doesn't end in `-`.
111
- */
112
161
  const namePattern = /^[A-Za-z][A-Za-z0-9]*(?:-[A-Za-z0-9]+)*$/u;
113
162
  const nameRule = "a name starts with a letter, then letters, digits and '-', with no '--' and no '-' at the end";
114
163
  const HostId = Schema.String.check(Schema.isPattern(hostIdPattern, { message: hostIdRule }));
@@ -147,37 +196,31 @@ const Runtime = Schema.Literals([
147
196
  "boat"
148
197
  ]);
149
198
  const Size = Schema.Int.check(Schema.isGreaterThan(0));
150
- /** The mutations that record themselves on a resource's `action`. */
199
+ /** The contract's mutations, which record themselves on a resource's `action`. */
151
200
  const ActionName = Schema.Literals([
152
- "create",
153
- "start",
154
- "stop",
155
- "delete",
156
- "fork",
157
- "restore",
158
- "capture"
201
+ "createMachine",
202
+ "startMachine",
203
+ "stopMachine",
204
+ "deleteMachine",
205
+ "forkMachine",
206
+ "restoreMachine",
207
+ "captureCheckpoint",
208
+ "deleteCheckpoint"
159
209
  ]);
160
210
  const ActionStatus = Schema.Literals([
161
211
  "running",
162
212
  "failed",
163
213
  "done"
164
214
  ]);
165
- const actionRecord = (name) => Schema.Union([Schema.Struct({
166
- name,
215
+ /** A resource's last action; the next action replaces it. */
216
+ const ActionRecord = Schema.Union([Schema.Struct({
217
+ name: ActionName,
167
218
  status: Schema.Literals(["running", "done"])
168
219
  }), Schema.Struct({
169
- name,
220
+ name: ActionName,
170
221
  status: Schema.Literal("failed"),
171
- error: Schema.Struct({
172
- tag: HostErrorTag,
173
- message: Schema.String
174
- })
222
+ error: HostError
175
223
  })]);
176
- /** A resource's last action; the next action replaces it. */
177
- const ActionRecord = actionRecord(ActionName);
178
- /** A checkpoint is only ever captured or deleted. */
179
- const CheckpointActionName = Schema.Literals(["capture", "delete"]);
180
- const CheckpointActionRecord = actionRecord(CheckpointActionName);
181
224
  const SshEndpoint = Schema.Struct({
182
225
  host: Schema.String,
183
226
  port: Schema.Int.check(Schema.isBetween({
@@ -189,6 +232,12 @@ const SshLogin = Schema.Struct({
189
232
  user: Schema.String,
190
233
  ...SshEndpoint.fields
191
234
  });
235
+ /**
236
+ * `state` is `missing` when the runtime doesn't have the machine, and `unknown` when it couldn't
237
+ * be read. Until its create, fork or restore has `made` it, a machine can only be read, stopped or
238
+ * deleted. `ssh` is only on a running machine whose runtime gives it an endpoint, and `hostKey`,
239
+ * `<type> <base64>`, once preparation has recorded it.
240
+ */
192
241
  const Machine = Schema.Struct({
193
242
  id: Id,
194
243
  runtime: Runtime,
@@ -198,55 +247,50 @@ const Machine = Schema.Struct({
198
247
  cpu: Size,
199
248
  ramMib: Size,
200
249
  diskGib: Size,
201
- /** `missing`: the runtime doesn't have it; `unknown`: it couldn't be read. */
202
250
  state: Schema.Literals([
203
251
  "running",
204
252
  "stopped",
205
253
  "missing",
206
254
  "unknown"
207
255
  ]),
208
- /** Until its create, fork or restore has made it, it can only be read, stopped or deleted. */
209
256
  made: Schema.Boolean,
210
257
  action: ActionRecord,
211
- /** Only on a running machine whose runtime gives it an endpoint. */
212
258
  ssh: Schema.optionalKey(SshLogin),
213
- /** `<type> <base64>`, once preparation has recorded it. */
214
259
  hostKey: Schema.optionalKey(Schema.String)
215
260
  }).annotate({ identifier: "Machine" });
261
+ /** The state as people read it: `running`, or `stopped (unmade)` for one never made. */
262
+ const stateLabel = ({ state, made }) => made ? state : `${state} (unmade)`;
263
+ /** `kind` is `ram` (RAM and disks) on smolvm, and `disk` on Tart and boat. */
216
264
  const Checkpoint = Schema.Struct({
217
265
  id: Id,
218
266
  createdAt: Schema.DateTimeUtcFromString,
219
267
  machine: Id,
220
- /** `ram` (RAM and disks) on smolvm, `disk` on Tart and boat. */
221
268
  kind: Schema.Literals(["ram", "disk"]),
222
269
  base: Schema.String,
223
270
  profile: Schema.optionalKey(Schema.String),
224
271
  cpu: Size,
225
272
  ramMib: Size,
226
273
  diskGib: Size,
227
- action: CheckpointActionRecord
274
+ action: ActionRecord
228
275
  }).annotate({ identifier: "Checkpoint" });
276
+ /** A checkpoint restores once its capture is done; it never changes after. */
277
+ const isReady = (checkpoint) => checkpoint.action.status === "done";
278
+ /**
279
+ * `runtimeVersion` is boat's API version on boat. `captureFrom` is the state a machine must be in
280
+ * to be captured: Tart's is `stopped`.
281
+ */
229
282
  const Host = Schema.Struct({
230
283
  id: HostId,
231
284
  runtime: Runtime,
232
285
  version: Schema.String,
233
- /** For boat, its API version. */
234
286
  runtimeVersion: Schema.String,
235
- /** The state a machine must be in to be captured: Tart's is `stopped`. */
236
287
  captureFrom: Schema.Literals(["running", "stopped"]),
237
288
  bases: Schema.Array(Schema.String)
238
289
  }).annotate({ identifier: "Host" });
239
- /**
240
- * `used` is counted as a `Capacity` refusal counts it, so it needn't match the host's own
241
- * machines.
242
- */
243
- const Limit = Schema.Struct({
290
+ const usageFields = {
244
291
  host: HostId,
245
- /**
246
- * `ramMib`: smolvm's RAM budget. `runningVms`: Tart's, Apple's two VMs per Mac.
247
- * `activeSandboxes` and the `starts` of each rolling window: the boat account's.
248
- */
249
292
  resource: Schema.Literals([
293
+ "cpu",
250
294
  "ramMib",
251
295
  "runningVms",
252
296
  "activeSandboxes",
@@ -254,9 +298,24 @@ const Limit = Schema.Struct({
254
298
  "startsPerHour",
255
299
  "startsPerDay"
256
300
  ]),
257
- limit: Schema.Int,
301
+ limit: Schema.optionalKey(Schema.Int)
302
+ };
303
+ /**
304
+ * A resource of a host and how much of it is in use, counted as a refusal counts it, so it
305
+ * needn't match the host's own machines. When a machine the sum counts couldn't be read, the
306
+ * sum would be a guess, so the row names those machines instead.
307
+ *
308
+ * `cpu` and `ramMib` are what the host's running machines were given, `runningVms` Apple's two
309
+ * VMs per Mac, and `activeSandboxes` and the `starts` of each rolling window the boat account's.
310
+ * `limit` is there only where the host refuses an action that would pass it.
311
+ */
312
+ const Usage = Schema.Union([Schema.Struct({
313
+ ...usageFields,
258
314
  used: Schema.Int
259
- }).annotate({ identifier: "Limit" });
315
+ }), Schema.Struct({
316
+ ...usageFields,
317
+ unread: Schema.Array(Id)
318
+ })]).annotate({ identifier: "Usage" });
260
319
  /** Nothing logs the script: it can carry secrets. */
261
320
  const Setup = Schema.Struct({
262
321
  script: Schema.String,
@@ -286,7 +345,6 @@ const FromMachine = Schema.Struct({
286
345
  machine: Id,
287
346
  name: Name
288
347
  });
289
- /** Every action is public, as a host listens only on the tailnet or loopback. */
290
348
  const read = {
291
349
  caller: Action.Anyone,
292
350
  readOnly: true
@@ -295,7 +353,6 @@ const write = {
295
353
  caller: Action.Anyone,
296
354
  readOnly: false
297
355
  };
298
- /** What each action fails with; each has `Invalid`, which the host's version check answers. */
299
356
  const listing = [Invalid, Internal];
300
357
  const reading = [
301
358
  Invalid,
@@ -306,10 +363,16 @@ const changing = [
306
363
  Invalid,
307
364
  NotFound,
308
365
  Conflict,
309
- Precondition,
366
+ Refused,
310
367
  Internal
311
368
  ];
312
- const takingRoom = [...changing, Capacity];
369
+ const booting = [
370
+ ...changing,
371
+ NotMade,
372
+ Missing,
373
+ WrongState,
374
+ Capacity
375
+ ];
313
376
  const Http = ActionHttp.make([
314
377
  Action.make("listMachines", {
315
378
  ...read,
@@ -326,14 +389,23 @@ const Http = ActionHttp.make([
326
389
  }),
327
390
  Action.make("createMachine", {
328
391
  ...write,
329
- error: takingRoom,
392
+ error: [
393
+ Invalid,
394
+ Conflict,
395
+ NotOffered,
396
+ Unsizable,
397
+ ScriptFailed,
398
+ Refused,
399
+ Capacity,
400
+ Internal
401
+ ],
330
402
  description: "Create a machine from a base, run its setup once, and prepare it.",
331
403
  input: CreateRequest,
332
404
  success: Machine
333
405
  }),
334
406
  Action.make("startMachine", {
335
407
  ...write,
336
- error: takingRoom,
408
+ error: [...booting, ScriptFailed],
337
409
  description: "Start a machine. On a running machine, run preparation again.",
338
410
  input: ById,
339
411
  success: Machine
@@ -353,14 +425,25 @@ const Http = ActionHttp.make([
353
425
  }),
354
426
  Action.make("forkMachine", {
355
427
  ...write,
356
- error: takingRoom,
428
+ error: [
429
+ ...booting,
430
+ Unsizable,
431
+ ScriptFailed
432
+ ],
357
433
  description: "Copy a machine to a new name on the same host.",
358
434
  input: FromMachine,
359
435
  success: Machine
360
436
  }),
361
437
  Action.make("restoreMachine", {
362
438
  ...write,
363
- error: takingRoom,
439
+ error: [
440
+ ...changing,
441
+ NotReady,
442
+ PinMismatch,
443
+ Unsizable,
444
+ ScriptFailed,
445
+ Capacity
446
+ ],
364
447
  description: "Create a machine on the checkpoint's host from that checkpoint.",
365
448
  input: Schema.Struct({
366
449
  checkpoint: Id,
@@ -383,7 +466,7 @@ const Http = ActionHttp.make([
383
466
  }),
384
467
  Action.make("captureCheckpoint", {
385
468
  ...write,
386
- error: takingRoom,
469
+ error: booting,
387
470
  description: "Capture a checkpoint of a machine. Its kind follows the runtime: ram on smolvm, which captures only a running machine, and disk on Tart and boat.",
388
471
  input: FromMachine,
389
472
  success: Checkpoint
@@ -404,27 +487,24 @@ const Http = ActionHttp.make([
404
487
  ...read,
405
488
  error: [
406
489
  Invalid,
407
- Precondition,
490
+ Refused,
408
491
  Internal
409
492
  ],
410
- description: "Read the host's limits and how much of each is in use. It fails when the runtime can't be read.",
411
- success: Schema.Array(Limit)
493
+ description: "Read the host's resources, how much of each is in use, and their limits. A machine whose state can't be read leaves the rows that count it unread; it fails when the runtime can't be read at all.",
494
+ success: Schema.Array(Usage)
412
495
  })
413
496
  ], { prefix: "/api" });
414
497
  const actionsByName = Action.byName(Http.actions);
415
498
  //#endregion
416
499
  //#region src/release.ts
417
500
  /** Also the version of the clankerbox binaries it ships with. */
418
- const version = "0.16.0";
501
+ const version = "0.18.0";
419
502
  /** major.minor: releases that share it speak the same API. */
420
503
  const release = (of) => of.split(".").slice(0, 2).join(".");
421
504
  /** A host refuses a request whose `release` differs from its own, or that has none. */
422
505
  const versionHeader = "clankerbox-version";
423
506
  //#endregion
424
- //#region src/text.ts
425
- const mibPerGib = 1024;
426
- /** Exactly, as a size flag takes it: `32G`, `1.5G`, `512M`, `1500M`. */
427
- const formatMib = (mib) => mib >= 1024 && mib % 512 === 0 ? `${mib / mibPerGib}G` : `${mib}M`;
507
+ //#region src/describe.ts
428
508
  const jsonPath = (path, names) => path.map((key, index) => {
429
509
  if (Number.isInteger(key)) return `[${String(key)}]`;
430
510
  return index === 0 ? names?.get(key) ?? String(key) : `.${String(key)}`;
@@ -462,10 +542,6 @@ const HostEntry = Schema.Struct({
462
542
  * reads the resource to see what happened.
463
543
  */
464
544
  var Client = class extends Context.Service()("@gjermundgaraba/clankerbox-sdk/Client") {};
465
- /**
466
- * Input that doesn't encode fails as `InvalidInput` before anything is sent, so a `SchemaError`
467
- * that reaches `settle` is a reply that didn't decode.
468
- */
469
545
  const hostApi = (entry) => ActionHttp.client(Http, {
470
546
  baseUrl: entry.url,
471
547
  transformClient: HttpClient.mapRequest(HttpClientRequest.setHeader(versionHeader, version))
@@ -498,13 +574,18 @@ const settle = (call, context) => bounded(call, context).pipe(Effect.catchTags({
498
574
  }));
499
575
  const encodeError = Schema.encodeSync(ClankerboxError);
500
576
  const decodeError = Schema.decodeSync(ClankerboxError);
501
- /** The error with the call's location set, and every other field it had. */
502
- const locate = (where) => (error) => decodeError({
503
- ...encodeError(error),
504
- ...where
577
+ const locate = ({ action, host, id, made }) => (error) => decodeError({
578
+ ...Record.filter({
579
+ action,
580
+ host,
581
+ id,
582
+ made
583
+ }, Predicate.isNotUndefined),
584
+ ...encodeError(error)
505
585
  });
506
586
  const checkHost = (entry, host) => host.id === entry.id ? Effect.succeed(host) : Effect.fail(new Invalid({ message: `host ${entry.id} (${entry.url}) calls itself ${host.id}; its entry in the host list must use that ID` }));
507
587
  const decodeHosts = Schema.decodeUnknownEffect(Schema.Array(HostEntry));
588
+ const subjectOf = (input) => "id" in input ? input.id : "machine" in input ? input.machine : input.checkpoint;
508
589
  /**
509
590
  * A client over `hosts`, in placement order. It sends nothing until called. An empty list, a
510
591
  * malformed entry or a host ID that appears twice is Invalid.
@@ -523,11 +604,7 @@ const make = (hosts, options) => Effect.gen(function* () {
523
604
  const found = routes.get(host);
524
605
  return found === void 0 ? Effect.fail(new Invalid({ message: `host ${host} isn't in the host list` })) : Effect.succeed(found);
525
606
  };
526
- /**
527
- * A call of `name` on one host, about `subject`. Its errors carry the host, the action and
528
- * `target`, the resource it reads, changes or makes.
529
- */
530
- const send = ({ entry, api }, name, input, subject, target = subject) => {
607
+ const send = ({ entry, api }, name, input, subject, made) => {
531
608
  const { readOnly } = actionsByName[name];
532
609
  const method = api[name];
533
610
  return settle(method(input), {
@@ -535,22 +612,14 @@ const make = (hosts, options) => Effect.gen(function* () {
535
612
  timeout: readOnly ? options?.readTimeout ?? readTimeout : options?.timeout,
536
613
  readOnly,
537
614
  what: subject === void 0 ? name : `${name} ${subject}`,
538
- target
539
- }).pipe(Effect.mapError(locate(target === void 0 ? {
540
- action: name,
541
- host: entry.id
542
- } : {
615
+ target: made ?? subject
616
+ }).pipe(Effect.mapError(locate({
543
617
  action: name,
544
618
  host: entry.id,
545
- id: target
619
+ id: subject,
620
+ made
546
621
  })));
547
622
  };
548
- /**
549
- * A call of `name` on the host of `id`. A fork, restore or capture names the one it makes as
550
- * `made`, which its errors then carry, so a caller can find what a failure left behind.
551
- */
552
- const byId = (name, id, input, made) => Effect.flatMap(Effect.flatMap(parseId(id), ({ host }) => route(host)), (found) => send(found, name, input, id, made ?? id));
553
- /** An error raised before the call is sent names its action too. */
554
623
  const naming = (name) => (effect) => Effect.mapError(effect, (error) => error.action === void 0 ? locate({ action: name })(error) : error);
555
624
  const gather = (call) => Effect.forEach(routed, (route) => Effect.map(Effect.result(call(route)), (result) => ({
556
625
  host: route.entry.id,
@@ -572,18 +641,16 @@ const make = (hosts, options) => Effect.gen(function* () {
572
641
  action: "getHost",
573
642
  host: route.entry.id
574
643
  })));
575
- const createOn = (host, name, spec) => Effect.gen(function* () {
576
- const id = yield* formatId(host, name);
577
- return yield* send(yield* route(host), "createMachine", {
578
- ...spec,
579
- id
580
- }, id);
581
- });
582
- /**
583
- * The first host in list order that offers `base`; the requests still out are interrupted,
584
- * so a silent host after it delays nothing. With no winner it is `Unavailable` only if a
585
- * host gave no reply: otherwise asking again changes nothing, and it is `Precondition`.
586
- */
644
+ const onResource = (name, input, makes) => naming(name)(Effect.gen(function* () {
645
+ const subject = subjectOf(input);
646
+ const { host } = yield* parseId(subject);
647
+ const made = makes === void 0 ? void 0 : yield* formatId(host, makes);
648
+ return yield* send(yield* route(host), name, input, subject, made);
649
+ }));
650
+ const createOn = (host, name, spec) => Effect.flatMap(formatId(host, name), (id) => onResource("createMachine", {
651
+ ...spec,
652
+ id
653
+ }, name));
587
654
  const offering = (base) => Effect.gen(function* () {
588
655
  const asked = yield* Effect.forEach(routed, (route) => Effect.map(Effect.forkScoped(Effect.result(readHost(route))), (fiber) => ({
589
656
  host: route.entry.id,
@@ -606,7 +673,10 @@ const make = (hosts, options) => Effect.gen(function* () {
606
673
  return yield* noReply ? new Unavailable({
607
674
  message,
608
675
  readOnly: true
609
- }) : new Precondition({ message });
676
+ }) : new NotOffered({
677
+ message,
678
+ base
679
+ });
610
680
  }).pipe(Effect.scoped);
611
681
  const create = (name, spec, options) => Effect.gen(function* () {
612
682
  yield* parseName(name);
@@ -614,34 +684,25 @@ const make = (hosts, options) => Effect.gen(function* () {
614
684
  return yield* createOn((yield* offering(spec.base)).id, name, spec);
615
685
  });
616
686
  const place = (base, options) => options?.host === void 0 ? offering(base) : Effect.flatMap(route(options.host), readHost);
617
- const sibling = (source, name) => Effect.flatMap(parseId(source), ({ host }) => formatId(host, name));
687
+ const deleting = (removal) => removal.pipe(Effect.as("deleted"), Effect.catchTag("NotFound", () => Effect.succeed("absent")));
618
688
  return {
619
689
  hosts: gather((route) => Effect.map(readHost(route), (host) => [host])),
620
690
  machines: gather((route) => send(route, "listMachines", {})),
621
691
  checkpoints: gather((route) => send(route, "listCheckpoints", {})),
622
692
  capacity: gather((route) => send(route, "getCapacity", {})),
623
- machine: (id) => naming("getMachine")(byId("getMachine", id, { id })),
624
- checkpoint: (id) => naming("getCheckpoint")(byId("getCheckpoint", id, { id })),
625
693
  place: (base, options) => naming("getHost")(place(base, options)),
626
694
  create: (name, spec, options) => naming("createMachine")(create(name, spec, options)),
627
- start: (id) => naming("startMachine")(byId("startMachine", id, { id })),
628
- stop: (id) => naming("stopMachine")(byId("stopMachine", id, { id })),
629
- delete: (id) => naming("deleteMachine")(byId("deleteMachine", id, { id })),
630
- fork: (machine, name) => naming("forkMachine")(Effect.flatMap(sibling(machine, name), (made) => byId("forkMachine", machine, {
631
- machine,
632
- name
633
- }, made))),
634
- restore: (checkpoint, name) => naming("restoreMachine")(Effect.flatMap(sibling(checkpoint, name), (made) => byId("restoreMachine", checkpoint, {
635
- checkpoint,
636
- name
637
- }, made))),
638
- capture: (machine, name) => naming("captureCheckpoint")(Effect.flatMap(sibling(machine, name), (made) => byId("captureCheckpoint", machine, {
639
- machine,
640
- name
641
- }, made))),
642
- deleteCheckpoint: (id) => naming("deleteCheckpoint")(byId("deleteCheckpoint", id, { id }))
695
+ getMachine: (input) => onResource("getMachine", input),
696
+ startMachine: (input) => onResource("startMachine", input),
697
+ stopMachine: (input) => onResource("stopMachine", input),
698
+ deleteMachine: (input) => deleting(onResource("deleteMachine", input)),
699
+ forkMachine: (input) => onResource("forkMachine", input, input.name),
700
+ restoreMachine: (input) => onResource("restoreMachine", input, input.name),
701
+ getCheckpoint: (input) => onResource("getCheckpoint", input),
702
+ captureCheckpoint: (input) => onResource("captureCheckpoint", input, input.name),
703
+ deleteCheckpoint: (input) => deleting(onResource("deleteCheckpoint", input))
643
704
  };
644
705
  });
645
706
  const layer = (hosts, options) => Layer.effect(Client, make(hosts, options));
646
707
  //#endregion
647
- export { Name as A, Invalid as B, MachineSpec as C, SshLogin as D, SshEndpoint as E, ClankerboxError as F, Precondition as H, Conflict as I, ConflictKind as L, parseId as M, parseName as N, HostId as O, Capacity as P, HostErrorTag as R, Machine as S, Setup as T, Unavailable as U, NotFound as V, hostErrors as W, CheckpointActionName as _, formatMib as a, Host as b, version as c, actionsByName as d, readTimeout as f, Checkpoint as g, ActionStatus as h, describeIssues as i, formatId as j, Id as k, versionHeader as l, ActionRecord as m, layer as n, mibPerGib as o, ActionName as p, describeFileError as r, release as s, client_exports as t, Http as u, CheckpointActionRecord as v, Runtime as w, Limit as x, CreateRequest as y, Internal as z };
708
+ export { parseId as A, NotFound as B, Usage as C, Id as D, HostId as E, ConflictKind as F, Refused as G, NotOffered as H, HostError as I, Unsizable as J, ScriptFailed as K, Internal as L, Capacity as M, ClankerboxError as N, Name as O, Conflict as P, Invalid as R, SshLogin as S, stateLabel as T, NotReady as U, NotMade as V, PinMismatch as W, hostErrors as X, WrongState as Y, retryable as Z, Machine as _, release as a, Setup as b, Http as c, ActionName as d, ActionRecord as f, Host as g, CreateRequest as h, describeIssues as i, parseName as j, formatId as k, actionsByName as l, Checkpoint as m, layer as n, version as o, ActionStatus as p, Unavailable as q, describeFileError as r, versionHeader as s, client_exports as t, readTimeout as u, MachineSpec as v, isReady as w, SshEndpoint as x, Runtime as y, Missing as z };