@zgeoff/imp-client 0.5.0 → 0.7.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/README.md +5 -3
- package/dist/index.d.ts +204 -0
- package/dist/index.js +37 -7
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
# @zgeoff/imp-client
|
|
2
2
|
|
|
3
3
|
A typed client for [impd](https://github.com/zgeoff/imp), the imp host daemon. It runs in Bun, in
|
|
4
|
-
Node 22 or later, and in browsers with
|
|
5
|
-
17.4 or later.
|
|
4
|
+
binaries that `bun build --compile` makes, in Node 22.6 or later, and in browsers with
|
|
5
|
+
`Promise.withResolvers`: Chrome 119, Firefox 121 and Safari 17.4 or later. CI checks each release
|
|
6
|
+
under Node, Bun and a compiled Bun binary.
|
|
6
7
|
|
|
7
8
|
```sh
|
|
8
9
|
npm install @zgeoff/imp-client
|
|
@@ -123,7 +124,8 @@ the command did not run to its exit, `exit` rejects with an `ExecError` whose `c
|
|
|
123
124
|
in the table below, plus `EXEC_FAILED` when the command cannot start) or one of `UNAUTHORIZED` (also
|
|
124
125
|
for an exec ticket that expired or was used), `UNREACHABLE`, `RESTARTING`, `CONNECTION_CLOSED`,
|
|
125
126
|
`DETACHED`, `BAD_MESSAGE`, `CLOSED`, `OUTPUT_OVERFLOW` and `LOCAL_ERROR`. Its `data` is impd's error
|
|
126
|
-
data.
|
|
127
|
+
data. An abort before the command starts, during the ticket call or the connect, rejects with the
|
|
128
|
+
abort's reason instead, an `AbortError` by default; after the start it ends the session as `CLOSED`.
|
|
127
129
|
|
|
128
130
|
## Errors
|
|
129
131
|
|
package/dist/index.d.ts
CHANGED
|
@@ -255,6 +255,14 @@ declare const impContract: {
|
|
|
255
255
|
memoryMib: z.ZodOptional<z.ZodInt>;
|
|
256
256
|
diskMib: z.ZodOptional<z.ZodInt>;
|
|
257
257
|
httpPort: z.ZodOptional<z.ZodInt>;
|
|
258
|
+
policy: z.ZodOptional<z.ZodObject<{
|
|
259
|
+
mode: z.ZodEnum<{
|
|
260
|
+
box: "box";
|
|
261
|
+
none: "none";
|
|
262
|
+
open: "open";
|
|
263
|
+
}>;
|
|
264
|
+
allow: z.ZodDefault<z.ZodReadonly<z.ZodArray<z.ZodString>>>;
|
|
265
|
+
}, z.core.$strip>>;
|
|
258
266
|
}, z.core.$strip>, z.ZodObject<{
|
|
259
267
|
id: z.ZodString;
|
|
260
268
|
name: z.ZodString;
|
|
@@ -1584,6 +1592,202 @@ declare const impContract: {
|
|
|
1584
1592
|
readonly status: 409;
|
|
1585
1593
|
};
|
|
1586
1594
|
}>, Record<never, never>>;
|
|
1595
|
+
policy: import("@orpc/contract").ContractProcedureBuilderWithInputOutput<z.ZodObject<{
|
|
1596
|
+
name: z.ZodString;
|
|
1597
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
1598
|
+
mode: z.ZodEnum<{
|
|
1599
|
+
box: "box";
|
|
1600
|
+
none: "none";
|
|
1601
|
+
open: "open";
|
|
1602
|
+
}>;
|
|
1603
|
+
allow: z.ZodDefault<z.ZodReadonly<z.ZodArray<z.ZodString>>>;
|
|
1604
|
+
}, z.core.$strip>, import("@orpc/contract").MergedErrorMap<Record<never, never>, {
|
|
1605
|
+
NOT_FOUND: {
|
|
1606
|
+
readonly message: "Not found";
|
|
1607
|
+
readonly data: z.ZodObject<{
|
|
1608
|
+
kind: z.ZodEnum<{
|
|
1609
|
+
backup: "backup";
|
|
1610
|
+
checkpoint: "checkpoint";
|
|
1611
|
+
grant: "grant";
|
|
1612
|
+
image: "image";
|
|
1613
|
+
imp: "imp";
|
|
1614
|
+
secret: "secret";
|
|
1615
|
+
session: "session";
|
|
1616
|
+
token: "token";
|
|
1617
|
+
}>;
|
|
1618
|
+
name: z.ZodString;
|
|
1619
|
+
}, z.core.$strip>;
|
|
1620
|
+
};
|
|
1621
|
+
CONFLICT: {
|
|
1622
|
+
readonly message: "Already exists";
|
|
1623
|
+
readonly data: z.ZodObject<{
|
|
1624
|
+
kind: z.ZodEnum<{
|
|
1625
|
+
backup: "backup";
|
|
1626
|
+
checkpoint: "checkpoint";
|
|
1627
|
+
grant: "grant";
|
|
1628
|
+
image: "image";
|
|
1629
|
+
imp: "imp";
|
|
1630
|
+
secret: "secret";
|
|
1631
|
+
session: "session";
|
|
1632
|
+
token: "token";
|
|
1633
|
+
}>;
|
|
1634
|
+
name: z.ZodString;
|
|
1635
|
+
}, z.core.$strip>;
|
|
1636
|
+
};
|
|
1637
|
+
FORBIDDEN: {
|
|
1638
|
+
readonly message: "Not allowed";
|
|
1639
|
+
};
|
|
1640
|
+
PRECONDITION_FAILED: {
|
|
1641
|
+
readonly message: "Not possible on this host";
|
|
1642
|
+
};
|
|
1643
|
+
RAM_BUDGET_EXCEEDED: {
|
|
1644
|
+
readonly message: "Not enough RAM budget, even after sleeping idle imps";
|
|
1645
|
+
readonly status: 503;
|
|
1646
|
+
readonly data: z.ZodObject<{
|
|
1647
|
+
budgetMib: z.ZodInt;
|
|
1648
|
+
usedMib: z.ZodInt;
|
|
1649
|
+
requestedMib: z.ZodInt;
|
|
1650
|
+
}, z.core.$strip>;
|
|
1651
|
+
};
|
|
1652
|
+
SERVICE_UNAVAILABLE: {
|
|
1653
|
+
readonly message: "impd is stopping";
|
|
1654
|
+
};
|
|
1655
|
+
INVALID_STATE: {
|
|
1656
|
+
readonly message: "The imp is not in a state that allows this";
|
|
1657
|
+
readonly status: 409;
|
|
1658
|
+
readonly data: z.ZodObject<{
|
|
1659
|
+
state: z.ZodEnum<{
|
|
1660
|
+
creating: "creating";
|
|
1661
|
+
error: "error";
|
|
1662
|
+
running: "running";
|
|
1663
|
+
sleeping: "sleeping";
|
|
1664
|
+
stopped: "stopped";
|
|
1665
|
+
}>;
|
|
1666
|
+
allowed: z.ZodArray<z.ZodEnum<{
|
|
1667
|
+
creating: "creating";
|
|
1668
|
+
error: "error";
|
|
1669
|
+
running: "running";
|
|
1670
|
+
sleeping: "sleeping";
|
|
1671
|
+
stopped: "stopped";
|
|
1672
|
+
}>>;
|
|
1673
|
+
}, z.core.$strip>;
|
|
1674
|
+
};
|
|
1675
|
+
DISK_FULL: {
|
|
1676
|
+
readonly message: "Not enough free disk on the host";
|
|
1677
|
+
readonly status: 507;
|
|
1678
|
+
readonly data: z.ZodObject<{
|
|
1679
|
+
availableBytes: z.ZodInt;
|
|
1680
|
+
reserveBytes: z.ZodInt;
|
|
1681
|
+
requestedBytes: z.ZodInt;
|
|
1682
|
+
}, z.core.$strip>;
|
|
1683
|
+
};
|
|
1684
|
+
AGENT_OUTDATED: {
|
|
1685
|
+
readonly message: "The imp's agent is too old for this";
|
|
1686
|
+
readonly status: 409;
|
|
1687
|
+
};
|
|
1688
|
+
}>, Record<never, never>>;
|
|
1689
|
+
setPolicy: import("@orpc/contract").ContractProcedureBuilderWithInputOutput<z.ZodObject<{
|
|
1690
|
+
name: z.ZodString;
|
|
1691
|
+
policy: z.ZodObject<{
|
|
1692
|
+
mode: z.ZodEnum<{
|
|
1693
|
+
box: "box";
|
|
1694
|
+
none: "none";
|
|
1695
|
+
open: "open";
|
|
1696
|
+
}>;
|
|
1697
|
+
allow: z.ZodDefault<z.ZodReadonly<z.ZodArray<z.ZodString>>>;
|
|
1698
|
+
}, z.core.$strip>;
|
|
1699
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
1700
|
+
mode: z.ZodEnum<{
|
|
1701
|
+
box: "box";
|
|
1702
|
+
none: "none";
|
|
1703
|
+
open: "open";
|
|
1704
|
+
}>;
|
|
1705
|
+
allow: z.ZodDefault<z.ZodReadonly<z.ZodArray<z.ZodString>>>;
|
|
1706
|
+
}, z.core.$strip>, import("@orpc/contract").MergedErrorMap<Record<never, never>, {
|
|
1707
|
+
NOT_FOUND: {
|
|
1708
|
+
readonly message: "Not found";
|
|
1709
|
+
readonly data: z.ZodObject<{
|
|
1710
|
+
kind: z.ZodEnum<{
|
|
1711
|
+
backup: "backup";
|
|
1712
|
+
checkpoint: "checkpoint";
|
|
1713
|
+
grant: "grant";
|
|
1714
|
+
image: "image";
|
|
1715
|
+
imp: "imp";
|
|
1716
|
+
secret: "secret";
|
|
1717
|
+
session: "session";
|
|
1718
|
+
token: "token";
|
|
1719
|
+
}>;
|
|
1720
|
+
name: z.ZodString;
|
|
1721
|
+
}, z.core.$strip>;
|
|
1722
|
+
};
|
|
1723
|
+
CONFLICT: {
|
|
1724
|
+
readonly message: "Already exists";
|
|
1725
|
+
readonly data: z.ZodObject<{
|
|
1726
|
+
kind: z.ZodEnum<{
|
|
1727
|
+
backup: "backup";
|
|
1728
|
+
checkpoint: "checkpoint";
|
|
1729
|
+
grant: "grant";
|
|
1730
|
+
image: "image";
|
|
1731
|
+
imp: "imp";
|
|
1732
|
+
secret: "secret";
|
|
1733
|
+
session: "session";
|
|
1734
|
+
token: "token";
|
|
1735
|
+
}>;
|
|
1736
|
+
name: z.ZodString;
|
|
1737
|
+
}, z.core.$strip>;
|
|
1738
|
+
};
|
|
1739
|
+
FORBIDDEN: {
|
|
1740
|
+
readonly message: "Not allowed";
|
|
1741
|
+
};
|
|
1742
|
+
PRECONDITION_FAILED: {
|
|
1743
|
+
readonly message: "Not possible on this host";
|
|
1744
|
+
};
|
|
1745
|
+
RAM_BUDGET_EXCEEDED: {
|
|
1746
|
+
readonly message: "Not enough RAM budget, even after sleeping idle imps";
|
|
1747
|
+
readonly status: 503;
|
|
1748
|
+
readonly data: z.ZodObject<{
|
|
1749
|
+
budgetMib: z.ZodInt;
|
|
1750
|
+
usedMib: z.ZodInt;
|
|
1751
|
+
requestedMib: z.ZodInt;
|
|
1752
|
+
}, z.core.$strip>;
|
|
1753
|
+
};
|
|
1754
|
+
SERVICE_UNAVAILABLE: {
|
|
1755
|
+
readonly message: "impd is stopping";
|
|
1756
|
+
};
|
|
1757
|
+
INVALID_STATE: {
|
|
1758
|
+
readonly message: "The imp is not in a state that allows this";
|
|
1759
|
+
readonly status: 409;
|
|
1760
|
+
readonly data: z.ZodObject<{
|
|
1761
|
+
state: z.ZodEnum<{
|
|
1762
|
+
creating: "creating";
|
|
1763
|
+
error: "error";
|
|
1764
|
+
running: "running";
|
|
1765
|
+
sleeping: "sleeping";
|
|
1766
|
+
stopped: "stopped";
|
|
1767
|
+
}>;
|
|
1768
|
+
allowed: z.ZodArray<z.ZodEnum<{
|
|
1769
|
+
creating: "creating";
|
|
1770
|
+
error: "error";
|
|
1771
|
+
running: "running";
|
|
1772
|
+
sleeping: "sleeping";
|
|
1773
|
+
stopped: "stopped";
|
|
1774
|
+
}>>;
|
|
1775
|
+
}, z.core.$strip>;
|
|
1776
|
+
};
|
|
1777
|
+
DISK_FULL: {
|
|
1778
|
+
readonly message: "Not enough free disk on the host";
|
|
1779
|
+
readonly status: 507;
|
|
1780
|
+
readonly data: z.ZodObject<{
|
|
1781
|
+
availableBytes: z.ZodInt;
|
|
1782
|
+
reserveBytes: z.ZodInt;
|
|
1783
|
+
requestedBytes: z.ZodInt;
|
|
1784
|
+
}, z.core.$strip>;
|
|
1785
|
+
};
|
|
1786
|
+
AGENT_OUTDATED: {
|
|
1787
|
+
readonly message: "The imp's agent is too old for this";
|
|
1788
|
+
readonly status: 409;
|
|
1789
|
+
};
|
|
1790
|
+
}>, Record<never, never>>;
|
|
1587
1791
|
fork: import("@orpc/contract").ContractProcedureBuilderWithInputOutput<z.ZodObject<{
|
|
1588
1792
|
source: z.ZodString;
|
|
1589
1793
|
name: z.ZodString;
|
package/dist/index.js
CHANGED
|
@@ -3,7 +3,7 @@ import { RPCLink } from "@orpc/client/fetch";
|
|
|
3
3
|
import * as z from "zod";
|
|
4
4
|
//#endregion
|
|
5
5
|
//#region src/check-server.ts
|
|
6
|
-
const CLIENT_VERSION = "0.
|
|
6
|
+
const CLIENT_VERSION = "0.7.0";
|
|
7
7
|
async function checkServer(rpc) {
|
|
8
8
|
const info = await rpc.system.info();
|
|
9
9
|
return {
|
|
@@ -65,10 +65,14 @@ const ExecStartMessageSchema = z.object({
|
|
|
65
65
|
tty: z.boolean(),
|
|
66
66
|
cols: DimensionSchema.optional(),
|
|
67
67
|
rows: DimensionSchema.optional(),
|
|
68
|
-
session: SessionNameSchema.optional()
|
|
68
|
+
session: SessionNameSchema.optional(),
|
|
69
|
+
tool: z.enum(["tar"]).optional()
|
|
69
70
|
}).refine((start) => start.session === void 0 || start.tty, {
|
|
70
71
|
message: "a session needs a tty",
|
|
71
72
|
path: ["tty"]
|
|
73
|
+
}).refine((start) => start.tool === void 0 || !start.tty && start.session === void 0 && start.env === void 0 && start.cwd === void 0, {
|
|
74
|
+
message: "a tool takes no tty, session, env or cwd",
|
|
75
|
+
path: ["tool"]
|
|
72
76
|
});
|
|
73
77
|
const ExecAttachMessageSchema = z.object({
|
|
74
78
|
type: z.literal("attach"),
|
|
@@ -89,6 +93,10 @@ z.discriminatedUnion("type", [
|
|
|
89
93
|
z.object({
|
|
90
94
|
type: z.literal("signal"),
|
|
91
95
|
signal: z.string().regex(/^SIG[A-Z0-9]+$/)
|
|
96
|
+
}),
|
|
97
|
+
z.object({
|
|
98
|
+
type: z.literal("stdout_ack"),
|
|
99
|
+
bytes: z.int().positive()
|
|
92
100
|
})
|
|
93
101
|
]);
|
|
94
102
|
const ExecServerMessageSchema = z.discriminatedUnion("type", [
|
|
@@ -111,6 +119,10 @@ const ExecServerMessageSchema = z.discriminatedUnion("type", [
|
|
|
111
119
|
"lost"
|
|
112
120
|
])
|
|
113
121
|
}),
|
|
122
|
+
z.object({
|
|
123
|
+
type: z.literal("stdin_ack"),
|
|
124
|
+
bytes: z.int().positive()
|
|
125
|
+
}),
|
|
114
126
|
z.object({
|
|
115
127
|
type: z.literal("error"),
|
|
116
128
|
message: z.string(),
|
|
@@ -210,7 +222,8 @@ function openExecSession(options) {
|
|
|
210
222
|
const state = {
|
|
211
223
|
opened: false,
|
|
212
224
|
started: false,
|
|
213
|
-
finished: false
|
|
225
|
+
finished: false,
|
|
226
|
+
refused: false
|
|
214
227
|
};
|
|
215
228
|
ws.binaryType = "arraybuffer";
|
|
216
229
|
const stopSocket = () => {
|
|
@@ -270,7 +283,7 @@ function openExecSession(options) {
|
|
|
270
283
|
kind: "detached",
|
|
271
284
|
reason: message.reason
|
|
272
285
|
});
|
|
273
|
-
else resolveOutcome({
|
|
286
|
+
else if (message.type === "stdin_ack") {} else resolveOutcome({
|
|
274
287
|
kind: "failed",
|
|
275
288
|
code: message.code ?? null,
|
|
276
289
|
message: message.message,
|
|
@@ -299,6 +312,8 @@ function openExecSession(options) {
|
|
|
299
312
|
}
|
|
300
313
|
});
|
|
301
314
|
const resolveRefusal = async (reason) => {
|
|
315
|
+
if (state.refused) return;
|
|
316
|
+
state.refused = true;
|
|
302
317
|
const access = await checkImpdAccess(options.baseUrl, options.token, options.fetch);
|
|
303
318
|
if (access === "unauthorized") resolveOutcome({ kind: "unauthorized" });
|
|
304
319
|
else if (access === "reachable" && options.ticket !== void 0) resolveOutcome({
|
|
@@ -314,6 +329,9 @@ function openExecSession(options) {
|
|
|
314
329
|
reason
|
|
315
330
|
});
|
|
316
331
|
};
|
|
332
|
+
ws.addEventListener("error", () => {
|
|
333
|
+
if (!state.opened && !state.finished) resolveRefusal("the connection failed");
|
|
334
|
+
});
|
|
317
335
|
ws.addEventListener("close", (event) => {
|
|
318
336
|
if (state.finished) return;
|
|
319
337
|
const reason = event.reason === "" ? `code ${String(event.code)}` : event.reason;
|
|
@@ -449,13 +467,21 @@ async function openHandle(deps, options, tty, start) {
|
|
|
449
467
|
state.ended = true;
|
|
450
468
|
stdout.end();
|
|
451
469
|
stderr.end();
|
|
452
|
-
abort?.removeEventListener("abort",
|
|
470
|
+
abort?.removeEventListener("abort", stopForAbort);
|
|
453
471
|
if ("error" in result) started.resolve({ error: result.error });
|
|
454
472
|
ended.resolve(result);
|
|
455
473
|
};
|
|
456
474
|
const stopSession = () => {
|
|
457
475
|
stopWith("CLOSED", "the exec session was closed");
|
|
458
476
|
};
|
|
477
|
+
const stopForAbort = () => {
|
|
478
|
+
if (session.isStarted()) {
|
|
479
|
+
stopSession();
|
|
480
|
+
return;
|
|
481
|
+
}
|
|
482
|
+
session.stop();
|
|
483
|
+
resolveHandle({ error: readAbortReason(abort) });
|
|
484
|
+
};
|
|
459
485
|
const waitForOutcome = async () => {
|
|
460
486
|
const outcome = await session.outcome;
|
|
461
487
|
const result = outcome.kind === "exit" ? { value: {
|
|
@@ -464,8 +490,8 @@ async function openHandle(deps, options, tty, start) {
|
|
|
464
490
|
} } : { error: toExecError(outcome) };
|
|
465
491
|
resolveHandle(result);
|
|
466
492
|
};
|
|
467
|
-
if (abort?.aborted === true)
|
|
468
|
-
else abort?.addEventListener("abort",
|
|
493
|
+
if (abort?.aborted === true) stopForAbort();
|
|
494
|
+
else abort?.addEventListener("abort", stopForAbort, { once: true });
|
|
469
495
|
waitForOutcome();
|
|
470
496
|
return buildHandle(session, {
|
|
471
497
|
started: started.promise,
|
|
@@ -476,6 +502,10 @@ async function openHandle(deps, options, tty, start) {
|
|
|
476
502
|
streams: [stdout.stream, stderr.stream]
|
|
477
503
|
});
|
|
478
504
|
}
|
|
505
|
+
function readAbortReason(abort) {
|
|
506
|
+
const reason = abort?.reason;
|
|
507
|
+
return reason instanceof Error ? reason : new DOMException("the exec was aborted", "AbortError");
|
|
508
|
+
}
|
|
479
509
|
function openConsole(deps, name, options = {}) {
|
|
480
510
|
return openExec(deps, name, [
|
|
481
511
|
"/bin/sh",
|