@gjermundgaraba/clankerbox-sdk 0.15.6 → 0.16.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.mjs CHANGED
@@ -1,36 +1,26 @@
1
1
  import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.mjs";
2
- import { A as parseName, B as Unavailable, C as release, D as Name, E as Id, F as HostErrorTag, I as Internal, L as Invalid, M as Conflict, N as ConflictKind, O as formatId, P as ErrorTag, R as NotFound, S as SshLogin, T as HostId, V as hostErrors, _ as MachineSpec, a as describeIssues, b as Setup, c as readTimeout, d as ActionRecord, f as Checkpoint, g as Machine, h as Limit, i as describeFileError, j as Capacity, k as parseId, l as versionHeader, m as Host, n as client_exports, o as formatMib, p as CreateRequest, s as Http, u as ActionName, v as Profile, w as version, x as SshEndpoint, y as Runtime, z as Precondition } from "./client-BkZhmYJx.mjs";
3
- import { Effect, FileSystem, Option, Path, Predicate, Schema } from "effect";
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";
3
+ import { Effect, FileSystem, Option, Path, Predicate, Record, Schema } from "effect";
4
+ import { Base64 } from "effect/encoding";
4
5
  //#region src/prebuild.ts
5
- /**
6
- * Prebuilds: a checkpoint of a machine whose setup has run, which a create of the same spec
7
- * restores instead of running the setup again. They are the client's alone. A prebuild is an
8
- * ordinary checkpoint under a name derived from what it is of, so a host stores no profile and
9
- * knows nothing of them, and a create finds its prebuild with one read by ID. A prebuild never
10
- * changes and nothing here deletes one: building anew is a `checkpoint delete` first.
11
- */
12
6
  var prebuild_exports = /* @__PURE__ */ __exportAll({
13
7
  build: () => build,
14
8
  create: () => create,
15
9
  nameOf: () => nameOf
16
10
  });
17
11
  const prefix = "prebuilt-";
18
- /** How much of the digest a prebuild's name carries. */
19
12
  const digestDigits = 12;
20
13
  /**
21
- * How many random bytes a builder's name carries: 64 bits, so no two builds draw one name. With
22
- * `prebuilding-`, it is 28 characters, inside the 29 a 32-character host ID and its `_` leave of
23
- * an ID's 62.
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.
24
16
  */
25
17
  const builderBytes = 8;
26
18
  const hex = (bytes) => Array.from(new Uint8Array(bytes), (byte) => byte.toString(16).padStart(2, "0")).join("");
27
19
  /**
28
- * The name of the spec's prebuild on a host: a digest of the spec's label, base and sizes, the
29
- * setup script as sent, and the host's runtime version. A `ram` checkpoint restores only under
30
- * the release that saved it, so after a runtime bump a create misses the old prebuild instead
31
- * of failing to restore it. The setup's timeout is no part of it, and neither is the base's
32
- * image: a host only names it. The script holds whatever secrets a recipe carries, and the name
33
- * 12 hex characters of a digest of it.
20
+ * A digest of the spec's label, base, sizes, setup script as sent, and the host's runtime
21
+ * version: a `ram` checkpoint restores only under the release that saved it, so after a runtime
22
+ * bump a create misses the old prebuild instead of failing to restore it. The setup's timeout and
23
+ * the base's image are no part of it.
34
24
  */
