@heyocomputer/hws 0.0.0-stage → 0.2.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/types.js ADDED
@@ -0,0 +1,13 @@
1
+ /**
2
+ * The admin API's JSON, as types.
3
+ *
4
+ * Every field is optional in practice — app-lb omits what is absent and adds
5
+ * fields over time — so these describe what you *may* find rather than what is
6
+ * guaranteed. The runtime never validates them: an unknown field is carried
7
+ * through untouched, which is what makes a client one version behind still work.
8
+ *
9
+ * `test/wire-contract.test.ts` reads `testdata/wire/*.json`, written by app-lb's
10
+ * own response types, and asserts every key in them is declared here. A field
11
+ * app-lb starts sending fails that test instead of silently going unread.
12
+ */
13
+ export {};
package/dist/wait.d.ts ADDED
@@ -0,0 +1,70 @@
1
+ /**
2
+ * Waiting for things to converge.
3
+ *
4
+ * app-lb answers a build with `202` and a job id, and a spec change immediately
5
+ * while the pool it describes takes a minute to exist. Both predicates are
6
+ * fiddly enough that everyone gets them slightly wrong:
7
+ *
8
+ * - a job is done when `status` stops being `running` — but its `log` grows
9
+ * while it runs, so progress means tracking what you have already seen;
10
+ * - a pool has converged when nothing is pending, enough backends are
11
+ * *healthy*, and nothing is draining. Counting `ready` alone reports success
12
+ * while a VM is still failing its health check, because `ready` is the size
13
+ * of the pool, not the healthy part of it.
14
+ */
15
+ import type { Heyctl } from "./client.js";
16
+ import type { DeploymentStatus, JobRecord } from "./types.js";
17
+ export declare const JOB_POLL_MS = 3000;
18
+ export declare const POOL_POLL_MS = 2000;
19
+ /**
20
+ * Where polling starts unless `pollMs` fixes it. It doubles from here up to
21
+ * {@link JOB_POLL_MS} / {@link POOL_POLL_MS}, so a VM that is ready in half a
22
+ * second is seen in about that, and a long build is still asked about politely.
23
+ */
24
+ export declare const FIRST_POLL_MS = 100;
25
+ export interface JobProgress {
26
+ job: JobRecord;
27
+ /** Lines that appeared since the last call — not the whole log. */
28
+ newLog: string[];
29
+ }
30
+ export interface PoolProgress {
31
+ desired: number;
32
+ healthy: number;
33
+ pending: number;
34
+ draining: number;
35
+ converged: boolean;
36
+ }
37
+ export interface WaitForJobOptions {
38
+ /**
39
+ * Poll through this deployment's job list (`GET /deployments/:id/jobs`)
40
+ * rather than `GET /jobs/:id`. A namespace token can read the former;
41
+ * app-lb releases before the namespace-scoped `GET /jobs/:id` refuse it
42
+ * the latter. Pass `job.deployment` from the record a `start*` call returned.
43
+ */
44
+ deployment?: string;
45
+ pollMs?: number;
46
+ timeoutMs?: number;
47
+ onProgress?: (p: JobProgress) => void;
48
+ signal?: AbortSignal;
49
+ }
50
+ export interface WaitForReadyOptions {
51
+ pollMs?: number;
52
+ timeoutMs?: number;
53
+ onProgress?: (p: PoolProgress) => void;
54
+ signal?: AbortSignal;
55
+ }
56
+ /**
57
+ * Poll a job until it finishes.
58
+ *
59
+ * Resolves whether it succeeded or failed — a failed job is an answer, not an
60
+ * error, and its `log` and `error` are the point. Only being unable to *ask*
61
+ * rejects.
62
+ */
63
+ export declare function waitForJob(client: Heyctl, jobId: string, opts?: WaitForJobOptions): Promise<JobRecord>;
64
+ /**
65
+ * Poll a deployment until its pool has converged.
66
+ *
67
+ * A `site` or `upstreams` deployment has no pool, so this returns as soon as it
68
+ * sees one — there is nothing to wait for.
69
+ */
70
+ export declare function waitForReady(client: Heyctl, id: string, opts?: WaitForReadyOptions): Promise<DeploymentStatus>;
package/dist/wait.js ADDED
@@ -0,0 +1,99 @@
1
+ /**
2
+ * Waiting for things to converge.
3
+ *
4
+ * app-lb answers a build with `202` and a job id, and a spec change immediately
5
+ * while the pool it describes takes a minute to exist. Both predicates are
6
+ * fiddly enough that everyone gets them slightly wrong:
7
+ *
8
+ * - a job is done when `status` stops being `running` — but its `log` grows
9
+ * while it runs, so progress means tracking what you have already seen;
10
+ * - a pool has converged when nothing is pending, enough backends are
11
+ * *healthy*, and nothing is draining. Counting `ready` alone reports success
12
+ * while a VM is still failing its health check, because `ready` is the size
13
+ * of the pool, not the healthy part of it.
14
+ */
15
+ import { NotFoundError, TimeoutError } from "./errors.js";
16
+ export const JOB_POLL_MS = 3_000;
17
+ export const POOL_POLL_MS = 2_000;
18
+ /**
19
+ * Where polling starts unless `pollMs` fixes it. It doubles from here up to
20
+ * {@link JOB_POLL_MS} / {@link POOL_POLL_MS}, so a VM that is ready in half a
21
+ * second is seen in about that, and a long build is still asked about politely.
22
+ */
23
+ export const FIRST_POLL_MS = 100;
24
+ const sleep = (ms, signal) => new Promise((resolve, reject) => {
25
+ const t = setTimeout(resolve, ms);
26
+ signal?.addEventListener("abort", () => {
27
+ clearTimeout(t);
28
+ reject(signal.reason);
29
+ }, { once: true });
30
+ });
31
+ /**
32
+ * Poll a job until it finishes.
33
+ *
34
+ * Resolves whether it succeeded or failed — a failed job is an answer, not an
35
+ * error, and its `log` and `error` are the point. Only being unable to *ask*
36
+ * rejects.
37
+ */
38
+ export async function waitForJob(client, jobId, opts = {}) {
39
+ const capMs = opts.pollMs ?? JOB_POLL_MS;
40
+ let pollMs = opts.pollMs ?? Math.min(FIRST_POLL_MS, capMs);
41
+ const timeoutMs = opts.timeoutMs ?? 1_800_000;
42
+ const deadline = Date.now() + timeoutMs;
43
+ let seen = 0;
44
+ for (;;) {
45
+ const job = opts.deployment
46
+ ? (await client.deploymentJobs(opts.deployment, opts.signal)).find((j) => j.id === jobId)
47
+ : await client.job(jobId, opts.signal);
48
+ if (!job)
49
+ throw new NotFoundError("job", `${jobId} (in deployment ${opts.deployment})`);
50
+ const log = job.log ?? [];
51
+ if (opts.onProgress) {
52
+ // Only the tail is new. app-lb keeps a bounded log, so if it truncated
53
+ // from the front `seen` can exceed the length — report nothing rather
54
+ // than a negative slice.
55
+ opts.onProgress({ job, newLog: log.slice(Math.min(seen, log.length)) });
56
+ }
57
+ seen = log.length;
58
+ if (job.status !== "running")
59
+ return job;
60
+ if (Date.now() >= deadline)
61
+ throw new TimeoutError(`job ${jobId}`, timeoutMs);
62
+ await sleep(pollMs, opts.signal);
63
+ pollMs = Math.min(pollMs * 2, capMs);
64
+ }
65
+ }
66
+ /**
67
+ * Poll a deployment until its pool has converged.
68
+ *
69
+ * A `site` or `upstreams` deployment has no pool, so this returns as soon as it
70
+ * sees one — there is nothing to wait for.
71
+ */
72
+ export async function waitForReady(client, id, opts = {}) {
73
+ const capMs = opts.pollMs ?? POOL_POLL_MS;
74
+ let pollMs = opts.pollMs ?? Math.min(FIRST_POLL_MS, capMs);
75
+ const timeoutMs = opts.timeoutMs ?? 300_000;
76
+ const deadline = Date.now() + timeoutMs;
77
+ for (;;) {
78
+ const status = await client.deployment(id, opts.signal);
79
+ if (status.kind !== "vm")
80
+ return status;
81
+ const vms = status.vms ?? [];
82
+ const healthy = vms.filter((v) => v.healthy && !v.draining).length;
83
+ const draining = vms.filter((v) => v.draining).length;
84
+ const progress = {
85
+ desired: status.desired_replicas,
86
+ healthy,
87
+ pending: status.pending,
88
+ draining,
89
+ converged: status.pending === 0 && healthy >= status.desired_replicas && draining === 0,
90
+ };
91
+ opts.onProgress?.(progress);
92
+ if (progress.converged)
93
+ return status;
94
+ if (Date.now() >= deadline)
95
+ throw new TimeoutError(`deployment ${id}`, timeoutMs);
96
+ await sleep(pollMs, opts.signal);
97
+ pollMs = Math.min(pollMs * 2, capMs);
98
+ }
99
+ }
package/package.json CHANGED
@@ -1,6 +1,60 @@
1
1
  {
2
2
  "name": "@heyocomputer/hws",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.2.0",
4
+ "description": "The app-lb SDK for TypeScript — deployments, autoscaling, exec and shells, rollouts, namespace plugins and telemetry for heyvm microVMs. Twin of the hws crate.",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "default": "./dist/index.js"
12
+ }
13
+ },
14
+ "files": [
15
+ "dist",
16
+ "README.md",
17
+ "CHANGELOG.md"
18
+ ],
19
+ "engines": {
20
+ "node": ">=18"
21
+ },
22
+ "scripts": {
23
+ "build": "tsc -p tsconfig.json",
24
+ "test": "npm run build && node --test test/*.test.js",
25
+ "prepublishOnly": "npm run build",
26
+ "e2e": "npm run build && node --test --test-reporter=spec e2e/*.e2e.mjs"
27
+ },
28
+ "dependencies": {
29
+ "ws": "^8.18.0"
30
+ },
31
+ "devDependencies": {
32
+ "@types/node": "^22.0.0",
33
+ "@types/ws": "^8.5.0",
34
+ "typescript": "^5.6.0"
35
+ },
36
+ "peerDependenciesMeta": {},
37
+ "keywords": [
38
+ "app-lb",
39
+ "heyvm",
40
+ "firecracker",
41
+ "microvm",
42
+ "sandbox",
43
+ "load-balancer",
44
+ "hws",
45
+ "heyo"
46
+ ],
47
+ "license": "Apache-2.0",
48
+ "repository": {
49
+ "type": "git",
50
+ "url": "git+https://github.com/Heyo-Computer/hws.git",
51
+ "directory": "app-lb/sdk/typescript"
52
+ },
53
+ "homepage": "https://github.com/Heyo-Computer/hws/tree/main/app-lb/sdk/typescript#readme",
54
+ "bugs": {
55
+ "url": "https://github.com/Heyo-Computer/hws/issues"
56
+ },
57
+ "publishConfig": {
58
+ "access": "public"
59
+ }
60
+ }