anbaric 1.7.0 → 1.8.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 +174 -71
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -1,98 +1,201 @@
|
|
|
1
1
|
# anbaric
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
3
|
+
Anbaric is a Typescript framework for building stateful applications. An application is
|
|
4
|
+
expressed as one or more state machines: long-lived jobs move through named
|
|
5
|
+
states, driven by actions and transitions, with their data validated against a
|
|
6
|
+
schema and every change recorded in an audit trail. Alongside the state machine
|
|
7
|
+
the framework provides schema-validated document storage and secret storage.
|
|
8
|
+
|
|
9
|
+
Anbaric also provides a platform-as-a-service to easily deploy stateful apps built on Anbaric. The same application runs unchanged on
|
|
10
|
+
a developer's machine, on a server you operate, or on Anbaric Cloud, where a
|
|
11
|
+
single CLI command deploys it. Application code depends only on the interfaces
|
|
12
|
+
in this package; the framework selects in-memory or platform-backed
|
|
13
|
+
implementations from the environment, so no deployment detail appears in the
|
|
14
|
+
code.
|
|
15
|
+
|
|
16
|
+
This package (`anbaric`) is the umbrella install for writing an application. It
|
|
17
|
+
re-exports the app-facing surface of the underlying packages.
|
|
10
18
|
|
|
11
19
|
```bash
|
|
12
20
|
npm install anbaric
|
|
13
21
|
```
|
|
14
22
|
|
|
15
|
-
An Anbaric
|
|
16
|
-
`"type": "module"` in package.json
|
|
17
|
-
file, run with `tsx`.
|
|
18
|
-
|
|
23
|
+
An Anbaric application is a standard Node.js ESM program written in TypeScript:
|
|
24
|
+
set `"type": "module"` in `package.json`, point `main` at the TypeScript entry
|
|
25
|
+
file, and run it with `tsx`.
|
|
26
|
+
|
|
27
|
+
## State machines
|
|
28
|
+
|
|
29
|
+
A **`StateMachine`** defines a workflow. It is constructed with an identifier, a
|
|
30
|
+
list of states, the name of the start state, and the property schema for its
|
|
31
|
+
jobs.
|
|
32
|
+
|
|
33
|
+
- A **`State`** is a named step. It holds a list of **actions** to run while a
|
|
34
|
+
job sits in that state, and a list of **transitions** to the next state.
|
|
35
|
+
- An **`Action`** is a unit of work. Its `run` function receives the job and
|
|
36
|
+
returns a `Map` of property changes: `run(job) => Promise<Map<string, any>>`.
|
|
37
|
+
An action does not mutate the job directly; the returned properties are
|
|
38
|
+
validated and applied by the machine. Each action declares the **actor** that
|
|
39
|
+
performs it.
|
|
40
|
+
- A **`Transition`** names a target state and carries a predicate over the job.
|
|
41
|
+
When a job is processed, the first transition whose predicate holds moves the
|
|
42
|
+
job to that state.
|
|
43
|
+
- A **`Job`** is one instance moving through the machine. It carries an
|
|
44
|
+
identifier, its current `state`, a `Map` of `properties`, and provenance
|
|
45
|
+
(`workflowId`, `startedBy`, `startedAt`, `lastUpdated`). Jobs are immutable;
|
|
46
|
+
each change produces a new version.
|
|
47
|
+
- A **`PropertyDefinition`** describes one property a job may carry: whether it
|
|
48
|
+
is required and how its value is validated. Every property a job holds —
|
|
49
|
+
including properties set by actions — must have a definition, or the change is
|
|
50
|
+
rejected.
|
|
51
|
+
- An **`Actor`** identifies who performs an operation, for authorization and
|
|
52
|
+
auditing. The actor types are `Code`, `Human`, `Agent` and `System`. `Code`
|
|
53
|
+
actions run automatically as jobs are processed; a `Human` or `Agent` actor
|
|
54
|
+
represents work performed by a person or an autonomous agent.
|
|
55
|
+
|
|
56
|
+
A job progresses automatically as it is processed: matching actions run, their
|
|
57
|
+
properties are applied, and the first satisfied transition advances the state.
|
|
58
|
+
This repeats until the job reaches a state from which nothing more applies.
|
|
59
|
+
|
|
60
|
+
There are three ways to influence a job:
|
|
61
|
+
|
|
62
|
+
- `startJob(properties?, actor?)` creates a job in the start state.
|
|
63
|
+
- `updateJob(jobId, properties, actor)` applies an explicit property change.
|
|
64
|
+
- `executeAction(jobId, action)` runs a single action immediately.
|
|
65
|
+
- Actions subscribed to a state run automatically whenever a job in that state
|
|
66
|
+
is processed.
|
|
67
|
+
|
|
68
|
+
Every operation is validated against the schema and written to the audit trail,
|
|
69
|
+
which records the resource, the actor, the interactions (create, update, state
|
|
70
|
+
change, delete, read), and a description of what changed.
|
|
71
|
+
|
|
72
|
+
## Documents and secrets
|
|
73
|
+
|
|
74
|
+
For data that does not belong to a job, the framework provides two stores,
|
|
75
|
+
obtained from factories and used with an actor for auditing.
|
|
76
|
+
|
|
77
|
+
- **`JsonStore`** stores JSON documents in named collections, optionally
|
|
78
|
+
validated against a JSON Schema.
|
|
79
|
+
- **`SecretStore`** stores named secret strings, encrypted at rest, and never
|
|
80
|
+
records secret values in the audit trail.
|
|
19
81
|
|
|
20
|
-
|
|
82
|
+
```ts
|
|
83
|
+
import {Human, JsonStoreFactory, SecretStoreFactory} from "anbaric";
|
|
21
84
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
85
|
+
const actor = new Human("ada", "admin");
|
|
86
|
+
|
|
87
|
+
const customers = JsonStoreFactory.instance("customers", {
|
|
88
|
+
type: "object",
|
|
89
|
+
required: ["name"],
|
|
90
|
+
properties: { name: { type: "string" } },
|
|
91
|
+
});
|
|
92
|
+
await customers.create(actor, "ada", { name: "Ada" });
|
|
93
|
+
const record = await customers.retrieve("ada", actor);
|
|
94
|
+
|
|
95
|
+
const secrets = SecretStoreFactory.instance();
|
|
96
|
+
await secrets.create(actor, "api-key", "s3cr3t");
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
## Running and deploying
|
|
100
|
+
|
|
101
|
+
Persistence, queueing and consumers are supplied by environment-driven
|
|
102
|
+
factories, so the same application runs in several ways without code changes.
|
|
103
|
+
|
|
104
|
+
- **Local.** With no environment configured, every store, queue and consumer is
|
|
105
|
+
in-memory and jobs progress automatically. Running the program with
|
|
106
|
+
`npx tsx src/main.ts` is a complete local run, suitable for development and
|
|
107
|
+
testing.
|
|
108
|
+
- **Anbaric Cloud.** `anbaric deploy` packages the application and runs it on
|
|
109
|
+
the hosted platform. The platform injects the configuration that points the
|
|
110
|
+
factories at platform-backed persistence, queueing, documents and secrets;
|
|
111
|
+
application code is unchanged. Authentication, job inspection and updates are
|
|
112
|
+
available through the CLI.
|
|
113
|
+
- **Self-hosted.** The [`anbaric-hosting`](https://npmjs.com/package/anbaric-hosting)
|
|
114
|
+
package runs the platform — the API, dispatcher, build layer and app proxy —
|
|
115
|
+
on infrastructure you operate, backed by Postgres.
|
|
116
|
+
|
|
117
|
+
Applications should not set the `ANBARIC_*` factory variables themselves; the
|
|
118
|
+
platform sets them when the application is deployed.
|
|
119
|
+
|
|
120
|
+
## Example
|
|
29
121
|
|
|
30
122
|
```ts
|
|
31
123
|
import {Action, Code, PropertyDefinition, State, StateMachine, Transition} from "anbaric";
|
|
32
124
|
|
|
33
|
-
const
|
|
34
|
-
|
|
125
|
+
const name = new PropertyDefinition("name");
|
|
126
|
+
name.required = true;
|
|
127
|
+
|
|
35
128
|
const email = new PropertyDefinition("email");
|
|
36
129
|
email.required = true;
|
|
37
130
|
|
|
131
|
+
const welcomeSent = new PropertyDefinition("welcomeSent");
|
|
132
|
+
welcomeSent.validation = (value) => typeof value === "boolean";
|
|
133
|
+
|
|
38
134
|
const sendWelcome = new Action("Send welcome email", new Code("send-welcome"));
|
|
39
|
-
sendWelcome.run = async (job) =>
|
|
135
|
+
sendWelcome.run = async (job) => {
|
|
136
|
+
// ... send the email to job.properties.get("email") ...
|
|
137
|
+
return new Map([["welcomeSent", true]]);
|
|
138
|
+
};
|
|
40
139
|
|
|
41
|
-
const
|
|
140
|
+
const onboarding = new StateMachine(
|
|
42
141
|
"customer-onboarding",
|
|
43
142
|
[
|
|
44
|
-
new State("new", [sendWelcome],
|
|
45
|
-
|
|
143
|
+
new State("new", [sendWelcome], [
|
|
144
|
+
new Transition("active", (job) => job.properties.get("welcomeSent") === true),
|
|
145
|
+
]),
|
|
46
146
|
new State("active"),
|
|
47
147
|
],
|
|
48
148
|
"new",
|
|
49
|
-
[email,
|
|
149
|
+
[name, email, welcomeSent],
|
|
50
150
|
);
|
|
51
151
|
|
|
52
|
-
const job = await
|
|
53
|
-
```
|
|
54
|
-
|
|
55
|
-
Key rules an agent must respect:
|
|
56
|
-
|
|
57
|
-
- **Actions return properties, they do not mutate the job**: `run` returns a
|
|
58
|
-
`Promise<Map<string, any>>` of property changes. Every returned property
|
|
59
|
-
must exist in the machine's schema or the change is discarded.
|
|
60
|
-
- **Every property a job ever carries needs a `PropertyDefinition`** —
|
|
61
|
-
including ones actions set. Unknown properties make updates invalid.
|
|
62
|
-
- **Three ways to influence a job**: `updateJob(jobId, properties, actor)`
|
|
63
|
-
(explicit change, actor declared), `executeAction(jobId, action)` (run one
|
|
64
|
-
action now, actor embedded), or subscribing actions to states (automatic on
|
|
65
|
-
processing). All are schema-validated and audited.
|
|
66
|
-
- **Jobs carry history**: `startedAt`, `startedBy`, `lastUpdated`, and
|
|
67
|
-
`transitions` (`{from, to, actor}` for every state change).
|
|
68
|
-
|
|
69
|
-
## Local versus deployed
|
|
70
|
-
|
|
71
|
-
Persistence, queueing and consumers come from env-driven factories. With no
|
|
72
|
-
environment set, everything is in-memory and jobs progress automatically —
|
|
73
|
-
`npx tsx src/main.ts` is a complete local run. On an Anbaric platform the
|
|
74
|
-
same factories talk to the platform because it injects
|
|
75
|
-
`ANBARIC_JOB_PERSISTENCE_TYPE=cloud`, `ANBARIC_QUEUE_TYPE=cloud`,
|
|
76
|
-
`ANBARIC_JSON_STORE_TYPE=cloud`, `ANBARIC_SECRET_STORE_TYPE=cloud` and
|
|
77
|
-
`ANBARIC_CLOUD_URL`. Never set these by hand in app code.
|
|
78
|
-
|
|
79
|
-
Documents and secrets follow the same pattern:
|
|
80
|
-
|
|
81
|
-
```ts
|
|
82
|
-
import {JsonStoreFactory, SecretStoreFactory} from "anbaric";
|
|
83
|
-
|
|
84
|
-
const customers = JsonStoreFactory.instance("customers", {
|
|
85
|
-
type: "object",
|
|
86
|
-
required: ["name"],
|
|
87
|
-
properties: { name: { type: "string" } },
|
|
88
|
-
});
|
|
89
|
-
await customers.save("ada", { name: "Ada" });
|
|
90
|
-
|
|
91
|
-
const secrets = SecretStoreFactory.instance();
|
|
92
|
-
await secrets.save("api-key", "s3cr3t");
|
|
152
|
+
const job = await onboarding.startJob(new Map([["name", "Ada"], ["email", "ada@example.com"]]));
|
|
93
153
|
```
|
|
94
154
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
155
|
+
The job starts in `new`. When it is processed the `sendWelcome` action runs and
|
|
156
|
+
sets `welcomeSent`, and the transition then advances the job to `active`. Run
|
|
157
|
+
locally this happens automatically in memory; deployed, it happens on the
|
|
158
|
+
platform.
|
|
159
|
+
|
|
160
|
+
## Command-line interface
|
|
161
|
+
|
|
162
|
+
The [`anbaric`](https://npmjs.com/package/anbaric-cli) CLI authenticates a
|
|
163
|
+
terminal, deploys applications, and inspects and drives jobs on a platform. The
|
|
164
|
+
principal commands are:
|
|
165
|
+
|
|
166
|
+
| Command | Purpose |
|
|
167
|
+
| --- | --- |
|
|
168
|
+
| `anbaric login` / `anbaric logout` | authorize this terminal against a platform, or revoke it |
|
|
169
|
+
| `anbaric configure [dir]` | set an application's name and internal port |
|
|
170
|
+
| `anbaric deploy [dir]` | deploy an application and wait until it is live |
|
|
171
|
+
| `anbaric update [dir]` | deploy over a running application without prompting |
|
|
172
|
+
| `anbaric apps` | list deployed applications |
|
|
173
|
+
| `anbaric state-machines` | list registered state machines |
|
|
174
|
+
| `anbaric job list [state-machine-id]` | list jobs |
|
|
175
|
+
| `anbaric job watch <job-id>` | follow a job's state as it changes |
|
|
176
|
+
| `anbaric job set-state <job-id> <state>` | move a job to a state and re-queue it |
|
|
177
|
+
| `anbaric job update <job-id> <key=value ...>` | change job properties and re-queue |
|
|
178
|
+
|
|
179
|
+
Every command accepts flags (such as `--environment`, `--tenant`, `--name`,
|
|
180
|
+
`--port`, `--yes`) that supply the answers a prompt would otherwise ask for, so
|
|
181
|
+
the CLI can be run non-interactively in scripts and CI. See the
|
|
182
|
+
[`anbaric-cli`](https://npmjs.com/package/anbaric-cli) documentation for the
|
|
183
|
+
full command and flag reference.
|
|
184
|
+
|
|
185
|
+
## Packages
|
|
186
|
+
|
|
187
|
+
`anbaric` re-exports these packages; install them individually for a narrower
|
|
188
|
+
dependency.
|
|
189
|
+
|
|
190
|
+
- [`anbaric-tsapi`](https://npmjs.com/package/anbaric-tsapi) — the interfaces
|
|
191
|
+
and value classes: `Job`, `State`, `Action`, `Transition`, `Actor`,
|
|
192
|
+
`JobPersistence`, `JsonStore`, `SecretStore`, `Auditor`.
|
|
193
|
+
- [`anbaric-state-machine`](https://npmjs.com/package/anbaric-state-machine) —
|
|
194
|
+
the `StateMachine`, actors, and in-memory implementations.
|
|
195
|
+
- [`anbaric-data-store`](https://npmjs.com/package/anbaric-data-store) — the
|
|
196
|
+
document and secret stores.
|
|
197
|
+
- [`anbaric-impl-cloud`](https://npmjs.com/package/anbaric-impl-cloud) — the
|
|
198
|
+
clients used when an application is deployed to a platform.
|
|
199
|
+
- [`anbaric-cli`](https://npmjs.com/package/anbaric-cli) — the platform CLI.
|
|
200
|
+
- [`anbaric-hosting`](https://npmjs.com/package/anbaric-hosting) — run a
|
|
201
|
+
platform yourself.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "anbaric",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.8.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",
|
|
@@ -10,9 +10,9 @@
|
|
|
10
10
|
"src"
|
|
11
11
|
],
|
|
12
12
|
"dependencies": {
|
|
13
|
-
"anbaric-impl-cloud": "^1.
|
|
14
|
-
"anbaric-data-store": "^1.
|
|
15
|
-
"anbaric-state-machine": "^1.
|
|
16
|
-
"anbaric-tsapi": "^1.
|
|
13
|
+
"anbaric-impl-cloud": "^1.8.0",
|
|
14
|
+
"anbaric-data-store": "^1.8.0",
|
|
15
|
+
"anbaric-state-machine": "^1.8.0",
|
|
16
|
+
"anbaric-tsapi": "^1.8.0"
|
|
17
17
|
}
|
|
18
18
|
}
|