35
25
  const nameOf = (spec, host) => Effect.promise(() => globalThis.crypto.subtle.digest("SHA-256", new TextEncoder().encode(JSON.stringify([
36
26
  spec.profile ?? null,
@@ -41,16 +31,13 @@ const nameOf = (spec, host) => Effect.promise(() => globalThis.crypto.subtle.dig
41
31
  spec.setup?.script ?? null,
42
32
  host.runtimeVersion
43
33
  ])))).pipe(Effect.map((digest) => `${prefix}${hex(digest).slice(0, digestDigits)}`));
44
- /** The checkpoint `id`, if the host has it. */
45
34
  const lookup = (client, id) => client.checkpoint(id).pipe(Effect.map(Option.some), Effect.catchTag("NotFound", () => Effect.succeedNone));
46
35
  /**
47
- * Creates a machine named `name` as `client.create` does, but restores it from the spec's
48
- * prebuild when the host it lands on has one whose capture is done. A spec without a setup has
49
- * none. Placement is the create's own, and a prebuild stays on the host that made it, so a spec
50
- * placed on another host runs its setup. A failed restore is the reply.
36
+ * `client.create`, restoring the spec's prebuild when the host it is placed on has one whose
37
+ * capture is done, unless `fresh`. A failed restore is the reply.
51
38
  */
52
39
  const create = (client, name, spec, options) => Effect.gen(function* () {
53
- if (spec.setup === void 0) return { machine: yield* client.create(name, spec, options) };
40
+ if (spec.setup === void 0 || options?.fresh === true) return { machine: yield* client.create(name, spec, { host: options?.host }) };
54
41
  const host = yield* client.place(spec.base, options);
55
42
  const id = yield* formatId(host.id, yield* nameOf(spec, host));
56
43
  const found = yield* lookup(client, id);
@@ -61,17 +48,10 @@ const create = (client, name, spec, options) => Effect.gen(function* () {
61
48
  return { machine: yield* client.create(name, spec, { host: host.id }) };
62
49
  });
63
50
  /**
64
- * Builds the spec's prebuild on the host a create of it lands on: a machine of its own runs the
65
- * setup, is captured under the prebuild's name, and is deleted. smolvm captures a running
66
- * machine, RAM included, and boat a running one too; Tart only a stopped one, so its machine is
67
- * stopped first. A prebuild whose capture is done is the answer as it is; one that failed or is
68
- * still captured is refused.
69
- *
70
- * Each build's machine has a random name of its own, and the host takes a checkpoint name once,
71
- * so of two builds of one spec at once both run the setup and the second's capture is
72
- * `Conflict`: neither touches the other's machine or prebuild. A build deletes its machine
73
- * however it ends. If that fails after a failure, the first failure is the reply; after a
74
- * capture, the prebuild is made, and the machine is the reply's `leftover`.
51
+ * 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.
75
55
  */
76
56
  const build = (client, spec, options) => Effect.gen(function* () {
77
57
  if (spec.setup === void 0) return yield* new Invalid({ message: "a prebuild needs a setup" });
@@ -92,7 +72,7 @@ const build = (client, spec, options) => Effect.gen(function* () {
92
72
  return {
93
73
  checkpoint: yield* Effect.gen(function* () {
94
74
  yield* client.create(builder, spec, { host: host.id });
95
- if (host.runtime === "tart") yield* client.stop(builderId);
75
+ if (host.captureFrom === "stopped") yield* client.stop(builderId);
96
76
  return yield* client.capture(builderId, name);
97
77
  }).pipe(Effect.onError(() => Effect.ignore(removal))),
98
78
  built: true,
@@ -100,43 +80,8 @@ const build = (client, spec, options) => Effect.gen(function* () {
100
80
  };
101
81
  });
102
82
  //#endregion
103
- //#region src/profile.ts
104
- /** Reads a profile file for `create`. Hosts never see a profile. */
105
- /** Profile files are JSON, decoded with closed input like every call: unknown keys are refused. */
106
- const decodeProfile = Schema.decodeUnknownEffect(Schema.fromJsonString(Profile));
107
- /**
108
- * Reads the profile file at `file`. Its name, the file name without `.json`, is the machine's
109
- * `profile` label, and its `setup.path` is resolved against the file's directory. The setup
110
- * itself is read by whoever sends it, with `readSetup`.
111
- */
112
- const loadProfile = (file) => Effect.gen(function* () {
113
- const fs = yield* FileSystem.FileSystem;
114
- const path = yield* Path.Path;
115
- const text = yield* fs.readFileString(file).pipe(Effect.mapError((error) => new Invalid({ message: `couldn't read profile ${file}: ${describeFileError(error)}` })));
116
- const profile = yield* decodeProfile(text, {
117
- onExcessProperty: "error",
118
- errors: "all"
119
- }).pipe(Effect.mapError((error) => new Invalid({ message: `profile ${file}: ${describeIssues(error)}` })));
120
- const label = path.basename(file, ".json");
121
- return profile.setup === void 0 ? {
122
- ...profile,
123
- label
124
- } : {
125
- ...profile,
126
- setup: {
127
- ...profile.setup,
128
- path: path.resolve(path.dirname(file), profile.setup.path)
129
- },
130
- label
131
- };
132
- });
133
- //#endregion
134
83
  //#region src/setup.ts
135
- /**
136
- * Setup scripts for create. A host receives one script: a file is sent as its text, and a
137
- * recipe directory (a `setup.sh` and the files it needs) is packed into one self-extracting
138
- * script. Nothing here logs a script or a packed recipe: a recipe can carry secrets.
139
- */
84
+ /** Nothing here logs a script or a packed recipe: a recipe can carry secrets. */
140
85
  const lineLength = 76;
141
86
  /** base64 has no `_`, so no line of a file's payload can end its here-document early. */
142
87
  const terminator = "CLANKERBOX_RECIPE";
@@ -145,21 +90,14 @@ const wrap = (base64) => {
145
90
  for (let at = 0; at < base64.length; at += lineLength) lines.push(base64.slice(at, at + lineLength));
146
91
  return lines.join("\n");
147
92
  };
148
- /** A string as one single-quoted shell word, whatever it holds. */
149
93
  const quote = (text) => `'${text.replaceAll("'", `'\\''`)}'`;
150
- /**
151
- * An entry's path as a command's argument: under `./`, so a name that starts with `-` is never
152
- * read as an option.
153
- */
94
+ /** Under `./`, so a name that starts with `-` is never read as an option. */
154
95
  const argument = (entry) => quote(`./${entry}`);
155
- /** Any execute bit, which is the one bit of a mode a recipe carries, as git does. */
96
+ /** The one bit of a mode a recipe carries, as git does, so a checkout's umask changes nothing. */
156
97
  const executable = 73;
157
98
  /**
158
- * Makes the recipe's files in a fresh directory, then runs `./setup.sh` from there: its `#!`
159
- * line picks the interpreter, as for a single-file setup, and the unpacker marks it executable
160
- * whatever mode it was packed with. The directory is under `/var/tmp`, as a single-file setup
161
- * is, which nothing mounts `noexec`, and is removed however setup ends. Setup runs as a child
162
- * rather than through `exec`, so the cleanup trap still runs after it.
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.
163
101
  */
164
102
  const selfExtracting = (steps) => [
165
103
  "#!/bin/sh",
@@ -174,20 +112,18 @@ const selfExtracting = (steps) => [
174
112
  ""
175
113
  ].join("\n");
176
114
  /**
177
- * Packs a recipe directory into one self-extracting script, which makes each of its entries
178
- * with plain shell: a directory with `mkdir`, a symlink with `ln -s` as it is, and a file from
179
- * base64, made executable when any of its execute bits is set. A symlink is never walked into,
180
- * so nothing outside the recipe is packed, and in the guest nothing is written through one.
181
- * Of a mode only the execute bit is kept, as git keeps it, so a checkout's umask changes
182
- * nothing. Entries go in sorted order and nothing records a time or an owner, so the same
183
- * files pack into the same script: a prebuild is named after it.
115
+ * Packs a recipe directory, a `setup.sh` and the files it needs, into one self-extracting script.
116
+ * Symlinks are recreated as they are and never walked into. The same files always pack into the
117
+ * same script, as a prebuild is named after it.
184
118
  */
185
119
  const packRecipe = (directory) => Effect.gen(function* () {
186
120
  const fs = yield* FileSystem.FileSystem;
187
- const noSetup = new Invalid({ message: `recipe ${directory} has no setup.sh` });
121
+ const noSetup = new Invalid({
122
+ message: `recipe ${directory} has no setup.sh`,
123
+ file: directory
124
+ });
188
125
  if ((yield* fs.stat(`${directory}/setup.sh`).pipe(Effect.mapError((error) => Predicate.isTagged(error.reason, "NotFound") ? noSetup : error))).type !== "File") return yield* noSetup;
189
126
  const steps = [];
190
- /** Packs the entries of the recipe's directory `under`, `""` being the recipe itself. */
191
127
  const walk = (under) => Effect.gen(function* () {
192
128
  const names = yield* fs.readDirectory(under === "" ? directory : `${directory}/${under}`);
193
129
  for (const name of names.toSorted()) {
@@ -203,30 +139,110 @@ const packRecipe = (directory) => Effect.gen(function* () {
203
139
  steps.push(`mkdir ${argument(entry)}`);
204
140
  yield* walk(entry);
205
141
  } else if (info.type === "File") {
206
- steps.push(`base64 -d >${argument(entry)} <<'${terminator}'`, wrap(Buffer.from(yield* fs.readFile(path)).toString("base64")), terminator);
142
+ steps.push(`base64 -d >${argument(entry)} <<'${terminator}'`, wrap(Base64.encode(yield* fs.readFile(path))), terminator);
207
143
  if ((info.mode & executable) !== 0) steps.push(`chmod +x ${argument(entry)}`);
208
- } else return yield* new Invalid({ message: `recipe ${directory} holds ${entry}, a ${info.type}: a recipe packs only files, directories and symlinks` });
144
+ } else return yield* new Invalid({
145
+ message: `recipe ${directory} holds ${entry}, a ${info.type}: a recipe packs only files, directories and symlinks`,
146
+ file: path
147
+ });
209
148
  }
210
149
  });
211
150
  yield* walk("");
212
151
  return selfExtracting(steps);
213
- }).pipe(Effect.catchTag("PlatformError", (error) => Effect.fail(new Invalid({ message: `couldn't pack recipe ${directory}: ${describeFileError(error)}` }))));
214
- /** Reads a setup: a script file's text, or a recipe directory packed into one script. */
152
+ }).pipe(Effect.catchTag("PlatformError", (error) => Effect.fail(new Invalid({
153
+ message: `couldn't pack recipe ${directory}: ${describeFileError(error)}`,
154
+ file: directory
155
+ }))));
156
+ /** A script file's text, or a recipe directory packed into one script. */
215
157
  const readSetup = (path) => Effect.gen(function* () {
216
158
  const fs = yield* FileSystem.FileSystem;
217
159
  if ((yield* fs.stat(path)).type === "Directory") return yield* packRecipe(path);
218
160
  return yield* fs.readFileString(path);
219
- }).pipe(Effect.catchTag("PlatformError", (error) => Effect.fail(new Invalid({ message: `couldn't read setup ${path}: ${describeFileError(error)}` }))));
161
+ }).pipe(Effect.catchTag("PlatformError", (error) => Effect.fail(new Invalid({
162
+ message: `couldn't read setup ${path}: ${describeFileError(error)}`,
163
+ file: path
164
+ }))));
220
165
  //#endregion
221
- //#region src/ssh.ts
166
+ //#region src/profile.ts
222
167
  /**
223
- * The system `ssh`'s arguments for a machine, pinned to its host key: what `clankerbox ssh` runs,
224
- * and what anything else that reaches a guest through port 22 does, such as a port forward.
168
+ * A client-side file that fills in a create's spec; hosts never see it. `setup.path` is a script
169
+ * file or a recipe directory, relative to the profile file.
225
170
  */
171
+ const Profile = Schema.Struct({
172
+ base: MachineSpec.fields.base,
173
+ cpu: MachineSpec.fields.cpu,
174
+ ramMib: MachineSpec.fields.ramMib,
175
+ diskGib: MachineSpec.fields.diskGib,
176
+ setup: Schema.optionalKey(Schema.Struct({
177
+ path: Schema.String,
178
+ timeoutSeconds: Setup.fields.timeoutSeconds
179
+ })),
180
+ host: Schema.optionalKey(HostId)
181
+ });
182
+ const decodeProfile = Schema.decodeUnknownEffect(Schema.fromJsonString(Profile));
226
183
  /**
227
- * The machine's SSH target, or what it lacks: an endpoint, which only a running machine has, or
228
- * a host key, which preparation records.
184
+ * Its file name without `.json` is the machine's `profile` label. Unknown keys are refused. The
185
+ * setup itself is read by whoever sends it, with `readSetup`.
229
186
  */
187
+ const loadProfile = (file) => Effect.gen(function* () {
188
+ const fs = yield* FileSystem.FileSystem;
189
+ const path = yield* Path.Path;
190
+ const text = yield* fs.readFileString(file).pipe(Effect.mapError((error) => new Invalid({
191
+ message: `couldn't read profile ${file}: ${describeFileError(error)}`,
192
+ file
193
+ })));
194
+ const profile = yield* decodeProfile(text, {
195
+ onExcessProperty: "error",
196
+ errors: "all"
197
+ }).pipe(Effect.mapError((error) => new Invalid({
198
+ message: `profile ${file}: ${describeIssues(error)}`,
199
+ file
200
+ })));
201
+ const label = path.basename(file, ".json");
202
+ return profile.setup === void 0 ? {
203
+ ...profile,
204
+ label
205
+ } : {
206
+ ...profile,
207
+ setup: {
208
+ ...profile.setup,
209
+ path: path.resolve(path.dirname(file), profile.setup.path)
210
+ },
211
+ label
212
+ };
213
+ });
214
+ const decodeSpec = Schema.decodeUnknownEffect(MachineSpec);
215
+ /**
216
+ * The spec and host a create of `profile` asks for, with the overrides applied and the setup
217
+ * read. Each refusal names `what`, and each field as `names` calls it.
218
+ */
219
+ const specRequest = (what, profile, overrides, names) => Effect.gen(function* () {
220
+ const file = overrides.setup ?? profile?.setup?.path;
221
+ const timeoutSeconds = overrides.setupTimeoutSeconds ?? profile?.setup?.timeoutSeconds;
222
+ 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` });
223
+ const script = file === void 0 ? void 0 : yield* readSetup(file);
224
+ const fields = {
225
+ base: overrides.base ?? profile?.base,
226
+ cpu: overrides.cpu ?? profile?.cpu,
227
+ ramMib: overrides.ramMib ?? profile?.ramMib,
228
+ diskGib: overrides.diskGib ?? profile?.diskGib,
229
+ setup: script === void 0 ? void 0 : Record.filter({
230
+ script,
231
+ timeoutSeconds
232
+ }, Predicate.isNotUndefined),
233
+ profile: profile?.label
234
+ };
235
+ return {
236
+ spec: yield* decodeSpec(Record.filter(fields, Predicate.isNotUndefined), {
237
+ onExcessProperty: "error",
238
+ errors: "all"
239
+ }).pipe(Effect.mapError((error) => new Invalid({ message: `${what}: ${describeIssues(error, names)}` }))),
240
+ host: overrides.host ?? profile?.host
241
+ };
242
+ });
243
+ //#endregion
244
+ //#region src/ssh.ts
245
+ /** Only a running machine has an endpoint, and only a prepared one a host key. */
230
246
  const sshTarget = (machine) => {
231
247
  const { id, ssh: login, hostKey } = machine;
232
248
  if (login === void 0) return { lacks: "SSH endpoint" };
@@ -237,20 +253,18 @@ const sshTarget = (machine) => {
237
253
  };
238
254
  };
239
255
  /**
240
- * The known-hosts line pinning the machine's host key under its ID: ssh looks the key up by
241
- * `HostKeyAlias`, so the line doesn't depend on the endpoint, which changes.
256
+ * ssh looks the key up by `HostKeyAlias`, so the line doesn't depend on the endpoint, which
257
+ * changes.
242
258
  */
243
259
  const knownHostsLine = (id, hostKey) => {
244
260
  const [type = "", key = ""] = hostKey.trim().split(/\s+/u);
245
261
  return `${id} ${type} ${key}\n`;
246
262
  };
247
263
  /**
248
- * ssh's arguments. OpenSSH keeps the first user it is given, so the leading `-l` decides it:
249
- * one in the caller's arguments or in an ssh config doesn't apply. The destination is the
250
- * machine's ID, so a `Host <pattern>` block in the user's ssh config can set keys and options
251
- * per machine; the endpoint and the pinned host key come from command-line options, which no
252
- * config overrides. The caller's arguments go after the destination, where OpenSSH still reads
253
- * options (`-L …`) before a remote command.
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.
254
268
  */
255
269
  const sshArguments = (target, user, knownHosts, extra) => [
256
270
  "-l",
@@ -271,10 +285,9 @@ const sshArguments = (target, user, knownHosts, extra) => [
271
285
  ...extra
272
286
  ];
273
287
  /**
274
- * Writes a one-line known-hosts file pinning the target's host key, and returns the arguments
275
- * that run ssh against it with strict host key checking, as `user`. The file lasts as long as
276
- * the scope, which must outlast the ssh it is for. Ports change on fork and restore, and on
277
- * boat at every start, so a target is read anew for each ssh.
288
+ * ssh's arguments, pinned to the target's host key through a known-hosts file that lasts as long
289
+ * as the scope, which must outlast the ssh. Ports change on fork, restore, and every boat start,
290
+ * so read the target anew for each ssh.
278
291
  */
279
292
  const pinnedSsh = (target, user, extra) => Effect.gen(function* () {
280
293
  const fs = yield* FileSystem.FileSystem;
@@ -285,4 +298,4 @@ const pinnedSsh = (target, user, extra) => Effect.gen(function* () {
285
298
  return sshArguments(target, user, knownHosts, extra);
286
299
  });
287
300
  //#endregion
288
- export { ActionName, ActionRecord, Capacity, Checkpoint, client_exports as Client, Conflict, ConflictKind, CreateRequest, ErrorTag, Host, HostErrorTag, HostId, Http, Id, Internal, Invalid, Limit, Machine, MachineSpec, Name, NotFound, prebuild_exports as Prebuild, Precondition, Profile, Runtime, Setup, SshEndpoint, SshLogin, Unavailable, describeFileError, describeIssues, formatId, formatMib, hostErrors, loadProfile, packRecipe, parseId, parseName, pinnedSsh, readSetup, readTimeout, release, sshTarget, version, versionHeader };
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 };
package/dist/node.d.mts CHANGED
@@ -1,6 +1,6 @@
1
- import { A as Invalid, a as Options, r as HostEntry, t as Client } from "./client-Cn1bj4It.mjs";
1
+ import { M as Invalid, a as Options, o as client_d_exports, r as HostEntry, t as Client } from "./client-DgDb-TR7.mjs";
2
2
  import { Layer } from "effect";
3
3
  //#region src/node.d.ts
4
- /** Node's `http` module sets no timeout of its own: a mutation can run for hours. */
4
+ /** Over Node's `http`, which sets no timeout of its own: a mutation can run for hours. */
5
5
  export declare const layer: (hosts: ReadonlyArray<HostEntry>, options?: Options) => Layer.Layer<Client, Invalid>;
6
6
  //#endregion
package/dist/node.mjs CHANGED
@@ -1,12 +1,12 @@
1
- import { r as make, t as Client } from "./client-BkZhmYJx.mjs";
1
+ import { n as layer$1 } from "./client-CLemHDCl.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
- * The client over Node's `http` module, the SDK's `/node` entry. It stands apart so that an
7
- * app that brings its own `HttpClient` to `Client.make` never loads `@effect/platform-node`.
6
+ * Apart from `.`, so an app that brings its own `HttpClient` never loads
7
+ * `@effect/platform-node`.
8
8
  */
9
- /** Node's `http` module sets no timeout of its own: a mutation can run for hours. */
10
- const layer = (hosts, options) => Layer.effect(Client, make(hosts, options)).pipe(Layer.provide(NodeHttpClient.layerNodeHttp));
9
+ /** Over Node's `http`, which sets no timeout of its own: a mutation can run for hours. */
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.15.6",
3
+ "version": "0.16.0",
4
4
  "description": "clankerbox's API contract and client library, for Effect",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -32,6 +32,7 @@
32
32
  },
33
33
  "devDependencies": {
34
34
  "@effect/platform-node": "4.0.4",
35
+ "@effect/vitest": "4.0.4",
35
36
  "@types/node": "26.6.4",
36
37
  "effect": "4.0.4",
37
38
  "typescript": "7.0.2",