anbaric 1.59.0 → 1.61.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/docs/api/cli.md CHANGED
@@ -75,7 +75,7 @@ ask for, so the CLI runs unattended in scripts and CI.
75
75
  | `--name <name>` | `app configure`/`deploy`/`update` | App name; prompts if omitted (suggested from `package.json`). |
76
76
  | `--port <port>` | `app configure`/`deploy`/`update` | Internal port (1–65535); prompts if omitted. |
77
77
  | `--yes` | `app deploy`, `app tear-down`, `jobs kill-old` | Skip confirmation. (`app update` implies it.) |
78
- | `--state <state>`, `--status <status>`, `--app <app>` | `jobs list` | Only jobs in that state / with that status (`active`, `Awaiting input`, `Failed`) / belonging to that app. |
78
+ | `--state <state>`, `--status <status>`, `--app <app>` | `jobs list` | Only jobs in that state / with that status (`active`, `Awaiting input`, `Failed`, `Stalled`) / belonging to that app. |
79
79
  | `--page <n>`, `--page-size <n>` | `jobs list` | Which page (from 0) and how many per page (default 100). |
80
80
  | `--oldest` | `jobs list` | Oldest first instead of newest. |
81
81
  | `--help`, `-h` | all | Print usage. |
@@ -156,6 +156,7 @@ readonly id : string
156
156
  name : string
157
157
  description : string
158
158
  actor : Actor
159
+ longRunning : boolean = false
159
160
 
160
161
  constructor(name : string, actor : Actor, description : string = "", id? : string)
161
162
 
@@ -177,6 +178,10 @@ sendWelcome.run = async (job) => new Map([["welcomeSent", true]]);
177
178
  - **`run`** — return a `Map` of the properties to change. Only properties in the
178
179
  machine's schema are applied; others are ignored with a warning. Only the
179
180
  properties that actually changed are written back.
