@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 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 `Promise.withResolvers`: Chrome 119, Firefox 121 and Safari
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.5.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", stopSession);
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) stopSession();
468
- else abort?.addEventListener("abort", stopSession, { once: true });
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",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zgeoff/imp-client",
3
- "version": "0.5.0",
3
+ "version": "0.7.0",
4
4
  "description": "Typed client for impd, the imp host daemon: imps, checkpoints, images and exec",
5
5
  "license": "MIT",
6
6
  "repository": {