anbaric 1.19.0 → 1.20.1
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 +337 -154
- package/package.json +9 -5
package/README.md
CHANGED
|
@@ -1,208 +1,370 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Anbaric
|
|
2
2
|
|
|
3
|
-
Anbaric is a TypeScript framework for building stateful applications.
|
|
4
|
-
application is expressed as one or more state machines: long-lived jobs move
|
|
5
|
-
through named states, driven by actions and transitions, with their data
|
|
6
|
-
validated against a schema and every change recorded in an audit trail.
|
|
7
|
-
Alongside the state machine the framework provides schema-validated document
|
|
8
|
-
storage and secret storage.
|
|
3
|
+
Anbaric is a TypeScript framework for building stateful enterprise applications. Anbaric is designed to help vibe-coded applications go the last mile, by providing persistence, state management, authentication and auditing.
|
|
9
4
|
|
|
10
|
-
|
|
11
|
-
same application runs unchanged on a developer's machine, on a server you
|
|
12
|
-
operate, or on Anbaric Cloud, where a single CLI command deploys it. Application
|
|
13
|
-
code depends only on the interfaces in this package; the framework selects
|
|
14
|
-
in-memory or platform-backed implementations from the environment, so no
|
|
15
|
-
deployment detail appears in the code.
|
|
5
|
+
We believe that fully bespoke software is the future, and this project was created to help a wider range of people build their own solutions, without compromising enterprise IT standards.
|
|
16
6
|
|
|
17
|
-
|
|
18
|
-
|
|
7
|
+
## Getting started
|
|
8
|
+
|
|
9
|
+
Install the Anbaric package and the CLI:
|
|
19
10
|
|
|
20
11
|
```bash
|
|
12
|
+
npm init
|
|
21
13
|
npm install anbaric
|
|
14
|
+
npm install -g anbaric-cli
|
|
22
15
|
```
|
|
23
16
|
|
|
24
|
-
|
|
25
|
-
set `"type": "module"` in `package.json`, point `main` at the TypeScript entry
|
|
26
|
-
file, and run it with `tsx`.
|
|
27
|
-
|
|
28
|
-
## State machines
|
|
29
|
-
|
|
30
|
-
A **`StateMachine`** defines a workflow. It is constructed with an identifier, a
|
|
31
|
-
list of states, the name of the start state, and the property schema for its
|
|
32
|
-
jobs.
|
|
33
|
-
|
|
34
|
-
- A **`State`** is a named step. It holds a list of **actions** to run while a
|
|
35
|
-
job sits in that state, and a list of **transitions** to the next state.
|
|
36
|
-
- An **`Action`** is a unit of work. Its `run` function receives the job and
|
|
37
|
-
returns a `Map` of property changes: `run(job) => Promise<Map<string, any>>`.
|
|
38
|
-
An action does not mutate the job directly; the returned properties are
|
|
39
|
-
validated and applied by the machine. Each action declares the **actor** that
|
|
40
|
-
performs it.
|
|
41
|
-
- A **`Transition`** names a target state and carries a predicate over the job.
|
|
42
|
-
When a job is processed, the first transition whose predicate holds moves the
|
|
43
|
-
job to that state.
|
|
44
|
-
- A **`Job`** is one instance moving through the machine. It carries an
|
|
45
|
-
identifier, its current `state`, a `Map` of `properties`, and provenance
|
|
46
|
-
(`workflowId`, `startedBy`, `startedAt`, `lastUpdated`). Jobs are immutable;
|
|
47
|
-
each change produces a new version.
|
|
48
|
-
- A **`PropertyDefinition`** describes one property a job may carry: whether it
|
|
49
|
-
is required and how its value is validated. Every property a job holds —
|
|
50
|
-
including properties set by actions — must have a definition, or the change is
|
|
51
|
-
rejected.
|
|
52
|
-
- An **`Actor`** identifies who performs an operation, for authorization and
|
|
53
|
-
auditing. The actor types are `Code`, `Human`, `Agent` and `System`. `Code`
|
|
54
|
-
actions run automatically as jobs are processed; a `Human` or `Agent` actor
|
|
55
|
-
represents work performed by a person or an autonomous agent.
|
|
56
|
-
|
|
57
|
-
A job progresses automatically as it is processed: matching actions run, their
|
|
58
|
-
properties are applied, and the first satisfied transition advances the state.
|
|
59
|
-
This repeats until the job reaches a state from which nothing more applies.
|
|
17
|
+
Prompt your agent:
|
|
60
18
|
|
|
61
|
-
|
|
19
|
+
```
|
|
20
|
+
Use Anbaric to build me a CRM.
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Run locally as usual (`npm run start`) or deploy to the Anbaric Cloud (`anbaric app deploy`).
|
|
62
24
|
|
|
63
|
-
-
|
|
64
|
-
- `updateJob(jobId, properties, actor)` applies an explicit property change.
|
|
65
|
-
- `executeAction(jobId, action)` runs a single action immediately.
|
|
66
|
-
- Actions subscribed to a state run automatically whenever a job in that state
|
|
67
|
-
is processed.
|
|
25
|
+
Anbaric is open source and you can self-host the platform or sign up at https://cloud.anbaric.ai to use the hosted version.
|
|
68
26
|
|
|
69
|
-
|
|
70
|
-
which records the resource, the actor, the interactions (create, update, state
|
|
71
|
-
change, delete, read), and a description of what changed.
|
|
27
|
+
## What the code looks like
|
|
72
28
|
|
|
73
|
-
|
|
29
|
+
Anbaric has a collection of features that can be used in your application. Here are some examples:
|
|
74
30
|
|
|
75
|
-
|
|
76
|
-
obtained from factories and used with an actor for auditing.
|
|
31
|
+
### Persist JSON
|
|
77
32
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
-
|
|
81
|
-
records secret values in the audit trail.
|
|
33
|
+
In this example we will store a customer's details in a JSON document. Every change that is made in Anbaric requires an `Actor` to be specified, which is used to ensure a complete audit trail exists for any application built using Anbaric. In general, an actor will be either a "human" actor, an AI agent or code being executed by the system.
|
|
34
|
+
|
|
35
|
+
When your app serves a page to a signed-in user, `Human.fromSession` turns that request into the human actor behind it — it resolves the browser's platform session (the `anbaric_session` cookie) against the platform, so the audit trail names the real user. Pass it the incoming request (or the session token directly). Outside a request — a seed script or system code — construct the actor yourself, e.g. `new Human("ada", ["admin"])` or a `Code` actor.
|
|
82
36
|
|
|
83
37
|
```ts
|
|
84
|
-
import {Human, JsonStoreFactory
|
|
38
|
+
import {Human, JsonStoreFactory} from "anbaric";
|
|
85
39
|
|
|
86
|
-
|
|
40
|
+
// inside your app's HTTP handler, `request` is the incoming browser request
|
|
41
|
+
const actor = await Human.fromSession(request);
|
|
42
|
+
const customers = JsonStoreFactory.instance("customers");
|
|
43
|
+
|
|
44
|
+
await customers.create(actor, "ada", { name: "Ada Lovelace", email: "ada@example.com" });
|
|
45
|
+
const ada = await customers.retrieve("ada", actor);
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Run a state machine
|
|
49
|
+
|
|
50
|
+
A state machine models work as jobs moving through named states. Actions run while a job sits in a state, and transitions decide where it goes next. A state machine can combine steps using different types of actors, so humans can own some action, AI agents and code can automate others.
|
|
51
|
+
|
|
52
|
+
```ts
|
|
53
|
+
import {Action, Code, PropertyDefinition, State, StateMachine, Transition} from "anbaric";
|
|
54
|
+
|
|
55
|
+
const sendWelcome = new Action("Send welcome email", new Code("welcome"));
|
|
56
|
+
sendWelcome.run = async (job) => {
|
|
57
|
+
// Access properties stored against the job
|
|
58
|
+
const email = job.properties.get("email");
|
|
59
|
+
// Run some code
|
|
60
|
+
console.log(`Sending welcome email to ${email}`);
|
|
61
|
+
// Return new properties to add to the job
|
|
62
|
+
const newProperties = new Map();
|
|
63
|
+
newProperties.set("emailSent", true);
|
|
64
|
+
return newProperties;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const onboarding = new StateMachine("onboarding", [
|
|
68
|
+
new State("new", [sendWelcome], [new Transition("active", (job) => job.properties.get("emailSent") === true)]),
|
|
69
|
+
new State("active"),
|
|
70
|
+
]);
|
|
71
|
+
|
|
72
|
+
const customer = await onboarding.startJob(new Map([["email", "ada@example.com"]]));
|
|
73
|
+
```
|
|
87
74
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
75
|
+
The job starts in `new`; when it is processed the action runs, sets `emailSent`, and the transition advances it to `active`.
|
|
76
|
+
|
|
77
|
+
### Using AI agents to automate states
|
|
78
|
+
|
|
79
|
+
An **agent** is an actor whose properties are produced by a model rather than by
|
|
80
|
+
hand-written code. A `RemoteLLMAgenticAction` gives the agent a prompt and a JSON
|
|
81
|
+
Schema, and applies the properties it returns to the job. Here an OpenAI-backed
|
|
82
|
+
agent triages a support ticket:
|
|
83
|
+
|
|
84
|
+
```ts
|
|
85
|
+
import {OpenAIAgent, RemoteLLMAgenticAction, State, StateMachine, Transition} from "anbaric";
|
|
86
|
+
|
|
87
|
+
const triager = new OpenAIAgent("triager", "support", {
|
|
88
|
+
apiKey: process.env.OPENAI_API_KEY!,
|
|
89
|
+
model: "gpt-5.4-mini",
|
|
92
90
|
});
|
|
93
|
-
await customers.create(actor, "ada", { name: "Ada" });
|
|
94
|
-
const record = await customers.retrieve("ada", actor);
|
|
95
91
|
|
|
96
|
-
const
|
|
97
|
-
|
|
92
|
+
const triage = new RemoteLLMAgenticAction(
|
|
93
|
+
"Triage the ticket",
|
|
94
|
+
triager,
|
|
95
|
+
[{ role: "system", content: "Decide the priority of the support ticket from its subject." }],
|
|
96
|
+
{ type: "object", properties: { priority: { type: "string", enum: ["low", "high"] } } },
|
|
97
|
+
);
|
|
98
|
+
|
|
99
|
+
const support = new StateMachine("support", [
|
|
100
|
+
new State("open", [triage], [new Transition("prioritised", (job) => job.properties.has("priority"))]),
|
|
101
|
+
new State("prioritised"),
|
|
102
|
+
]);
|
|
98
103
|
```
|
|
99
104
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
Persistence, queueing and consumers are supplied by environment-driven
|
|
103
|
-
factories, so the same application runs in several ways without code changes.
|
|
104
|
-
|
|
105
|
-
- **Local.** With no environment configured, every store, queue and consumer is
|
|
106
|
-
in-memory and jobs progress automatically. Running the program with
|
|
107
|
-
`npx tsx src/main.ts` is a complete local run, suitable for development and
|
|
108
|
-
testing.
|
|
109
|
-
- **Anbaric Cloud.** `anbaric app deploy` packages the application (source only, no
|
|
110
|
-
`node_modules`) and runs it on the hosted platform. The platform installs the
|
|
111
|
-
application's own dependencies as it builds the image, so any npm package the
|
|
112
|
-
application imports works when deployed. The platform injects the configuration
|
|
113
|
-
that points the factories at platform-backed persistence, queueing, documents
|
|
114
|
-
and secrets; application code is unchanged. Authentication, job inspection and
|
|
115
|
-
updates are available through the CLI.
|
|
116
|
-
- **Self-hosted.** The [`anbaric-hosting`](https://npmjs.com/package/anbaric-hosting)
|
|
117
|
-
package runs the platform — the API, dispatcher, build layer and app proxy —
|
|
118
|
-
on infrastructure you operate, backed by Postgres.
|
|
105
|
+
Because the agent is just another actor, its work is audited like any other —
|
|
106
|
+
the triage decision is attributed to `triager`.
|
|
119
107
|
|
|
120
|
-
|
|
121
|
-
platform sets them when the application is deployed.
|
|
108
|
+
### Wait for human input
|
|
122
109
|
|
|
123
|
-
|
|
110
|
+
Sometimes a job cannot progress by itself — it needs a person to approve
|
|
111
|
+
something, fill in a form, or make a decision. An **`Await`** is a step in a
|
|
112
|
+
state's action list that *pauses* the job instead of running: when a job reaches
|
|
113
|
+
it, the job is parked in the **`Awaiting input`** status and is not processed
|
|
114
|
+
again until its properties are updated. Point people at wherever they provide
|
|
115
|
+
that input with a **`resolveUrl`** — a function of the job, so you can build a
|
|
116
|
+
per-job link with the job id (and anything else) in the query string.
|
|
117
|
+
|
|
118
|
+
Here an order waits for a human to approve it. The app serves a small approval
|
|
119
|
+
UI at `/approve` (any framework — the page itself is omitted here); it reads the
|
|
120
|
+
job id from the query string, collects the decision, and calls
|
|
121
|
+
`updateJob(jobId, { approved: true }, actor)`. The `Await` sends the reviewer
|
|
122
|
+
there, and a transition moves the job on once `approved` is set:
|
|
124
123
|
|
|
125
124
|
```ts
|
|
126
|
-
import {
|
|
125
|
+
import {Await, State, StateMachine, Transition} from "anbaric";
|
|
127
126
|
|
|
128
|
-
const
|
|
129
|
-
|
|
127
|
+
const approve = new Await("Approve the order", "HUMAN");
|
|
128
|
+
approve.fields = ["approved"]; // the input we expect back
|
|
129
|
+
approve.resolveUrl = (job) => `/approve?job=${job.id}`; // where the human provides it
|
|
130
130
|
|
|
131
|
-
const
|
|
132
|
-
|
|
131
|
+
const fulfilment = new StateMachine("fulfilment", [
|
|
132
|
+
new State("review", [approve], [new Transition("approved", (job) => job.properties.get("approved") === true)]),
|
|
133
|
+
new State("approved"),
|
|
134
|
+
]);
|
|
133
135
|
|
|
134
|
-
const
|
|
135
|
-
|
|
136
|
+
const order = await fulfilment.startJob(new Map([["total", 4200]]));
|
|
137
|
+
// The job reaches `approve`, parks in "Awaiting input", and waits.
|
|
138
|
+
// When the approval UI calls updateJob(order.id, { approved: true }, actor),
|
|
139
|
+
// the job resumes, the transition fires, and it advances to `approved`.
|
|
140
|
+
```
|
|
136
141
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
+
The pause is recorded in the audit trail, and the [awaiting-input
|
|
143
|
+
widget](#the-admin-console-and-widgets) lists every parked job — with a
|
|
144
|
+
clickable `resolveUrl` for those awaiting a human. Pass `"EXTERNAL_SYSTEM"`
|
|
145
|
+
instead of `"HUMAN"` when the input will come from another service rather than a
|
|
146
|
+
person.
|
|
142
147
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
);
|
|
148
|
+
### Use an RDBMS
|
|
149
|
+
|
|
150
|
+
A full relational store is available for structured data. Locally it is SQLite; deployed, it is the tenant's PostgreSQL.
|
|
151
|
+
|
|
152
|
+
```ts
|
|
153
|
+
import {Human, SqlStoreFactory} from "anbaric";
|
|
154
|
+
|
|
155
|
+
const actor = new Human("ada", "admin");
|
|
156
|
+
const sql = SqlStoreFactory.instance();
|
|
157
|
+
|
|
158
|
+
await sql.execute(actor, "CREATE TABLE IF NOT EXISTS notes (id INTEGER PRIMARY KEY, body TEXT)");
|
|
159
|
+
await sql.execute(actor, "INSERT INTO notes (body) VALUES (?)", ["hello"]);
|
|
160
|
+
const rows = await sql.query(actor, "SELECT id, body FROM notes");
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### Serve a web UI
|
|
164
|
+
|
|
165
|
+
An app can serve its own HTTP frontend. Listen on `process.env.PORT` and the
|
|
166
|
+
platform's app proxy routes `<platform>/<app-name>/*` to it — combine it with
|
|
167
|
+
the persistence and state-machine APIs above to put your data on a page.
|
|
168
|
+
|
|
169
|
+
```ts
|
|
170
|
+
import {createServer} from "node:http";
|
|
171
|
+
|
|
172
|
+
createServer((request, response) => {
|
|
173
|
+
response.writeHead(200, { "content-type": "text/html" });
|
|
174
|
+
response.end("<h1>My Anbaric app</h1>");
|
|
175
|
+
}).listen(Number(process.env.PORT ?? 3000));
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
Serve trivial pages directly like this; build richer UIs with React and the
|
|
179
|
+
Anbaric design system (`anbaric-design-system`). `sample-apps/crm` is a complete
|
|
180
|
+
worked example — an HTTP server that renders its customers as HTML and exposes a
|
|
181
|
+
JSON API backed by a state machine.
|
|
182
|
+
|
|
183
|
+
## Auditing
|
|
184
|
+
|
|
185
|
+
Every write to a job, document, secret or the SQL store is recorded against the
|
|
186
|
+
actor that made it — which is why the store methods take an actor. An audit
|
|
187
|
+
record captures the actor, the resource, the interaction (create, update, state
|
|
188
|
+
change, delete, kill…) and a short description. Locally the records print to the
|
|
189
|
+
console; deployed, the platform persists them and they are browsable in the
|
|
190
|
+
admin console. Reads and lists can be audited too, but the platform masks them
|
|
191
|
+
by default to keep the volume down.
|
|
192
|
+
|
|
193
|
+
## Using the CLI
|
|
194
|
+
|
|
195
|
+
`anbaric-cli` authorizes your terminal against a platform, deploys apps, and
|
|
196
|
+
inspects and drives their jobs. After a one-time `anbaric login` (a browser
|
|
197
|
+
flow), a typical loop is:
|
|
198
|
+
|
|
199
|
+
```bash
|
|
200
|
+
anbaric app deploy # deploy the current project and wait until it is live
|
|
201
|
+
anbaric jobs list # list jobs and their states
|
|
202
|
+
anbaric jobs watch <job-id> # follow a job as it progresses
|
|
203
|
+
anbaric jobs stats # counts per state and the queue depth
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
Every interactive prompt also has a flag, so the CLI runs unattended in scripts
|
|
207
|
+
and CI. The full command list is in the [CLI reference](#cli-reference) below.
|
|
208
|
+
|
|
209
|
+
## The Admin console and widgets
|
|
210
|
+
|
|
211
|
+
The platform serves an admin console at its root — a dashboard assembled from
|
|
212
|
+
**plugins**. The built-in `anbaric-plugins/state-machines` plugin lists your
|
|
213
|
+
state machines and their jobs. You add your own pages and widgets by writing a
|
|
214
|
+
small plugin — a `pages`/`widgets` object — and naming it in `ANBARIC_PLUGINS`.
|
|
215
|
+
Widgets are React components, optionally backed by a server-side data function,
|
|
216
|
+
rendered with the platform's design system. See
|
|
217
|
+
[`anbaric-plugins`](anbaric-plugins/README.md).
|
|
218
|
+
|
|
219
|
+
## State machine deep-dive
|
|
220
|
+
|
|
221
|
+
A **`StateMachine`** is constructed from a workflow id, its `State`s, the start
|
|
222
|
+
state, and the property schema for its jobs. A **`State`** holds the **actions**
|
|
223
|
+
that run while a job sits in it and the **transitions** to other states. An
|
|
224
|
+
**`Action`** has a `run` function returning the property changes to apply — it
|
|
225
|
+
never mutates the job directly. A **`Transition`** names a target state and a
|
|
226
|
+
predicate; the first transition whose predicate holds moves the job. A **`Job`**
|
|
227
|
+
is one instance moving through the machine, carrying its `properties`, its
|
|
228
|
+
current `state`, and its history. A state can be marked terminal (or use the
|
|
229
|
+
`Terminal` helper, which carries a `SUCCESS`/`FAILURE` outcome); a job that
|
|
230
|
+
reaches one stops and is not processed again.
|
|
231
|
+
|
|
232
|
+
Every action also declares the **actor** it runs as (`Code`, `Human` or
|
|
233
|
+
`Agent` — pure identity objects), recorded for auditing and authorization.
|
|
234
|
+
There are three ways to influence a job:
|
|
235
|
+
|
|
236
|
+
1. **Update it directly**, declaring who is acting:
|
|
237
|
+
`machine.updateJob(jobId, new Map([["approved", true]]), new Human("chris", "manager"))`.
|
|
238
|
+
2. **Execute an action directly** — the actor is embedded in the action:
|
|
239
|
+
`machine.executeAction(jobId, approveAction)`.
|
|
240
|
+
3. **Subscribe an action to a state** — pass it in the `State` constructor or
|
|
241
|
+
call `state.subscribe(action)`, and it runs (predicate permitting)
|
|
242
|
+
whenever a job is processed in that state.
|
|
243
|
+
|
|
244
|
+
Whichever way, property changes are schema-validated and audited, and each
|
|
245
|
+
job carries its history: who started it, every from→to transition and the
|
|
246
|
+
actor that made it.
|
|
247
|
+
|
|
248
|
+
A state's action list may also contain an **`Await`** — a pause point rather
|
|
249
|
+
than an actor acting. When a job reaches one it stops running actions and is
|
|
250
|
+
parked: its `status` becomes `Awaiting input` (distinct from its `state`), and
|
|
251
|
+
it holds a **`WaitForInput`** describing what is expected — the `fields`, a
|
|
252
|
+
resolved `resolveUrl`, whether a `HUMAN` or `EXTERNAL_SYSTEM` is expected, and
|
|
253
|
+
any metadata the `Await` computed for the job. A parked job is not re-processed
|
|
254
|
+
until an `updateJob` arrives; on resume it skips its actions and only
|
|
255
|
+
re-evaluates its transitions, so the input drives it on and the wait clears once
|
|
256
|
+
it moves to another state. Actions placed after an `Await` in the same state do
|
|
257
|
+
not run when the job resumes.
|
|
258
|
+
|
|
259
|
+
Everything is pluggable through env-driven factories: locally (no env vars)
|
|
260
|
+
you get in-memory persistence and queueing; deployed, the same factories talk
|
|
261
|
+
to the platform automatically. The same applies to `JsonStoreFactory`
|
|
262
|
+
(schema-validated documents), `SecretStoreFactory` (encrypted secrets) and
|
|
263
|
+
`SqlStoreFactory` (a relational store) from `anbaric-data-store`.
|
|
264
|
+
|
|
265
|
+
The SQL store is backed by SQLite locally (in-memory by default) and, once
|
|
266
|
+
deployed, by the tenant's PostgreSQL in a dedicated `anbaric_app_data` schema
|
|
267
|
+
shared by all of the tenant's apps. Write portable SQL where you can — the two
|
|
268
|
+
differ in a few places, notably parameter placeholders (`?` for SQLite, `$1`
|
|
269
|
+
for PostgreSQL); see the [`anbaric-data-store`](anbaric-data-store/README.md)
|
|
270
|
+
docs for the full list.
|
|
271
|
+
|
|
272
|
+
If your app serves HTTP, listen on `process.env.PORT` and users reach it at
|
|
273
|
+
`<platform>/<app-name>`. All front-end must be React and use the Anbaric
|
|
274
|
+
design system (`anbaric-design-system`) — see the living style guide by
|
|
275
|
+
opening `anbaric-design-system/dist/index.html`.
|
|
276
|
+
|
|
277
|
+
## Run it locally
|
|
278
|
+
|
|
279
|
+
With no `ANBARIC_*` variables set, the factories return the in-memory
|
|
280
|
+
persistence, queue and stores, so the program runs entirely in-process — no
|
|
281
|
+
database or platform involved. Run the TypeScript entry file directly with tsx;
|
|
282
|
+
`npx` fetches it on demand if your project doesn't already depend on it:
|
|
283
|
+
|
|
284
|
+
```bash
|
|
285
|
+
npx tsx src/main.ts
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
Jobs are held in memory and progress as the in-memory consumer delivers them;
|
|
289
|
+
the state is gone when the process exits. This is the mode for development and
|
|
290
|
+
tests.
|
|
291
|
+
|
|
292
|
+
## Deploy it
|
|
154
293
|
|
|
155
|
-
|
|
294
|
+
```bash
|
|
295
|
+
npm install -g anbaric-cli # once; provides the anbaric command
|
|
296
|
+
anbaric login # pick a platform; a browser authorizes this terminal
|
|
297
|
+
anbaric app configure # writes .anbaric/app-config.json (name + internal port)
|
|
298
|
+
anbaric app deploy # packs, uploads, bakes an image, waits until live
|
|
156
299
|
```
|
|
157
300
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
301
|
+
`app deploy` returns once the app is actually up — its built-in admin port
|
|
302
|
+
answers the platform's liveness ping. Watch it
|
|
303
|
+
work with `anbaric apps`, `anbaric jobs list [state-machine]`, and
|
|
304
|
+
`anbaric jobs watch <job-id>`. The `app` commands run from anywhere inside the
|
|
305
|
+
project — they walk up to the nearest `package.json`.
|
|
306
|
+
|
|
307
|
+
### What deployment does (and expects)
|
|
308
|
+
|
|
309
|
+
The platform unpacks your upload onto a pre-canned base image, installs the
|
|
310
|
+
app's dependencies, bakes a Docker image and runs it as a container. One thing
|
|
311
|
+
it does **not** do:
|
|
312
|
+
|
|
313
|
+
- **No build step.** `npm run build` is never run — your TypeScript source is
|
|
314
|
+
executed directly (via tsx). Ship source, not `dist/`.
|
|
315
|
+
|
|
316
|
+
Dependencies, though, *are* installed: the image runs `npm install` for the
|
|
317
|
+
app's declared dependencies (the `anbaric-*` packages and any others), so
|
|
318
|
+
ordinary npm dependencies work. Only source is uploaded — `node_modules` is
|
|
319
|
+
not.
|
|
320
|
+
|
|
321
|
+
An app must have:
|
|
322
|
+
|
|
323
|
+
- `package.json` with `main` pointing at the entry file (e.g. `src/main.ts`)
|
|
324
|
+
and `"type": "module"`.
|
|
325
|
+
- `.anbaric/app-config.json` with a `name` (lowercase letters, numbers, `-`,
|
|
326
|
+
`_`) and an `internalPort` — `anbaric app configure` creates it, and
|
|
327
|
+
`app deploy` prompts if it's missing.
|
|
162
328
|
|
|
163
|
-
|
|
329
|
+
A built-in admin process answers the platform's liveness check, so an app needs
|
|
330
|
+
no HTTP server of its own to deploy. If it does serve HTTP (reached through the
|
|
331
|
+
app proxy), it listens on `process.env.PORT`. The platform injects all wiring as
|
|
332
|
+
env vars (`ANBARIC_*_TYPE=cloud`, the platform URL, the consumer and admin
|
|
333
|
+
ports) — never hardcode these.
|
|
164
334
|
|
|
165
|
-
|
|
166
|
-
terminal, deploys applications, and inspects and drives jobs on a platform. The
|
|
167
|
-
principal commands are:
|
|
335
|
+
## CLI reference
|
|
168
336
|
|
|
169
337
|
| Command | Purpose |
|
|
170
338
|
| --- | --- |
|
|
171
|
-
| `anbaric login`
|
|
172
|
-
| `anbaric apps` | list deployed
|
|
173
|
-
| `anbaric app configure` |
|
|
174
|
-
| `anbaric app deploy` | deploy
|
|
175
|
-
| `anbaric app update` | deploy
|
|
176
|
-
| `anbaric app status <name>` | show an
|
|
177
|
-
| `anbaric app tail <name>` | stream an
|
|
178
|
-
| `anbaric app tear-down <name>` | stop and remove a deployed
|
|
339
|
+
| `anbaric login` | choose a platform and authorize this terminal (browser flow; keypair saved to `~/.anbaric/`) |
|
|
340
|
+
| `anbaric apps` | list deployed apps |
|
|
341
|
+
| `anbaric app configure` | create or update `.anbaric/app-config.json` |
|
|
342
|
+
| `anbaric app deploy` | deploy the app (prompts before replacing a running one) |
|
|
343
|
+
| `anbaric app update` | deploy, replacing without prompting |
|
|
344
|
+
| `anbaric app status <name>` | show an app's deploy state and whether it is up |
|
|
345
|
+
| `anbaric app tail <name>` | stream an app's runtime logs |
|
|
346
|
+
| `anbaric app tear-down <name>` | stop and remove a deployed app |
|
|
179
347
|
| `anbaric state-machines` | list registered state machines |
|
|
180
348
|
| `anbaric jobs create <sm-id> <start-state> [k=v ...]` | create a job and queue it for processing |
|
|
181
349
|
| `anbaric jobs list [state-machine-id]` | list jobs |
|
|
182
350
|
| `anbaric jobs stats` | job counts per state and the queue size |
|
|
183
|
-
| `anbaric jobs watch <job-id>` | follow a job's state
|
|
184
|
-
| `anbaric jobs set-state <job-id> <state>` | move a job
|
|
185
|
-
| `anbaric jobs update <job-id> <key=value ...>` |
|
|
351
|
+
| `anbaric jobs watch <job-id>` | follow a job's state live |
|
|
352
|
+
| `anbaric jobs set-state <job-id> <state>` | move a job and re-queue it |
|
|
353
|
+
| `anbaric jobs update <job-id> <key=value ...>` | update job properties and re-queue |
|
|
186
354
|
| `anbaric jobs kill <job-id>` | kill a job so it stops progressing |
|
|
187
355
|
| `anbaric jobs kill-old <age>` | kill jobs not updated within `<age>` (e.g. `24h`, `7d`) |
|
|
188
356
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
run from anywhere inside it.
|
|
192
|
-
|
|
193
|
-
Every command accepts flags (such as `--environment`, `--tenant`, `--name`,
|
|
194
|
-
`--port`, `--yes`) that supply the answers a prompt would otherwise ask for, so
|
|
195
|
-
the CLI can be run non-interactively in scripts and CI. See the
|
|
196
|
-
[`anbaric-cli`](https://npmjs.com/package/anbaric-cli) documentation for the
|
|
197
|
-
full command and flag reference.
|
|
357
|
+
All commands accept `--platform-url` and `--tenant`; `login` sets the
|
|
358
|
+
defaults. Manage your CLI keys in the browser at `<platform>/manage-keys`.
|
|
198
359
|
|
|
199
360
|
## Packages
|
|
200
361
|
|
|
201
|
-
`anbaric`
|
|
202
|
-
|
|
362
|
+
`anbaric` is the umbrella install for writing an application — it re-exports the
|
|
363
|
+
app-facing surface of the packages below. Install them individually for a
|
|
364
|
+
narrower dependency.
|
|
203
365
|
|
|
204
366
|
- [`anbaric-tsapi`](https://npmjs.com/package/anbaric-tsapi) — the interfaces
|
|
205
|
-
and value classes: `Job`, `State`, `Action`, `Transition`, `Actor`,
|
|
367
|
+
and value classes: `Job`, `State`, `Action`, `Await`, `Transition`, `Actor`,
|
|
206
368
|
`JobPersistence`, `JsonStore`, `SecretStore`, `Auditor`.
|
|
207
369
|
- [`anbaric-state-machine`](https://npmjs.com/package/anbaric-state-machine) —
|
|
208
370
|
the `StateMachine`, actors, and in-memory implementations.
|
|
@@ -213,3 +375,24 @@ dependency.
|
|
|
213
375
|
- [`anbaric-cli`](https://npmjs.com/package/anbaric-cli) — the platform CLI.
|
|
214
376
|
- [`anbaric-hosting`](https://npmjs.com/package/anbaric-hosting) — run a
|
|
215
377
|
platform yourself.
|
|
378
|
+
|
|
379
|
+
Applications should not set the `ANBARIC_*` factory variables themselves; the
|
|
380
|
+
platform sets them when the application is deployed, so the same code runs
|
|
381
|
+
in-memory locally and platform-backed once deployed.
|
|
382
|
+
|
|
383
|
+
## Run your own platform
|
|
384
|
+
|
|
385
|
+
`npm install -g anbaric-hosting` provides the full hosting service - API,
|
|
386
|
+
dispatcher, build layers and proxy - bootable with the `anbaric-hosting`
|
|
387
|
+
command and configured entirely through `ANBARIC_*` environment variables.
|
|
388
|
+
|
|
389
|
+
## Run a platform locally
|
|
390
|
+
|
|
391
|
+
```bash
|
|
392
|
+
cd gitops/local
|
|
393
|
+
tofu init && tofu apply # Docker Desktop: Postgres + the platform on :8787
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
See `gitops/README.md` for staging/prod and for enabling Auth0 login.
|
|
397
|
+
`sample-apps/crm` is a complete worked example — deploy it by running
|
|
398
|
+
`anbaric app deploy` from inside `sample-apps/crm`.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "anbaric",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.20.1",
|
|
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",
|
|
@@ -9,10 +9,14 @@
|
|
|
9
9
|
"files": [
|
|
10
10
|
"src"
|
|
11
11
|
],
|
|
12
|
+
"scripts": {
|
|
13
|
+
"prepack": "rm -f README.md && cp ../README.md README.md",
|
|
14
|
+
"postpack": "rm -f README.md && ln -s ../README.md README.md"
|
|
15
|
+
},
|
|
12
16
|
"dependencies": {
|
|
13
|
-
"anbaric-impl-cloud": "^1.
|
|
14
|
-
"anbaric-data-store": "^1.
|
|
15
|
-
"anbaric-state-machine": "^1.
|
|
16
|
-
"anbaric-tsapi": "^1.
|
|
17
|
+
"anbaric-impl-cloud": "^1.20.1",
|
|
18
|
+
"anbaric-data-store": "^1.20.1",
|
|
19
|
+
"anbaric-state-machine": "^1.20.1",
|
|
20
|
+
"anbaric-tsapi": "^1.20.1"
|
|
17
21
|
}
|
|
18
22
|
}
|