@zgeoff/imp-client 0.6.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.6.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 {
@@ -222,7 +222,8 @@ function openExecSession(options) {
222
222
  const state = {
223
223
  opened: false,
224
224
  started: false,
225
- finished: false
225
+ finished: false,
226
+ refused: false
226
227
  };
227
228
  ws.binaryType = "arraybuffer";
228
229
  const stopSocket = () => {
@@ -311,6 +312,8 @@ function openExecSession(options) {
311
312
  }
312
313
  });
313
314
  const resolveRefusal = async (reason) => {
315
+ if (state.refused) return;
316
+ state.refused = true;
314
317
  const access = await checkImpdAccess(options.baseUrl, options.token, options.fetch);
315
318
  if (access === "unauthorized") resolveOutcome({ kind: "unauthorized" });
316
319
  else if (access === "reachable" && options.ticket !== void 0) resolveOutcome({
@@ -326,6 +329,9 @@ function openExecSession(options) {
326
329
  reason
327
330
  });
328
331
  };
332
+ ws.addEventListener("error", () => {
333
+ if (!state.opened && !state.finished) resolveRefusal("the connection failed");
334
+ });
329
335
  ws.addEventListener("close", (event) => {
330
336
  if (state.finished) return;
331
337
  const reason = event.reason === "" ? `code ${String(event.code)}` : event.reason;
@@ -461,13 +467,21 @@ async function openHandle(deps, options, tty, start) {
461
467
  state.ended = true;
462
468
  stdout.end();
463
469
  stderr.end();
464
- abort?.removeEventListener("abort", stopSession);
470
+ abort?.removeEventListener("abort", stopForAbort);
465
471
  if ("error" in result) started.resolve({ error: result.error });
466
472
  ended.resolve(result);
467
473
  };
468
474
  const stopSession = () => {
469
475
  stopWith("CLOSED", "the exec session was closed");
470
476
  };
477
+ const stopForAbort = () => {
478
+ if (session.isStarted()) {
479
+ stopSession();
480
+ return;
481
+ }
482
+ session.stop();
483
+ resolveHandle({ error: readAbortReason(abort) });
484
+ };
471
485
  const waitForOutcome = async () => {
472
486
  const outcome = await session.outcome;
473
487
  const result = outcome.kind === "exit" ? { value: {
@@ -476,8 +490,8 @@ async function openHandle(deps, options, tty, start) {
476
490
  } } : { error: toExecError(outcome) };
477
491
  resolveHandle(result);
478
492
  };
479
- if (abort?.aborted === true) stopSession();
480
- else abort?.addEventListener("abort", stopSession, { once: true });
493
+ if (abort?.aborted === true) stopForAbort();
494
+ else abort?.addEventListener("abort", stopForAbort, { once: true });
481
495
  waitForOutcome();
482
496
  return buildHandle(session, {
483
497
  started: started.promise,
@@ -488,6 +502,10 @@ async function openHandle(deps, options, tty, start) {
488
502
  streams: [stdout.stream, stderr.stream]
489
503
  });
490
504
  }
505
+ function readAbortReason(abort) {
506
+ const reason = abort?.reason;
507
+ return reason instanceof Error ? reason : new DOMException("the exec was aborted", "AbortError");
508
+ }
491
509
  function openConsole(deps, name, options = {}) {
492
510
  return openExec(deps, name, [
493
511
  "/bin/sh",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zgeoff/imp-client",
3
- "version": "0.6.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": {