@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.
package/dist/index.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { A as HostErrorTag, C as actionsByName, D as Conflict, E as ClankerboxError, F as Unavailable, I as hostErrors, M as Invalid, N as NotFound, O as ConflictKind, P as Precondition, S as Http, T as Capacity, _ as MachineSpec, b as SshEndpoint, c as ActionRecord, d as CheckpointActionName, f as CheckpointActionRecord, g as Machine, h as Limit, i as Interface, j as Internal, k as HostError, l as ActionStatus, m as Host, n as CreateOptions$1, o as client_d_exports, p as CreateRequest, s as ActionName, u as Checkpoint, v as Runtime, w as readTimeout, x as SshLogin, y as Setup } from "./client-DgDb-TR7.mjs";
1
+ import { A as ConflictKind, B as Refused, C as Declared, D as Capacity, E as readTimeout, F as NotFound, G as hostErrors, H as Unavailable, I as NotMade, K as retryable, L as NotOffered, M as Internal, N as Invalid, O as ClankerboxError, P as Missing, R as NotReady, S as stateLabel, T as actionsByName, U as Unsizable, V as ScriptFailed, W as WrongState, _ as Setup, b as Usage, c as ActionRecord, d as CreateRequest, f as Host, g as Runtime, h as Reading, i as Interface, j as HostError, k as Conflict, l as ActionStatus, m as MachineSpec, n as CreateOptions$1, o as client_d_exports, p as Machine, s as ActionName, u as Checkpoint, v as SshEndpoint, w as Http, x as isReady, y as SshLogin, z as PinMismatch } from "./client-8af586hb.mjs";
2
2
  import { Effect, FileSystem, Path, PlatformError, Schema, Scope } from "effect";
3
3
  //#region src/ids.d.ts
4
4
  export declare const HostId: Schema.String;
@@ -46,11 +46,14 @@ interface Built {
46
46
  }
47
47
  /**
48
48
  * Builds the spec's prebuild on the host a create of it is placed on, with a machine of its own
49
- * that it deletes however it ends. A prebuild that is there and done is the answer; one that
50
- * failed or is still being captured is refused. Of two builds of one spec at once, the second's
51
- * capture is `Conflict`, and neither touches the other's machine or prebuild.
49
+ * that it deletes however it ends. A prebuild that is there and done is the answer, one that
50
+ * failed is deleted and built again, and one still running is `Conflict{busy}`, as its capture
51
+ * holds it. Of two builds of one spec at once, the second's capture is `Conflict`, and neither
52
+ * touches the other's machine.
52
53
  */
53
- declare const build: (client: Interface, spec: MachineSpec, options?: CreateOptions$1) => Effect.Effect<Built, ClankerboxError>;
54
+ declare const build: (client: Interface, spec: MachineSpec & {
55
+ readonly setup: Setup;
56
+ }, options?: CreateOptions$1) => Effect.Effect<Built, ClankerboxError>;
54
57
  //#endregion
55
58
  //#region src/release.d.ts
56
59
  /** Also the version of the clankerbox binaries it ships with. */
@@ -120,10 +123,11 @@ export interface SshTarget {
120
123
  readonly login: SshLogin;
121
124
  readonly hostKey: string;
122
125
  }
123
- /** Only a running machine has an endpoint, and only a prepared one a host key. */
124
- export declare const sshTarget: (machine: Machine) => SshTarget | {
125
- readonly lacks: "SSH endpoint" | "SSH host key on record";
126
- };
126
+ /**
127
+ * Only a running machine has an endpoint, and only a prepared one a host key; a machine without
128
+ * either is `WrongState`, naming what it lacks and why.
129
+ */
130
+ export declare const sshTarget: (machine: Machine) => Effect.Effect<SshTarget, WrongState>;
127
131
  /**
128
132
  * ssh's arguments, pinned to the target's host key through a known-hosts file that lasts as long
129
133
  * as the scope, which must outlast the ssh. Ports change on fork, restore, and every boat start,
@@ -131,10 +135,7 @@ export declare const sshTarget: (machine: Machine) => SshTarget | {
131
135
  */
