@akterjs/client 0.1.2 → 0.1.4

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
@@ -14,5 +14,7 @@ An actor whose contract declares `files: Akter.fileAccess({ callers })` has file
14
14
 
15
15
  `@akterjs/client/operations` is the actor half of the operation registry that drives the `akter` CLI and the hosted MCP server (the Cloud half is `@akterjs/cloud-api/operations`): `actorOperations` lists each exposed kind (`call`, `query`, `state`, `watch`, `create`, `ensure`, `fork`, and the file kinds `files/list`, `files/usage`, `files/download` and `files/upload`, whose three session RPCs are one kind) with its command, tool name, input schema and annotations, `kindsOf` gives every RPC of an actor's group its kind or none, so a new kind of RPC fails the coverage test until it has an operation, and `actorRequestOf` builds the RPC tag and payload a caller sends, minting a UUIDv7 command id when the kind takes one and none was given. `@akterjs/client/actor-rpc` holds the one-RPC unary and streaming clients those requests go through, with schema-free JSON payloads, and the file transfers both callers run: `uploadSession({ request, content, wait })` opens an upload, appends the bytes in chunks of at most 1 MiB at their offsets and closes it for the receipt, and `downloadChunks({ request, wait })` collects a download's base64 chunks, failing whole rather than returning part of a file.
16
16
 
17
+ `@akterjs/client/cloud-pending` is the Cloud half's one reply both callers ask again on: `isStillRunning({ status, body })` says whether a Cloud API answer is the 503 "still running, ask again with the same id" that a fork past the API's 30 second deadline gets, and `askAgainSchedule` is how they ask, 1, 2 and 4 seconds apart and then every 5, for about 5 minutes.
18
+
17
19
  - Depends on: `@akterjs/effect` (peer, contracts only) and `effect` (peer). `@akterjs/effect` is a peer so the app and the client share one copy: the client decodes errors into its classes, and a second copy would break `instanceof` on them.
18
20
  - Must not depend on: any private package.
@@ -151,7 +151,7 @@ const declared = [
151
151
  readOnly: true
152
152
  },
