@gjermundgaraba/clankerbox-sdk 0.18.3 → 0.20.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/{client-8af586hb.d.mts → client-Dr3PcuCP.d.mts} +365 -256
- package/dist/{client-DHNZ57xn.mjs → client-S3cTnrsp.mjs} +165 -100
- package/dist/index.d.mts +17 -19
- package/dist/index.mjs +39 -53
- package/dist/node.d.mts +1 -1
- package/dist/node.mjs +1 -1
- package/package.json +10 -11
package/dist/index.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
|
|
2
|
-
import { A as
|
|
2
|
+
import { A as Name, B as Missing, C as SshEndpoint, D as stateLabel, E as Usage, F as ClankerboxError, G as Refused, H as NotMade, I as Conflict, J as Unavailable, K as ScriptFailed, L as HostError, M as parseId, N as parseName, O as HostId, P as Capacity, Q as retryable, R as Internal, S as Setup, T as TailnetStatus, U as NotOffered, V as NotFound, W as PinMismatch, X as WrongState, Y as Unsizable, Z as hostErrors, _ as Phase, a as release, b as PublicKeys, c as Http, d as Checkpoint, f as CreateRequest, g as MachineSpec, h as Machine, i as describeIssues, j as formatId, k as Id, l as actionsByName, m as Host, o as version, p as Desired, q as Superseded, r as describeFileError, s as versionHeader, t as client_exports, u as readTimeout, v as PrebuildRequest, w as SshLogin, x as Runtime, y as Prebuilt, z as Invalid } from "./client-S3cTnrsp.mjs";
|
|
3
3
|
import { Effect, FileSystem, Option, Path, Predicate, Record, Schema } from "effect";
|
|
4
4
|
import { Base64, Hex } from "effect/encoding";
|
|
5
5
|
//#region src/prebuild.ts
|
|
@@ -10,10 +10,9 @@ var prebuild_exports = /* @__PURE__ */ __exportAll({
|
|
|
10
10
|
});
|
|
11
11
|
const prefix = "prebuilt-";
|
|
12
12
|
const digestDigits = 12;
|
|
13
|
-
const builderBytes = 8;
|
|
14
13
|
/**
|
|
15
|
-
* A digest of the spec's label, base, sizes, setup script as sent, and the host's
|
|
16
|
-
* version: a `ram` checkpoint restores only under the release that saved it, so after a runtime
|
|
14
|
+
* A digest of the spec's label, base, sizes, setup script as sent, authorized keys, and the host's
|
|
15
|
+
* runtime version: a `ram` checkpoint restores only under the release that saved it, so after a runtime
|
|
17
16
|
* bump a create misses the old prebuild instead of failing to restore it. The setup's timeout and
|
|
18
17
|
* the base's image are no part of it.
|
|
19
18
|
*/
|
|
@@ -24,19 +23,20 @@ const nameOf = (spec, host) => Effect.promise(() => globalThis.crypto.subtle.dig
|
|
|
24
23
|
spec.ramMib,
|
|
25
24
|
spec.diskGib,
|
|
26
25
|
spec.setup?.script ?? null,
|
|
26
|
+
spec.setup?.authorizedKeys ?? null,
|
|
27
27
|
host.runtimeVersion
|
|
28
28
|
])))).pipe(Effect.map((digest) => `${prefix}${Hex.encode(new Uint8Array(digest)).slice(0, digestDigits)}`));
|
|
29
29
|
const lookup = (client, id) => client.getCheckpoint({ id }).pipe(Effect.map(Option.some), Effect.catchTag("NotFound", () => Effect.succeedNone));
|
|
30
30
|
/**
|
|
31
|
-
* `client.create`, restoring the spec's prebuild when the host it is placed on has one
|
|
32
|
-
*
|
|
31
|
+
* `client.create`, restoring the spec's prebuild when the host it is placed on has one, unless
|
|
32
|
+
* `fresh`. A failed restore is the reply.
|
|
33
33
|
*/
|
|
34
34
|
const create = (client, name, spec, options) => Effect.gen(function* () {
|
|
35
35
|
if (spec.setup === void 0 || options?.fresh === true) return { machine: yield* client.create(name, spec, { host: options?.host }) };
|
|
36
36
|
const host = yield* client.place(spec.base, options);
|
|
37
37
|
const id = yield* formatId(host.id, yield* nameOf(spec, host));
|
|
38
38
|
const found = yield* lookup(client, id);
|
|
39
|
-
if (Option.isSome(found)
|
|
39
|
+
if (Option.isSome(found)) return {
|
|
40
40
|
machine: yield* client.restoreMachine({
|
|
41
41
|
checkpoint: id,
|
|
42
42
|
name
|
|
@@ -46,47 +46,16 @@ const create = (client, name, spec, options) => Effect.gen(function* () {
|
|
|
46
46
|
return { machine: yield* client.create(name, spec, { host: host.id }) };
|
|
47
47
|
});
|
|
48
48
|
/**
|
|
49
|
-
*
|
|
50
|
-
*
|
|
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.
|
|
49
|
+
* Asks the host a create of the spec is placed on for the spec's prebuild, which it builds with a
|
|
50
|
+
* machine of its own unless it has it. Two builds of one spec at once share the host's one build.
|
|
54
51
|
*/
|
|
55
52
|
const build = (client, spec, options) => Effect.gen(function* () {
|
|
56
53
|
const host = yield* client.place(spec.base, options);
|
|
57
|
-
const
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
if (isReady(found.value)) return {
|
|
63
|
-
checkpoint: found.value,
|
|
64
|
-
built: false
|
|
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 });
|
|
74
|
-
}
|
|
75
|
-
const builder = `prebuilding-${Hex.encode(globalThis.crypto.getRandomValues(new Uint8Array(builderBytes)))}`;
|
|
76
|
-
const builderId = yield* formatId(host.id, builder);
|
|
77
|
-
const removal = client.deleteMachine({ id: builderId });
|
|
78
|
-
return {
|
|
79
|
-
checkpoint: yield* Effect.gen(function* () {
|
|
80
|
-
yield* client.create(builder, spec, { host: host.id });
|
|
81
|
-
if (host.captureFrom === "stopped") yield* client.stopMachine({ id: builderId });
|
|
82
|
-
return yield* client.captureCheckpoint({
|
|
83
|
-
machine: builderId,
|
|
84
|
-
name
|
|
85
|
-
});
|
|
86
|
-
}).pipe(Effect.onError(() => Effect.ignore(removal))),
|
|
87
|
-
built: true,
|
|
88
|
-
leftover: yield* removal.pipe(Effect.as(void 0), Effect.catch(() => Effect.succeed(builderId)))
|
|
89
|
-
};
|
|
54
|
+
const id = yield* formatId(host.id, yield* nameOf(spec, host));
|
|
55
|
+
return yield* client.prebuildCheckpoint({
|
|
56
|
+
id,
|
|
57
|
+
...spec
|
|
58
|
+
});
|
|
90
59
|
});
|
|
91
60
|
//#endregion
|
|
92
61
|
//#region src/setup.ts
|
|
@@ -163,6 +132,21 @@ const readSetup = (path) => Effect.gen(function* () {
|
|
|
163
132
|
message: `couldn't read setup ${path}: ${describeFileError(error)}`,
|
|
164
133
|
file: path
|
|
165
134
|
}))));
|
|
135
|
+
const decodePublicKeys = Schema.decodeUnknownEffect(PublicKeys);
|
|
136
|
+
/**
|
|
137
|
+
* A public-key file's text, such as `~/.ssh/id_ed25519.pub`'s, for a setup's `authorizedKeys`, with
|
|
138
|
+
* its line endings as `\n`.
|
|
139
|
+
*/
|
|
140
|
+
const readAuthorizedKeys = (path) => Effect.gen(function* () {
|
|
141
|
+
const text = yield* (yield* FileSystem.FileSystem).readFileString(path).pipe(Effect.mapError((error) => new Invalid({
|
|
142
|
+
message: `couldn't read authorized keys ${path}: ${describeFileError(error)}`,
|
|
143
|
+
file: path
|
|
144
|
+
})));
|
|
145
|
+
return yield* decodePublicKeys(text.replaceAll("\r\n", "\n")).pipe(Effect.mapError((error) => new Invalid({
|
|
146
|
+
message: `authorized keys ${path}: ${describeIssues(error)}`,
|
|
147
|
+
file: path
|
|
148
|
+
})));
|
|
149
|
+
});
|
|
166
150
|
//#endregion
|
|
167
151
|
//#region src/profile.ts
|
|
168
152
|
/**
|
|
@@ -221,16 +205,18 @@ const specRequest = (what, profile, overrides, names) => Effect.gen(function* ()
|
|
|
221
205
|
const file = overrides.setup ?? profile?.setup?.path;
|
|
222
206
|
const timeoutSeconds = overrides.setupTimeoutSeconds ?? profile?.setup?.timeoutSeconds;
|
|
223
207
|
if (file === void 0 && overrides.setupTimeoutSeconds !== void 0) return yield* new Invalid({ message: `${what}: ${names?.get("setupTimeoutSeconds") ?? "the setup timeout"} needs a setup to time` });
|
|
224
|
-
const
|
|
208
|
+
const keys = overrides.authorizedKeys;
|
|
209
|
+
const setup = file === void 0 ? void 0 : keys === void 0 ? yield* new Invalid({ message: `${what}: a setup needs ${names?.get("authorizedKeys") ?? "authorizedKeys"}, the public keys the host installs for the login user after it` }) : Record.filter({
|
|
210
|
+
script: yield* readSetup(file),
|
|
211
|
+
timeoutSeconds,
|
|
212
|
+
authorizedKeys: yield* readAuthorizedKeys(keys)
|
|
213
|
+
}, Predicate.isNotUndefined);
|
|
225
214
|
const fields = {
|
|
226
215
|
base: overrides.base ?? profile?.base,
|
|
227
216
|
cpu: overrides.cpu ?? profile?.cpu,
|
|
228
217
|
ramMib: overrides.ramMib ?? profile?.ramMib,
|
|
229
218
|
diskGib: overrides.diskGib ?? profile?.diskGib,
|
|
230
|
-
setup
|
|
231
|
-
script,
|
|
232
|
-
timeoutSeconds
|
|
233
|
-
}, Predicate.isNotUndefined),
|
|
219
|
+
setup,
|
|
234
220
|
profile: profile?.label
|
|
235
221
|
};
|
|
236
222
|
return {
|
|
@@ -248,7 +234,7 @@ const specRequest = (what, profile, overrides, names) => Effect.gen(function* ()
|
|
|
248
234
|
* either is `WrongState`, naming what it lacks and why.
|
|
249
235
|
*/
|
|
250
236
|
const sshTarget = (machine) => {
|
|
251
|
-
const { id, ssh: login, hostKey
|
|
237
|
+
const { id, ssh: login, hostKey } = machine;
|
|
252
238
|
if (login !== void 0 && hostKey !== void 0) return Effect.succeed({
|
|
253
239
|
id,
|
|
254
240
|
login,
|
|
@@ -256,7 +242,7 @@ const sshTarget = (machine) => {
|
|
|
256
242
|
});
|
|
257
243
|
const lacks = login === void 0 ? "SSH endpoint" : "SSH host key on record";
|
|
258
244
|
const state = stateLabel(machine);
|
|
259
|
-
const message = `${id} has no ${lacks} (${state}
|
|
245
|
+
const message = `${id} has no ${lacks} (${state}): see clankerbox get ${id}`;
|
|
260
246
|
return Effect.fail(new WrongState({
|
|
261
247
|
message,
|
|
262
248
|
machine: id,
|
|
@@ -306,4 +292,4 @@ const formatMib = (mib) => mib >= 1024 && mib % 512 === 0 ? `${mib / mibPerGib}G
|
|
|
306
292
|
/** As a size flag takes it: `60G`. */
|
|
307
293
|
const formatGib = (gib) => `${gib}G`;
|
|
308
294
|
//#endregion
|
|
309
|
-
export {
|
|
295
|
+
export { Capacity, Checkpoint, ClankerboxError, client_exports as Client, Conflict, CreateRequest, Desired, Host, HostError, HostId, Http, Id, Internal, Invalid, Machine, MachineSpec, Missing, Name, NotFound, NotMade, NotOffered, Phase, PinMismatch, prebuild_exports as Prebuild, PrebuildRequest, Prebuilt, Profile, PublicKeys, Refused, Runtime, ScriptFailed, Setup, SshEndpoint, SshLogin, Superseded, TailnetStatus, Unavailable, Unsizable, Usage, WrongState, actionsByName, describeFileError, describeIssues, formatGib, formatId, formatMib, hostErrors, knownHostsLine, loadProfile, mibPerGib, packRecipe, parseId, parseName, pinnedSsh, readAuthorizedKeys, readSetup, readTimeout, release, retryable, specRequest, sshTarget, stateLabel, version, versionHeader };
|
package/dist/node.d.mts
CHANGED
package/dist/node.mjs
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gjermundgaraba/clankerbox-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.20.0",
|
|
4
4
|
"description": "clankerbox's API contract and client library, for Effect",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -27,21 +27,20 @@
|
|
|
27
27
|
"publishConfig": {
|
|
28
28
|
"access": "public"
|
|
29
29
|
},
|
|
30
|
-
"dependencies": {
|
|
31
|
-
"@gjermundgaraba/effect-actions": "0.17.0"
|
|
32
|
-
},
|
|
33
30
|
"devDependencies": {
|
|
34
|
-
"@effect/platform-node": "4.0.
|
|
35
|
-
"@effect/vitest": "4.0.
|
|
36
|
-
"@
|
|
37
|
-
"
|
|
31
|
+
"@effect/platform-node": "4.0.5",
|
|
32
|
+
"@effect/vitest": "4.0.5",
|
|
33
|
+
"@gjermundgaraba/effect-actions": "0.18.2",
|
|
34
|
+
"@types/node": "26.6.5",
|
|
35
|
+
"effect": "4.0.5",
|
|
38
36
|
"typescript": "7.0.2",
|
|
39
|
-
"vite": "npm:@voidzero-dev/vite-plus-core@1.
|
|
40
|
-
"vite-plus": "1.
|
|
41
|
-
"vitest": "5.0.
|
|
37
|
+
"vite": "npm:@voidzero-dev/vite-plus-core@1.3.0",
|
|
38
|
+
"vite-plus": "1.3.0",
|
|
39
|
+
"vitest": "5.0.5"
|
|
42
40
|
},
|
|
43
41
|
"peerDependencies": {
|
|
44
42
|
"@effect/platform-node": "~4.0.4",
|
|
43
|
+
"@gjermundgaraba/effect-actions": "^0.18.0",
|
|
45
44
|
"effect": "~4.0.4"
|
|
46
45
|
},
|
|
47
46
|
"peerDependenciesMeta": {
|