132
136
  export declare const pinnedSsh: (target: SshTarget, user: string, extra: ReadonlyArray<string>) => Effect.Effect<Array<string>, PlatformError.PlatformError, FileSystem.FileSystem | Path.Path | Scope.Scope>;
133
137
  //#endregion
134
- //#region src/text.d.ts
135
- export declare const mibPerGib = 1024;
136
- /** Exactly, as a size flag takes it: `32G`, `1.5G`, `512M`, `1500M`. */
137
- export declare const formatMib: (mib: number) => string;
138
+ //#region src/describe.d.ts
138
139
  /** On one line: `cpu: missing; ram: not a known field`. `names` renames top-level fields. */
139
140
  export declare const describeIssues: (error: Schema.SchemaError, names?: ReadonlyMap<PropertyKey, string>) => string;
140
141
  /**
@@ -143,4 +144,11 @@ export declare const describeIssues: (error: Schema.SchemaError, names?: Readonl
143
144
  */
144
145
  export declare const describeFileError: ({ reason }: PlatformError.PlatformError) => string;
145
146
  //#endregion
146
- export { ActionName, ActionRecord, ActionStatus, Capacity, Checkpoint, CheckpointActionName, CheckpointActionRecord, ClankerboxError, client_d_exports as Client, Conflict, ConflictKind, CreateRequest, Host, HostError, HostErrorTag, Http, Internal, Invalid, Limit, Machine, MachineSpec, NotFound, prebuild_d_exports as Prebuild, Precondition, Runtime, Setup, SshEndpoint, SshLogin, Unavailable, actionsByName, hostErrors, readTimeout };
147
+ //#region src/sizes.d.ts
148
+ export declare const mibPerGib = 1024;
149
+ /** Exactly, as a size flag takes it: `32G`, `1.5G`, `512M`, `1500M`. */
150
+ export declare const formatMib: (mib: number) => string;
151
+ /** As a size flag takes it: `60G`. */
152
+ export declare const formatGib: (gib: number) => string;
153
+ //#endregion
154
+ export { ActionName, ActionRecord, ActionStatus, Capacity, Checkpoint, ClankerboxError, client_d_exports as Client, Conflict, ConflictKind, CreateRequest, Declared, Host, HostError, Http, Internal, Invalid, Machine, MachineSpec, Missing, NotFound, NotMade, NotOffered, NotReady, PinMismatch, prebuild_d_exports as Prebuild, Reading, Refused, Runtime, ScriptFailed, Setup, SshEndpoint, SshLogin, Unavailable, Unsizable, Usage, WrongState, actionsByName, hostErrors, isReady, readTimeout, retryable, stateLabel };
package/dist/index.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
2
- import { A as Name, B as Invalid, C as MachineSpec, D as SshLogin, E as SshEndpoint, F as ClankerboxError, H as Precondition, I as Conflict, L as ConflictKind, M as parseId, N as parseName, O as HostId, P as Capacity, R as HostErrorTag, S as Machine, T as Setup, U as Unavailable, V as NotFound, W as hostErrors, _ as CheckpointActionName, a as formatMib, b as Host, c as version, d as actionsByName, f as readTimeout, g as Checkpoint, h as ActionStatus, i as describeIssues, j as formatId, k as Id, l as versionHeader, m as ActionRecord, o as mibPerGib, p as ActionName, r as describeFileError, s as release, t as client_exports, u as Http, v as CheckpointActionRecord, w as Runtime, x as Limit, y as CreateRequest, z as Internal } from "./client-CLemHDCl.mjs";
2
+ import { A as parseId, B as NotFound, C as Usage, D as Id, E as HostId, F as ConflictKind, G as Refused, H as NotOffered, I as HostError, J as Unsizable, K as ScriptFailed, L as Internal, M as Capacity, N as ClankerboxError, O as Name, P as Conflict, R as Invalid, S as SshLogin, T as stateLabel, U as NotReady, V as NotMade, W as PinMismatch, X as hostErrors, Y as WrongState, Z as retryable, _ as Machine, a as release, b as Setup, c as Http, d as ActionName, f as ActionRecord, g as Host, h as CreateRequest, i as describeIssues, j as parseName, k as formatId, l as actionsByName, m as Checkpoint, o as version, p as ActionStatus, q as Unavailable, r as describeFileError, s as versionHeader, t as client_exports, u as readTimeout, v as MachineSpec, w as isReady, x as SshEndpoint, y as Runtime, z as Missing } from "./client-CbMWwHdT.mjs";
3
3
  import { Effect, FileSystem, Option, Path, Predicate, Record, Schema } from "effect";