181
+ - **`longRunning`** — set it on a step that takes minutes rather than seconds.
182
+ Nothing constrains how long a step may take either way; what this changes is
183
+ that the job is [heartbeated](#stalled-jobs) while the step runs, so a step
184
+ that dies is visible rather than silent.
180
185
 
181
186
  See [Actions and actors](../features/actions-and-actors.md).
182
187
 
@@ -269,22 +274,24 @@ One instance moving through a machine. You mostly **read** jobs (returned by
269
274
  ```ts
270
275
  readonly id : string
271
276
  readonly state : string
272
- readonly properties : Map<string, any>
277
+ readonly properties : JobProperties // read on demand; see below
273
278
  readonly workflowId? : string // the machine's id …
274
279
  readonly appId? : string // … and the app it runs in (its composite identity)
275
280
  readonly startedAt : Date
276
281
  readonly startedBy : string
277
282
  readonly lastUpdated : Date
278
283
  readonly killed : boolean
279
- status : string // "active", "Awaiting input" or "Failed"
284
+ status : string // "active", "Awaiting input", "Failed" or "Stalled"
280
285
  waitingFor? : string
281
286
  awaitMetadata? : WaitForInput // present while parked on an Await
287
+ heartbeatAt? : Date // when a long-running step last said it was going
282
288
 
283
289
  namespace Job {
284
290
  const Status = {
285
291
  ACTIVE: "active",
286
292
  AWAITING_INPUT: "Awaiting input",
287
293
  FAILED: "Failed",
294
+ STALLED: "Stalled",
288
295
  } as const
289
296
  }
290
297
  ```
@@ -296,6 +303,23 @@ A job whose action threw is `FAILED`, with the reason in its audit trail. It
296
303
  stays in its state rather than transitioning, and an update that moves it on
297
304
  returns it to `ACTIVE` — so a failure is recoverable, not terminal.
298
305
 
306
+ ### Stalled jobs
307
+
308
+ While an action marked [`longRunning`](#action) runs, the machine heartbeats
309
+ its job once a minute. If those heartbeats stop for five minutes — the process
310
+ running the step died, in a crash or a restart — the job is marked `STALLED`.
311
+
312
+ That is all that happens. The platform does not re-run the step and does not
313
+ requeue the job: only the app knows what a half-finished long step already did,
314
+ so replaying it is not the platform's call. A stalled job is a job somebody
315
+ should look at — list them with `anbaric jobs list --status Stalled` — and
316
+ resume, by updating it or setting its state, if that is the right thing to do.
317
+ A heartbeat arriving from a job already marked stalled returns it to `ACTIVE`,
318
+ so a step that was merely slower than the sweep corrects itself.
319
+
320
+ Ordinary actions are not heartbeated: they are expected to be short, and the
321
+ [drain](cli.md#apps) on deploy is what protects them.
322
+
299
323
  ### `JobProperties`
300
324
 
301
325
  `job.properties` is not a `Map`: it reads on demand, so a job may hold a great
@@ -339,7 +363,7 @@ type JobPersistence.Query = {
339
363
  workflowId? : string,
340
364
  appId? : string,
341
365
  state? : string,
342
- status? : string, // "active", "Awaiting input", "Failed"
366
+ status? : string, // "active", "Awaiting input", "Failed", "Stalled"
343
367
  killed? : boolean,
344
368
  order? : "oldest" | "newest" // by when the job was started; oldest by default
345
369
  }
package/docs/api/web.md CHANGED
@@ -109,7 +109,7 @@ job was started: `oldest` (the default, for compatibility) or `newest`.
109
109
  The other parameters are filters, applied on the platform **before** paging, so
110
110
  a filter sees every job and page numbers count matching jobs only. All are
111
111
  optional and combine with AND: `workflowId` (the state machine), `appId`,
112
- `state`, `status` (`active`, `Awaiting input`, `Failed`) and `killed`
112
+ `state`, `status` (`active`, `Awaiting input`, `Failed`, `Stalled`) and `killed`
113
113
  (`true`/`false`).
114
114
 
115
115
  In code the same listing is `JobPersistenceFactory.instance().list(actor,
@@ -41,6 +41,28 @@ the example above, and why a transition's guard may be too. Afterwards the
41
41
  machine writes back only the properties that changed — never the ones the pass
42
42
  read, let alone the ones it never loaded.
43
43
 
44
+ ### Say when a step is long-running
45
+
46
+ Nothing limits how long an action may take — a mailbox import that runs for
47
+ twenty minutes is a perfectly ordinary action. But a step that long is worth
48
+ declaring:
49
+
50
+ ```ts
51
+ importMailbox.longRunning = true;
52
+ ```
53
+
54
+ While it runs, the machine tells the platform once a minute that the job is
55
+ still going. If that stops for five minutes — the process died, in a crash or
56
+ a restart — the job is marked **`Stalled`**. Nothing else happens: the platform
57
+ never re-runs a long step on its own, because only your app knows what a
58
+ half-finished one already did. A stalled job is one for a person to look at
59
+ (`anbaric jobs list --status Stalled`) and resume if it should be. A heartbeat
60
+ from a stalled job puts it back to `active`, so a step slower than the check
61
+ corrects itself.
62
+
63
+ Leave it off for ordinary steps: they are short, and a
64
+ [drain](../api/cli.md#apps) on deploy is what keeps those from being cut off.
65
+
44
66
  ### Prewarm what a state needs
45
67
 
46
68
  Each property read that wasn't already held is a fetch. A state can name what
@@ -46,12 +46,12 @@ unrouted absolute path that arrives with a `Referer` from your app is redirected
46
46
  back to `/app/<name>/…` (a `307`, so form POSTs keep their body). So ordinary
47
47
  absolute links followed from a page of your app just work.
48
48
 
49
- ## Your app's own hostname
49
+ ## Your app's own address
50
50
 
51
- Every app also answers on a hostname of its own:
51
+ Every app answers on an address of its own:
52
52
 
53
53
  ```
54
- https://<app>--<tenant>.<platform domain>
54
+ https://<address>.<platform domain>
55
55
  ```
56
56
 
57
57
  There your app is at the root, exactly where it was when you ran it locally, so
@@ -59,10 +59,29 @@ absolute paths, `fetch("/api/…")` and anything else that assumes the root work
59
59
  without being rewritten. This is the address to share and the one to prefer; the
60
60
  `/app/<name>` form below stays available and unchanged.
61
61
 
62
- The tenant is part of the name because two tenants may each have a `hello-world`.
63
- A double hyphen separates the two, so an app name may not contain one, and the
64
- app and tenant together must fit the 63 characters a hostname label allows. A
65
- deploy that would break either rule is refused with the reason.
62
+ `anbaric app deploy` prints the address, and the console shows it next to the app
63
+ on the **Manage apps** page.
64
+
65
+ ### How an address is chosen
66
+
67
+ An address is one label under the platform's domain, so it has to be unique
68
+ across every customer, not just yours. When an app is first deployed it is given
69
+ its own name if that name is free. If it is not, a word is added — `invoices`
70
+ becomes something like `invoices-walnut` — and if even that is crowded, a short
71
+ number. A handful of names are reserved and never handed out.
72
+
73
+ Your app's name is unaffected by any of this. The name is what you deploy,
74
+ reference and tear down by; the address is only where it answers.
75
+
76
+ ### Changing an address
77
+
78
+ A builder can set an app's address on the **Manage apps** page in the console. An
79
+ address someone else already holds is refused, and the old address stops working
80
+ as soon as the new one is saved — so change it before you share it, not after.
81
+
82
+ An address lasts as long as the app. Tearing an app down frees its address for
83
+ anyone to take, including another customer, so a torn-down app's links should be
84
+ treated as gone for good.
66
85
 
67
86
  ## Under /app/&lt;name&gt;
68
87
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "anbaric",
3
- "version": "1.59.0",
3
+ "version": "1.61.0",
4
4
  "description": "Everything needed to write an Anbaric app: state machines, jobs, document and secret stores, local in-memory implementations and the Anbaric Cloud clients",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -24,9 +24,9 @@
24
24
  "prepublishOnly": "npm run build"
25
25
  },
26
26
  "dependencies": {
27
- "anbaric-impl-cloud": "^1.59.0",
28
- "anbaric-data-store": "^1.59.0",
29
- "anbaric-state-machine": "^1.59.0",
30
- "anbaric-tsapi": "^1.59.0"
27
+ "anbaric-impl-cloud": "^1.61.0",
28
+ "anbaric-data-store": "^1.61.0",
29
+ "anbaric-state-machine": "^1.61.0",
30
+ "anbaric-tsapi": "^1.61.0"
31
31
  }
32
32
  }