@gjermundgaraba/clankerbox-sdk 0.4.1 → 0.12.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.
- package/dist/client-BVzrg2pq.d.mts +289 -0
- package/dist/client-CbgD_J1o.mjs +583 -0
- package/dist/index.d.mts +573 -0
- package/dist/index.mjs +106 -0
- package/dist/node.d.mts +6 -0
- package/dist/node.mjs +12 -0
- package/dist/rolldown-runtime-D7D4PA-g.mjs +13 -0
- package/package.json +33 -44
- package/README.md +0 -100
- package/dist/gen/clankerbox/v1/host_pb.d.ts +0 -432
- package/dist/gen/clankerbox/v1/host_pb.js +0 -99
- package/dist/gen/clankerbox/v1/machine_pb.d.ts +0 -494
- package/dist/gen/clankerbox/v1/machine_pb.js +0 -113
- package/dist/gen/clankerbox/v1/profile_pb.d.ts +0 -600
- package/dist/gen/clankerbox/v1/profile_pb.js +0 -156
- package/dist/gen/clankerbox/v1/resources_pb.d.ts +0 -595
- package/dist/gen/clankerbox/v1/resources_pb.js +0 -283
- package/dist/gen/clankerbox/v1/session_pb.d.ts +0 -876
- package/dist/gen/clankerbox/v1/session_pb.js +0 -219
- package/dist/index.d.ts +0 -5
- package/dist/index.js +0 -5
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { A as Capacity, B as hostErrors, C as HostId, D as isId, E as formatId, F as Internal, I as Invalid, L as NotFound, M as ConflictKind, N as ErrorTag, O as parseId, P as HostErrorTag, R as Precondition, S as version, T as Name, _ as Profile, a as HostGroup, b as SshEndpoint, c as readTimeout, d as ActionRecord, f as Checkpoint, g as MachineSpec, h as Machine, i as CheckpointGroup, j as Conflict, k as parseName, l as versionHeader, m as Host, n as client_exports, o as Http, p as CreateRequest, s as MachineGroup, u as ActionName, v as Runtime, w as Id, x as release, y as Setup, z as Unavailable } from "./client-CbgD_J1o.mjs";
|
|
2
|
+
import { Effect, FileSystem, Path, Predicate, Schema, Stream } from "effect";
|
|
3
|
+
import { ChildProcess, ChildProcessSpawner } from "effect/process";
|
|
4
|
+
//#region src/profile.ts
|
|
5
|
+
/** Reads a profile file for `create`. Hosts never see a profile. */
|
|
6
|
+
/** Profile files are JSON, decoded with closed input like every call: unknown keys are refused. */
|
|
7
|
+
const decodeProfile = Schema.decodeUnknownEffect(Schema.fromJsonString(Profile));
|
|
8
|
+
/**
|
|
9
|
+
* Reads the profile file at `file`. Its name, the file name without `.json`, is the machine's
|
|
10
|
+
* `profile` label, and its `setup.path` is resolved against the file's directory. The setup
|
|
11
|
+
* itself is read by whoever sends it, with `readSetup`.
|
|
12
|
+
*/
|
|
13
|
+
const loadProfile = (file) => Effect.gen(function* () {
|
|
14
|
+
const fs = yield* FileSystem.FileSystem;
|
|
15
|
+
const path = yield* Path.Path;
|
|
16
|
+
const text = yield* fs.readFileString(file).pipe(Effect.mapError((error) => new Invalid({ message: `couldn't read profile ${file}: ${error.message}` })));
|
|
17
|
+
const profile = yield* decodeProfile(text, { onExcessProperty: "error" }).pipe(Effect.mapError((error) => new Invalid({ message: `profile ${file}: ${error.message}` })));
|
|
18
|
+
const label = path.basename(file, ".json");
|
|
19
|
+
return profile.setup === void 0 ? {
|
|
20
|
+
...profile,
|
|
21
|
+
label
|
|
22
|
+
} : {
|
|
23
|
+
...profile,
|
|
24
|
+
setup: {
|
|
25
|
+
...profile.setup,
|
|
26
|
+
path: path.resolve(path.dirname(file), profile.setup.path)
|
|
27
|
+
},
|
|
28
|
+
label
|
|
29
|
+
};
|
|
30
|
+
});
|
|
31
|
+
//#endregion
|
|
32
|
+
//#region src/setup.ts
|
|
33
|
+
/**
|
|
34
|
+
* Setup scripts for create. A host receives one script: a file is sent as its text, and a
|
|
35
|
+
* recipe directory (a `setup.sh` and the files it needs) is packed into one self-extracting
|
|
36
|
+
* script. Nothing here logs a script or a packed recipe: a recipe can carry secrets.
|
|
37
|
+
*/
|
|
38
|
+
const lineLength = 76;
|
|
39
|
+
/** base64 has no `_`, so no line of the payload can end the here-document early. */
|
|
40
|
+
const terminator = "CLANKERBOX_RECIPE";
|
|
41
|
+
const wrap = (base64) => {
|
|
42
|
+
const lines = [];
|
|
43
|
+
for (let at = 0; at < base64.length; at += lineLength) lines.push(base64.slice(at, at + lineLength));
|
|
44
|
+
return lines.join("\n");
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* Pipes the archive into `tar -x` in a fresh directory, so no file of the recipe's can
|
|
48
|
+
* collide with the archive, then runs `./setup.sh` from there: its `#!` line picks the
|
|
49
|
+
* interpreter, as for a single-file setup, and the unpacker marks it executable whatever mode
|
|
50
|
+
* it was packed with. The directory is under `/var/tmp`, as a single-file setup is, which
|
|
51
|
+
* nothing mounts `noexec`, and is removed however setup ends. Setup runs as a child
|
|
52
|
+
* rather than through `exec`, so the cleanup trap still runs after it. `--no-same-owner`
|
|
53
|
+
* keeps the packer's uid off the files when root unpacks them in the guest.
|
|
54
|
+
*/
|
|
55
|
+
const selfExtracting = (archive) => [
|
|
56
|
+
"#!/bin/sh",
|
|
57
|
+
"set -eu",
|
|
58
|
+
"recipe=$(mktemp -d /var/tmp/clankerbox-recipe.XXXXXX)",
|
|
59
|
+
`trap 'rm -rf "$recipe"' EXIT`,
|
|
60
|
+
"trap 'exit 1' HUP INT TERM",
|
|
61
|
+
`base64 -d <<'${terminator}' | tar -x -f - --no-same-owner -C "$recipe"`,
|
|
62
|
+
wrap(archive),
|
|
63
|
+
terminator,
|
|
64
|
+
"cd \"$recipe\"",
|
|
65
|
+
"chmod +x setup.sh",
|
|
66
|
+
"./setup.sh",
|
|
67
|
+
""
|
|
68
|
+
].join("\n");
|
|
69
|
+
/**
|
|
70
|
+
* Packs a recipe directory into one self-extracting script: a base64 tar that unpacks into a
|
|
71
|
+
* temporary directory, then runs `setup.sh` from there. `COPYFILE_DISABLE=1` and
|
|
72
|
+
* `--no-xattrs` keep macOS `tar` from adding `._*` files and provenance headers.
|
|
73
|
+
*/
|
|
74
|
+
const packRecipe = (directory) => Effect.gen(function* () {
|
|
75
|
+
const fs = yield* FileSystem.FileSystem;
|
|
76
|
+
const noSetup = new Invalid({ message: `recipe ${directory} has no setup.sh` });
|
|
77
|
+
if ((yield* fs.stat(`${directory}/setup.sh`).pipe(Effect.mapError((error) => Predicate.isTagged(error.reason, "NotFound") ? noSetup : error))).type !== "File") return yield* noSetup;
|
|
78
|
+
const tar = yield* (yield* ChildProcessSpawner.ChildProcessSpawner).spawn(ChildProcess.make("tar", [
|
|
79
|
+
"--no-xattrs",
|
|
80
|
+
"-c",
|
|
81
|
+
"-f",
|
|
82
|
+
"-",
|
|
83
|
+
"-C",
|
|
84
|
+
directory,
|
|
85
|
+
"."
|
|
86
|
+
], {
|
|
87
|
+
env: { COPYFILE_DISABLE: "1" },
|
|
88
|
+
extendEnv: true,
|
|
89
|
+
stdin: "ignore"
|
|
90
|
+
}));
|
|
91
|
+
const [archive, errors, code] = yield* Effect.all([
|
|
92
|
+
Stream.mkUint8Array(tar.stdout),
|
|
93
|
+
Stream.mkString(Stream.decodeText(tar.stderr)),
|
|
94
|
+
tar.exitCode
|
|
95
|
+
], { concurrency: "unbounded" });
|
|
96
|
+
if (code !== 0) return yield* new Invalid({ message: `tar couldn't pack recipe ${directory} (exit ${code}): ${errors.trim()}` });
|
|
97
|
+
return selfExtracting(Buffer.from(archive.buffer, archive.byteOffset, archive.byteLength).toString("base64"));
|
|
98
|
+
}).pipe(Effect.scoped, Effect.catchTag("PlatformError", (error) => Effect.fail(new Invalid({ message: `couldn't pack recipe ${directory}: ${error.message}` }))));
|
|
99
|
+
/** Reads a setup: a script file's text, or a recipe directory packed into one script. */
|
|
100
|
+
const readSetup = (path) => Effect.gen(function* () {
|
|
101
|
+
const fs = yield* FileSystem.FileSystem;
|
|
102
|
+
if ((yield* fs.stat(path)).type === "Directory") return yield* packRecipe(path);
|
|
103
|
+
return yield* fs.readFileString(path);
|
|
104
|
+
}).pipe(Effect.catchTag("PlatformError", (error) => Effect.fail(new Invalid({ message: `couldn't read setup ${path}: ${error.message}` }))));
|
|
105
|
+
//#endregion
|
|
106
|
+
export { ActionName, ActionRecord, Capacity, Checkpoint, CheckpointGroup, client_exports as Client, Conflict, ConflictKind, CreateRequest, ErrorTag, Host, HostErrorTag, HostGroup, HostId, Http, Id, Internal, Invalid, Machine, MachineGroup, MachineSpec, Name, NotFound, Precondition, Profile, Runtime, Setup, SshEndpoint, Unavailable, formatId, hostErrors, isId, loadProfile, packRecipe, parseId, parseName, readSetup, readTimeout, release, version, versionHeader };
|
package/dist/node.d.mts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { E as Invalid, n as HostEntry, r as Options, t as Client } from "./client-BVzrg2pq.mjs";
|
|
2
|
+
import { Layer } from "effect";
|
|
3
|
+
//#region src/node.d.ts
|
|
4
|
+
/** Node's `http` module sets no timeout of its own: a mutation can run for hours. */
|
|
5
|
+
export declare const layer: (hosts: ReadonlyArray<HostEntry>, options?: Options) => Layer.Layer<Client, Invalid>;
|
|
6
|
+
//#endregion
|
package/dist/node.mjs
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { r as make, t as Client } from "./client-CbgD_J1o.mjs";
|
|
2
|
+
import { Layer } from "effect";
|
|
3
|
+
import * as NodeHttpClient from "@effect/platform-node/NodeHttpClient";
|
|
4
|
+
//#region src/node.ts
|
|
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`.
|
|
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));
|
|
11
|
+
//#endregion
|
|
12
|
+
export { layer };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
//#region \0rolldown/runtime.js
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __exportAll = (all, no_symbols) => {
|
|
4
|
+
let target = {};
|
|
5
|
+
for (var name in all) __defProp(target, name, {
|
|
6
|
+
get: all[name],
|
|
7
|
+
enumerable: true
|
|
8
|
+
});
|
|
9
|
+
if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
|
|
10
|
+
return target;
|
|
11
|
+
};
|
|
12
|
+
//#endregion
|
|
13
|
+
export { __exportAll as t };
|
package/package.json
CHANGED
|
@@ -1,65 +1,54 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gjermundgaraba/clankerbox-sdk",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.12.1",
|
|
4
|
+
"description": "clankerbox's API contract and client library, for Effect",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
7
7
|
"type": "git",
|
|
8
8
|
"url": "git+https://github.com/gjermundgaraba/clankerbox.git",
|
|
9
|
-
"directory": "
|
|
10
|
-
},
|
|
11
|
-
"homepage": "https://github.com/gjermundgaraba/clankerbox/tree/main/protocol#readme",
|
|
12
|
-
"bugs": "https://github.com/gjermundgaraba/clankerbox/issues",
|
|
13
|
-
"publishConfig": {
|
|
14
|
-
"access": "public",
|
|
15
|
-
"registry": "https://registry.npmjs.org/"
|
|
9
|
+
"directory": "packages/sdk"
|
|
16
10
|
},
|
|
11
|
+
"files": [
|
|
12
|
+
"dist"
|
|
13
|
+
],
|
|
17
14
|
"type": "module",
|
|
15
|
+
"sideEffects": false,
|
|
18
16
|
"exports": {
|
|
19
17
|
".": {
|
|
20
|
-
"types": "./dist/index.d.
|
|
21
|
-
"import": "./dist/index.
|
|
18
|
+
"types": "./dist/index.d.mts",
|
|
19
|
+
"import": "./dist/index.mjs"
|
|
22
20
|
},
|
|
23
|
-
"./
|
|
24
|
-
"types": "./dist/
|
|
25
|
-
"import": "./dist/
|
|
21
|
+
"./node": {
|
|
22
|
+
"types": "./dist/node.d.mts",
|
|
23
|
+
"import": "./dist/node.mjs"
|
|
26
24
|
},
|
|
27
|
-
"./
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
"./session": {
|
|
32
|
-
"types": "./dist/gen/clankerbox/v1/session_pb.d.ts",
|
|
33
|
-
"import": "./dist/gen/clankerbox/v1/session_pb.js"
|
|
34
|
-
},
|
|
35
|
-
"./host": {
|
|
36
|
-
"types": "./dist/gen/clankerbox/v1/host_pb.d.ts",
|
|
37
|
-
"import": "./dist/gen/clankerbox/v1/host_pb.js"
|
|
38
|
-
},
|
|
39
|
-
"./profile": {
|
|
40
|
-
"types": "./dist/gen/clankerbox/v1/profile_pb.d.ts",
|
|
41
|
-
"import": "./dist/gen/clankerbox/v1/profile_pb.js"
|
|
42
|
-
}
|
|
25
|
+
"./package.json": "./package.json"
|
|
26
|
+
},
|
|
27
|
+
"publishConfig": {
|
|
28
|
+
"access": "public"
|
|
43
29
|
},
|
|
44
|
-
"files": [
|
|
45
|
-
"dist",
|
|
46
|
-
"README.md",
|
|
47
|
-
"LICENSE"
|
|
48
|
-
],
|
|
49
30
|
"dependencies": {
|
|
50
|
-
"@
|
|
31
|
+
"@gjermundgaraba/effect-actions": "0.9.0"
|
|
51
32
|
},
|
|
52
33
|
"devDependencies": {
|
|
53
|
-
"@
|
|
34
|
+
"@effect/platform-node": "4.0.0",
|
|
35
|
+
"@types/node": "26.6.4",
|
|
36
|
+
"effect": "4.0.0",
|
|
54
37
|
"typescript": "7.0.2",
|
|
55
|
-
"
|
|
56
|
-
"
|
|
38
|
+
"vite": "npm:@voidzero-dev/vite-plus-core@1.0.0",
|
|
39
|
+
"vite-plus": "1.0.0"
|
|
40
|
+
},
|
|
41
|
+
"peerDependencies": {
|
|
42
|
+
"@effect/platform-node": "^4.0.0",
|
|
43
|
+
"effect": "^4.0.0"
|
|
44
|
+
},
|
|
45
|
+
"peerDependenciesMeta": {
|
|
46
|
+
"@effect/platform-node": {
|
|
47
|
+
"optional": true
|
|
48
|
+
}
|
|
57
49
|
},
|
|
58
50
|
"scripts": {
|
|
59
|
-
"
|
|
60
|
-
"
|
|
61
|
-
"check": "tsc -p tsconfig.json --noEmit",
|
|
62
|
-
"test": "node --test test/*.test.mjs",
|
|
63
|
-
"test:package": "node scripts/test-package.mjs"
|
|
51
|
+
"build": "vp pack",
|
|
52
|
+
"test": "vp test"
|
|
64
53
|
}
|
|
65
54
|
}
|
package/README.md
DELETED
|
@@ -1,100 +0,0 @@
|
|
|
1
|
-
# @gjermundgaraba/clankerbox-sdk
|
|
2
|
-
|
|
3
|
-
Generated Protobuf messages, TypeScript types and Connect RPC service descriptors
|
|
4
|
-
for clankerbox. This ESM package includes compiled JavaScript and declarations;
|
|
5
|
-
consumers do not need protoc, a Go toolchain, or a clankerbox source checkout.
|
|
6
|
-
|
|
7
|
-
```sh
|
|
8
|
-
npm install @gjermundgaraba/clankerbox-sdk @connectrpc/connect @connectrpc/connect-node
|
|
9
|
-
```
|
|
10
|
-
|
|
11
|
-
```ts
|
|
12
|
-
import { MachineService, SessionService } from "@gjermundgaraba/clankerbox-sdk";
|
|
13
|
-
import { createClient } from "@connectrpc/connect";
|
|
14
|
-
import { createConnectTransport } from "@connectrpc/connect-node";
|
|
15
|
-
```
|
|
16
|
-
|
|
17
|
-
The package supplies descriptors, not a transport or lifecycle coordinator.
|
|
18
|
-
Node consumers supply Connect 2.x and use
|
|
19
|
-
`createConnectTransport({httpVersion: "2", ...})`; browser fetch cannot carry
|
|
20
|
-
the bidirectional attachment stream. `@bufbuild/protobuf` is included as a
|
|
21
|
-
runtime dependency. All descriptors are exported at the root and through
|
|
22
|
-
`/resources`, `/machine`, `/session` and `/host` subpaths.
|
|
23
|
-
|
|
24
|
-
SDK **0.4.1** targets clankerbox **0.9.0** and its attachment-owned session
|
|
25
|
-
contract: sessions are created by `Open.create` inside `AttachSession`, and each
|
|
26
|
-
`Input` is sent at the `input_offset` the guest last acknowledged. Use this pair
|
|
27
|
-
together; SDK and controller version numbers are independent. The wire contract
|
|
28
|
-
is unchanged from SDK 0.4.0 and clankerbox 0.8.0. Older installations and guest
|
|
29
|
-
images are not migrated.
|
|
30
|
-
|
|
31
|
-
## RPC contract
|
|
32
|
-
|
|
33
|
-
The wire package is `clankerbox.v1`. The Protobuf sources under
|
|
34
|
-
`clankerbox/v1/` generate Go messages in `gen/clankerbox/v1`, Go Connect
|
|
35
|
-
handlers and clients in `gen/clankerbox/v1/clankerboxv1connect`, and TypeScript
|
|
36
|
-
in `src/gen/clankerbox/v1`. Generated code is checked in.
|
|
37
|
-
|
|
38
|
-
The services:
|
|
39
|
-
|
|
40
|
-
- `MachineService`: discovery, resource reads, lifecycle mutations, operation
|
|
41
|
-
inspection and label replacement. Lifecycle mutations carry idempotency keys
|
|
42
|
-
and return durable Operations; clients poll `GetOperation` and never resubmit
|
|
43
|
-
a mutation because a wait timed out. `SetLabels` is the exception: it takes
|
|
44
|
-
no key and returns the updated `Machine` synchronously.
|
|
45
|
-
- `SessionService`: terminal sessions, mounted on controller, host and guest
|
|
46
|
-
with endpoint-specific authorization.
|
|
47
|
-
- `HostService`: private operation submission and status plus host and machine
|
|
48
|
-
inspection. Each action is a `oneof` payload.
|
|
49
|
-
|
|
50
|
-
Public `Profile`, `Host`, `Machine` and `Checkpoint` messages omit image paths,
|
|
51
|
-
private endpoints, engine store paths and credentials. `internal/rpcmodel`
|
|
52
|
-
converts between these messages and the services' persisted records.
|
|
53
|
-
|
|
54
|
-
Errors combine a Connect status code with a typed `ErrorDetail` reason.
|
|
55
|
-
`retryable` describes the operation, not terminal input: input is addressed by
|
|
56
|
-
offset, so repeating it on the same attachment is safe and carrying it to another
|
|
57
|
-
is not. The streaming semantics of
|
|
58
|
-
`AttachSession` are specified in
|
|
59
|
-
[terminal sessions](https://github.com/gjermundgaraba/clankerbox/blob/main/docs/terminal-sessions.md).
|
|
60
|
-
|
|
61
|
-
## Generation and packaging
|
|
62
|
-
|
|
63
|
-
```sh
|
|
64
|
-
cd protocol
|
|
65
|
-
pnpm install --frozen-lockfile
|
|
66
|
-
pnpm generate
|
|
67
|
-
pnpm build
|
|
68
|
-
pnpm check
|
|
69
|
-
pnpm test
|
|
70
|
-
pnpm test:package
|
|
71
|
-
pnpm pack
|
|
72
|
-
```
|
|
73
|
-
|
|
74
|
-
`generate` requires protoc 36.1 and installs pinned protoc-gen-go,
|
|
75
|
-
protoc-gen-connect-go and protoc-gen-es into `.tools/`. Go dependency versions
|
|
76
|
-
are in the root `go.mod`; JavaScript versions are in `package.json` and the
|
|
77
|
-
lockfile. Regeneration must leave the checked-in output unchanged.
|
|
78
|
-
|
|
79
|
-
`pnpm pack` clean-builds JavaScript and declarations and copies the repository's
|
|
80
|
-
MIT license into the package. `pnpm test:package` packs and installs the artifact
|
|
81
|
-
in a temporary consumer with lifecycle scripts disabled, then checks runtime
|
|
82
|
-
imports, TypeScript declarations, package contents and license inclusion.
|
|
83
|
-
|
|
84
|
-
Publishing uses dedicated `sdk-v<VERSION>` tags, not controller release tags.
|
|
85
|
-
See [SDK publishing](https://github.com/gjermundgaraba/clankerbox/blob/main/docs/sdk-publishing.md)
|
|
86
|
-
for first-publish setup and the tokenless GitHub Actions release workflow.
|
|
87
|
-
|
|
88
|
-
## Tests
|
|
89
|
-
|
|
90
|
-
```sh
|
|
91
|
-
go test -race ./internal/rpcmodel ./gen/...
|
|
92
|
-
cd protocol && pnpm test
|
|
93
|
-
```
|
|
94
|
-
|
|
95
|
-
The Go tests cover conversion round trips, private-field redaction, every
|
|
96
|
-
operation action and state, and typed errors over a real connection. The SDK
|
|
97
|
-
tests cover bigint and JSON precision, oneofs and service shapes.
|
|
98
|
-
`test/real-vm.mjs` and `test/public-session.mjs` drive a running machine and are
|
|
99
|
-
not part of the default test glob; see
|
|
100
|
-
[tests](https://github.com/gjermundgaraba/clankerbox/blob/main/tests/README.md).
|