4
- import { Base64 } from "effect/encoding";
4
+ import { Base64, Hex } from "effect/encoding";
5
5
  //#region src/prebuild.ts
6
6
  var prebuild_exports = /* @__PURE__ */ __exportAll({
7
7
  build: () => build,
@@ -10,12 +10,7 @@ var prebuild_exports = /* @__PURE__ */ __exportAll({
10
10
  });
11
11
  const prefix = "prebuilt-";
12
12
  const digestDigits = 12;
13
- /**
14
- * 64 bits, so no two builds draw one name. With `prebuilding-` it is 28 characters, inside the 29
15
- * a 32-character host ID and its `_` leave of an ID's 62.
16
- */
17
13
  const builderBytes = 8;
18
- const hex = (bytes) => Array.from(new Uint8Array(bytes), (byte) => byte.toString(16).padStart(2, "0")).join("");
19
14
  /**
20
15
  * A digest of the spec's label, base, sizes, setup script as sent, and the host's runtime
21
16
  * version: a `ram` checkpoint restores only under the release that saved it, so after a runtime
@@ -30,8 +25,8 @@ const nameOf = (spec, host) => Effect.promise(() => globalThis.crypto.subtle.dig
30
25
  spec.diskGib,
31
26
  spec.setup?.script ?? null,
32
27
  host.runtimeVersion
33
- ])))).pipe(Effect.map((digest) => `${prefix}${hex(digest).slice(0, digestDigits)}`));
34
- const lookup = (client, id) => client.checkpoint(id).pipe(Effect.map(Option.some), Effect.catchTag("NotFound", () => Effect.succeedNone));
28
+ ])))).pipe(Effect.map((digest) => `${prefix}${Hex.encode(new Uint8Array(digest)).slice(0, digestDigits)}`));
29
+ const lookup = (client, id) => client.getCheckpoint({ id }).pipe(Effect.map(Option.some), Effect.catchTag("NotFound", () => Effect.succeedNone));
35
30
  /**
36
31
  * `client.create`, restoring the spec's prebuild when the host it is placed on has one whose
37
32
  * capture is done, unless `fresh`. A failed restore is the reply.
@@ -41,39 +36,53 @@ const create = (client, name, spec, options) => Effect.gen(function* () {
41
36
  const host = yield* client.place(spec.base, options);
42
37
  const id = yield* formatId(host.id, yield* nameOf(spec, host));
43
38
  const found = yield* lookup(client, id);
44
- if (Option.isSome(found) && found.value.action.status === "done") return {
45
- machine: yield* client.restore(id, name),
39
+ if (Option.isSome(found) && isReady(found.value)) return {
40
+ machine: yield* client.restoreMachine({
41
+ checkpoint: id,
42
+ name
43
+ }),
46
44
  prebuild: found.value
47
45
  };
48
46
  return { machine: yield* client.create(name, spec, { host: host.id }) };
49
47
  });
50
48
  /**
51
49
  * Builds the spec's prebuild on the host a create of it is placed on, with a machine of its own
52
- * that it deletes however it ends. A prebuild that is there and done is the answer; one that
53
- * failed or is still being captured is refused. Of two builds of one spec at once, the second's
54
- * capture is `Conflict`, and neither touches the other's machine or prebuild.
50
+ * that it deletes however it ends. A prebuild that is there and done is the answer, one that
51
+ * failed is deleted and built again, and one still running is `Conflict{busy}`, as its capture
52
+ * holds it. Of two builds of one spec at once, the second's capture is `Conflict`, and neither
53
+ * touches the other's machine.
55
54
  */
56
55
  const build = (client, spec, options) => Effect.gen(function* () {
57
- if (spec.setup === void 0) return yield* new Invalid({ message: "a prebuild needs a setup" });
58
56
  const host = yield* client.place(spec.base, options);
59
57
  const name = yield* nameOf(spec, host);
60
58
  const id = yield* formatId(host.id, name);
61
59
  const found = yield* lookup(client, id);
62
60
  if (Option.isSome(found)) {
63
61
  const { action } = found.value;
64
- return action.status === "done" ? {
62
+ if (isReady(found.value)) return {
65
63
  checkpoint: found.value,
66
64
  built: false
67
- } : yield* new Precondition({ message: `prebuild ${id}'s ${action.name} is ${action.status}; delete it to prebuild again` });
65
+ };
66
+ if (action.status === "running") return yield* new Conflict({
67
+ message: `prebuild ${id} is busy: its ${action.name} is running`,
68
+ kind: "busy",
69
+ host: host.id,
70
+ action: "captureCheckpoint",
71
+ id
72
+ });
73
+ yield* client.deleteCheckpoint({ id });
68
74
  }
69
- const builder = `prebuilding-${hex(globalThis.crypto.getRandomValues(new Uint8Array(builderBytes)))}`;
75
+ const builder = `prebuilding-${Hex.encode(globalThis.crypto.getRandomValues(new Uint8Array(builderBytes)))}`;
70
76
  const builderId = yield* formatId(host.id, builder);
71
- const removal = Effect.catchTag(client.delete(builderId), "NotFound", () => Effect.void);
77
+ const removal = client.deleteMachine({ id: builderId });
72
78
  return {
73
79
  checkpoint: yield* Effect.gen(function* () {
74
80
  yield* client.create(builder, spec, { host: host.id });
75
- if (host.captureFrom === "stopped") yield* client.stop(builderId);
76
- return yield* client.capture(builderId, name);
81
+ if (host.captureFrom === "stopped") yield* client.stopMachine({ id: builderId });
82
+ return yield* client.captureCheckpoint({
83
+ machine: builderId,
84
+ name
85
+ });
77
86
  }).pipe(Effect.onError(() => Effect.ignore(removal))),
78
87
  built: true,
79
88
  leftover: yield* removal.pipe(Effect.as(void 0), Effect.catch(() => Effect.succeed(builderId)))
@@ -81,9 +90,7 @@ const build = (client, spec, options) => Effect.gen(function* () {
81
90
  });
82
91
  //#endregion
83
92
  //#region src/setup.ts
84
- /** Nothing here logs a script or a packed recipe: a recipe can carry secrets. */
85
93
  const lineLength = 76;
86
- /** base64 has no `_`, so no line of a file's payload can end its here-document early. */
87
94
  const terminator = "CLANKERBOX_RECIPE";
88
95
  const wrap = (base64) => {
89
96
  const lines = [];
@@ -91,14 +98,8 @@ const wrap = (base64) => {
91
98
  return lines.join("\n");
92
99
  };
93
100
  const quote = (text) => `'${text.replaceAll("'", `'\\''`)}'`;
94
- /** Under `./`, so a name that starts with `-` is never read as an option. */
95
101
  const argument = (entry) => quote(`./${entry}`);
96
- /** The one bit of a mode a recipe carries, as git does, so a checkout's umask changes nothing. */
97
102
  const executable = 73;
98
- /**
99
- * Under `/var/tmp`, which nothing mounts `noexec`. Setup runs as a child rather than through
100
- * `exec`, so the cleanup trap still runs after it.
101
- */
102
103
  const selfExtracting = (steps) => [
103
104
  "#!/bin/sh",
104
105
  "set -eu",
@@ -242,30 +243,30 @@ const specRequest = (what, profile, overrides, names) => Effect.gen(function* ()
242
243
  });
243
244
  //#endregion
244
245
  //#region src/ssh.ts
245
- /** Only a running machine has an endpoint, and only a prepared one a host key. */
246
+ /**
247
+ * Only a running machine has an endpoint, and only a prepared one a host key; a machine without
248
+ * either is `WrongState`, naming what it lacks and why.
249
+ */
246
250
  const sshTarget = (machine) => {
247
- const { id, ssh: login, hostKey } = machine;
248
- if (login === void 0) return { lacks: "SSH endpoint" };
249
- return hostKey === void 0 ? { lacks: "SSH host key on record" } : {
251
+ const { id, ssh: login, hostKey, action } = machine;
252
+ if (login !== void 0 && hostKey !== void 0) return Effect.succeed({
250
253
  id,
251
254
  login,
252
255
  hostKey
253
- };
256
+ });
257
+ const lacks = login === void 0 ? "SSH endpoint" : "SSH host key on record";
258
+ const state = stateLabel(machine);
259
+ const message = `${id} has no ${lacks} (${state}, ${action.name} ${action.status}): see clankerbox get ${id}`;
260
+ return Effect.fail(new WrongState({
261
+ message,
262
+ machine: id,
263
+ state
264
+ }));
254
265
  };
255
- /**
256
- * ssh looks the key up by `HostKeyAlias`, so the line doesn't depend on the endpoint, which
257
- * changes.
258
- */
259
266
  const knownHostsLine = (id, hostKey) => {
260
267
  const [type = "", key = ""] = hostKey.trim().split(/\s+/u);
261
268
  return `${id} ${type} ${key}\n`;
262
269
  };
263
- /**
264
- * OpenSSH keeps the first user it is given, so the leading `-l` decides it. The destination is
265
- * the machine's ID, so a `Host <pattern>` block in the user's ssh config can match it, while the
266
- * endpoint and the pinned key come from options no config overrides. The caller's arguments go
267
- * after the destination, where OpenSSH still reads options (`-L …`) before a remote command.
268
- */
269
270
  const sshArguments = (target, user, knownHosts, extra) => [
270
271
  "-l",
271
272
  user,
@@ -298,4 +299,11 @@ const pinnedSsh = (target, user, extra) => Effect.gen(function* () {
298
299
  return sshArguments(target, user, knownHosts, extra);
299
300
  });
300
301
  //#endregion
301
- export { ActionName, ActionRecord, ActionStatus, Capacity, Checkpoint, CheckpointActionName, CheckpointActionRecord, ClankerboxError, client_exports as Client, Conflict, ConflictKind, CreateRequest, Host, HostErrorTag, HostId, Http, Id, Internal, Invalid, Limit, Machine, MachineSpec, Name, NotFound, prebuild_exports as Prebuild, Precondition, Profile, Runtime, Setup, SshEndpoint, SshLogin, Unavailable, actionsByName, describeFileError, describeIssues, formatId, formatMib, hostErrors, loadProfile, mibPerGib, packRecipe, parseId, parseName, pinnedSsh, readSetup, readTimeout, release, specRequest, sshTarget, version, versionHeader };
302
+ //#region src/sizes.ts
303
+ const mibPerGib = 1024;
304
+ /** Exactly, as a size flag takes it: `32G`, `1.5G`, `512M`, `1500M`. */
305
+ const formatMib = (mib) => mib >= 1024 && mib % 512 === 0 ? `${mib / mibPerGib}G` : `${mib}M`;
306
+ /** As a size flag takes it: `60G`. */
307
+ const formatGib = (gib) => `${gib}G`;
308
+ //#endregion
309
+ export { ActionName, ActionRecord, ActionStatus, Capacity, Checkpoint, ClankerboxError, client_exports as Client, Conflict, ConflictKind, CreateRequest, Host, HostError, HostId, Http, Id, Internal, Invalid, Machine, MachineSpec, Missing, Name, NotFound, NotMade, NotOffered, NotReady, PinMismatch, prebuild_exports as Prebuild, Profile, Refused, Runtime, ScriptFailed, Setup, SshEndpoint, SshLogin, Unavailable, Unsizable, Usage, WrongState, actionsByName, describeFileError, describeIssues, formatGib, formatId, formatMib, hostErrors, isReady, loadProfile, mibPerGib, packRecipe, parseId, parseName, pinnedSsh, readSetup, readTimeout, release, retryable, specRequest, sshTarget, stateLabel, version, versionHeader };
package/dist/node.d.mts CHANGED
@@ -1,6 +1,10 @@
1
- import { M as Invalid, a as Options, o as client_d_exports, r as HostEntry, t as Client } from "./client-DgDb-TR7.mjs";
1
+ import { N as Invalid, a as Options, r as HostEntry, t as Client } from "./client-8af586hb.mjs";
2
2
  import { Layer } from "effect";
3
3
  //#region src/node.d.ts
4
- /** Over Node's `http`, which sets no timeout of its own: a mutation can run for hours. */
4
+ /**
5
+ * The client over Node's `http`, which sets no timeout of its own: a mutation can run for hours.
6
+ * It is in `./node`, apart from `.`, so an app that brings its own `HttpClient` never loads
7
+ * `@effect/platform-node`.
8
+ */
5
9
  export declare const layer: (hosts: ReadonlyArray<HostEntry>, options?: Options) => Layer.Layer<Client, Invalid>;
6
10
  //#endregion
package/dist/node.mjs CHANGED
@@ -1,12 +1,12 @@
1
- import { n as layer$1 } from "./client-CLemHDCl.mjs";
1
+ import { n as layer$1 } from "./client-CbMWwHdT.mjs";
2
2
  import { Layer } from "effect";
3
3
  import * as NodeHttpClient from "@effect/platform-node/NodeHttpClient";
4
4
  //#region src/node.ts
5
5
  /**
6
- * Apart from `.`, so an app that brings its own `HttpClient` never loads
6
+ * The client over Node's `http`, which sets no timeout of its own: a mutation can run for hours.
7
+ * It is in `./node`, apart from `.`, so an app that brings its own `HttpClient` never loads
7
8
  * `@effect/platform-node`.
8
9
  */
9
- /** Over Node's `http`, which sets no timeout of its own: a mutation can run for hours. */
10
10
  const layer = (hosts, options) => layer$1(hosts, options).pipe(Layer.provide(NodeHttpClient.layerNodeHttp));
11
11
  //#endregion
12
12
  export { layer };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gjermundgaraba/clankerbox-sdk",
3
- "version": "0.16.0",
3
+ "version": "0.18.0",
4
4
  "description": "clankerbox's API contract and client library, for Effect",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -28,7 +28,7 @@
28
28
  "access": "public"
29
29
  },
30
30
  "dependencies": {
31
- "@gjermundgaraba/effect-actions": "0.14.0"
31
+ "@gjermundgaraba/effect-actions": "0.17.0"
32
32
  },
33
33
  "devDependencies": {
34
34
  "@effect/platform-node": "4.0.4",
@@ -36,8 +36,9 @@
36
36
  "@types/node": "26.6.4",
37
37
  "effect": "4.0.4",
38
38
  "typescript": "7.0.2",
39
- "vite": "npm:@voidzero-dev/vite-plus-core@1.1.0",
40
- "vite-plus": "1.1.0"
39
+ "vite": "npm:@voidzero-dev/vite-plus-core@1.2.0",
40
+ "vite-plus": "1.2.0",
41
+ "vitest": "5.0.4"
41
42
  },
42
43
  "peerDependencies": {
43
44
  "@effect/platform-node": "~4.0.4",