153
153
  {
154
- description: "Copy one file (at most 1 MiB) between an actor and its computer. fromComputer returns a receipt; attach it with an actor command.",
154
+ description: "Copy one file between an actor and its computer. fromComputer returns a receipt; attach it with an actor command.",
155
155
  fields: [
156
156
  "actor",
157
157
  "key",
@@ -0,0 +1,16 @@
1
+ import { Schedule } from "effect";
2
+ interface Answered {
3
+ readonly body: unknown;
4
+ readonly status: number;
5
+ }
6
+ /** Whether an answer is the Cloud API's documented "still running, ask again with the same id". */
7
+ declare const isStillRunning: ({ body, status }: Answered) => boolean;
8
+ /** How long a caller keeps asking again before it gives the 503 back as the answer. */
9
+ declare const stillRunningWait = "5 minutes";
10
+ /**
11
+ * How a caller asks again while an operation is still running: 1, 2 and 4 seconds apart, then every 5, for about
12
+ * five minutes. Each ask is held by the Cloud API for up to its 30 s deadline, so the waits only keep a fast refusal
13
+ * from becoming a tight loop.
14
+ */
15
+ declare const askAgainSchedule: Schedule.Schedule<import("effect/Duration").Duration, unknown, never, never>;
16
+ export { askAgainSchedule, isStillRunning, stillRunningWait };
@@ -0,0 +1,21 @@
1
+ import { Schedule, Schema } from "effect";
2
+ // src/cloud/cloud-pending.pure.ts
3
+ /**
4
+ * The Cloud API answers an operation that outlasts its 30 s deadline, such as a fork that copies computers, with 503
5
+ * ActorUnavailableError and a reason saying it is still running and to ask again with the same id, which answers it and
6
+ * never does the work twice (akter-cloud's actorForks.create). Anything else a 503 says, such as a lost reply or a fork
7
+ * that didn't finish, is an error: only this reason means that asking again, unchanged, is the way to the answer.
8
+ */
9
+ const unavailable = 503;
10
+ const StillRunning = Schema.TaggedStruct("ActorUnavailableError", { reason: Schema.String.check(Schema.isPattern(/\bis still running\b.*\bask again with the same \w+ id\b/iu)) });
11
+ /** Whether an answer is the Cloud API's documented "still running, ask again with the same id". */
12
+ const isStillRunning = ({ body, status }) => status === unavailable && Schema.is(StillRunning)(body);
13
+ /** How long a caller keeps asking again before it gives the 503 back as the answer. */
14
+ const stillRunningWait = "5 minutes";
15
+ /**
16
+ * How a caller asks again while an operation is still running: 1, 2 and 4 seconds apart, then every 5, for about
17
+ * five minutes. Each ask is held by the Cloud API for up to its 30 s deadline, so the waits only keep a fast refusal
18
+ * from becoming a tight loop.
19
+ */
20
+ const askAgainSchedule = Schedule.max([Schedule.min([Schedule.exponential("1 second"), Schedule.spaced("5 seconds")]), Schedule.during(stillRunningWait)]);
21
+ export { askAgainSchedule, isStillRunning, stillRunningWait };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@akterjs/client",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "Effect and Promise clients for calling Akter actors from Bun, Node, Workers and browsers.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -34,6 +34,10 @@
34
34
  "./actor-rpc": {
35
35
  "types": "./dist/actor-rpc.pure.d.mts",
36
36
  "default": "./dist/actor-rpc.pure.mjs"
37
+ },
38
+ "./cloud-pending": {
39
+ "types": "./dist/cloud/cloud-pending.pure.d.mts",
40
+ "default": "./dist/cloud/cloud-pending.pure.mjs"
37
41
  }
38
42
  },
39
43
  "publishConfig": {
@@ -41,7 +45,7 @@
41
45
  "provenance": true
42
46
  },
43
47
  "devDependencies": {
44
- "@akterjs/effect": "0.1.2",
48
+ "@akterjs/effect": "0.1.4",
45
49
  "@akterjs/workerd-test": "0.0.0",
46
50
  "@effect/platform-node": "4.1.2",
47
51
  "@effect/vitest": "4.1.2",
@@ -55,7 +59,7 @@
55
59
  "vitest": "5.1.0"
56
60
  },
57
61
  "peerDependencies": {
58
- "@akterjs/effect": "0.1.2",
62
+ "@akterjs/effect": "0.1.4",
59
63
  "effect": "4.1.2"
60
64
  },
61
65
  "scripts": {
@@ -171,7 +171,7 @@ const declared: readonly Declared[] = [
171
171
  },
172
172
  {
173
173
  description:
174
- "Copy one file (at most 1 MiB) between an actor and its computer. fromComputer returns a receipt; attach it with an actor command.",
174
+ "Copy one file between an actor and its computer. fromComputer returns a receipt; attach it with an actor command.",
175
175
  fields: ["actor", "key", "computer", "direction", "path", "computerPath"],
176
176
  flag: "computerFiles",
177
177
  idempotent: false,
@@ -0,0 +1,36 @@
1
+ import { Schedule, Schema } from "effect";
2
+
3
+ /**
4
+ * The Cloud API answers an operation that outlasts its 30 s deadline, such as a fork that copies computers, with 503
5
+ * ActorUnavailableError and a reason saying it is still running and to ask again with the same id, which answers it and
6
+ * never does the work twice (akter-cloud's actorForks.create). Anything else a 503 says, such as a lost reply or a fork
7
+ * that didn't finish, is an error: only this reason means that asking again, unchanged, is the way to the answer.
8
+ */
9
+ const unavailable = 503;
10
+
11
+ const StillRunning = Schema.TaggedStruct("ActorUnavailableError", {
12
+ reason: Schema.String.check(Schema.isPattern(/\bis still running\b.*\bask again with the same \w+ id\b/iu)),
13
+ });
14
+
15
+ interface Answered {
16
+ readonly body: unknown;
17
+ readonly status: number;
18
+ }
19
+
20
+ /** Whether an answer is the Cloud API's documented "still running, ask again with the same id". */
21
+ const isStillRunning = ({ body, status }: Answered): boolean => status === unavailable && Schema.is(StillRunning)(body);
22
+
23
+ /** How long a caller keeps asking again before it gives the 503 back as the answer. */
24
+ const stillRunningWait = "5 minutes";
25
+
26
+ /**
27
+ * How a caller asks again while an operation is still running: 1, 2 and 4 seconds apart, then every 5, for about
28
+ * five minutes. Each ask is held by the Cloud API for up to its 30 s deadline, so the waits only keep a fast refusal
29
+ * from becoming a tight loop.
30
+ */
31
+ const askAgainSchedule = Schedule.max([
32
+ Schedule.min([Schedule.exponential("1 second"), Schedule.spaced("5 seconds")]),
33
+ Schedule.during(stillRunningWait),
34
+ ]);
35
+
36
+ export { askAgainSchedule, isStillRunning, stillRunningWait };