ctx.taskKey, so the API you call can drop a duplicate.Automations, as data.
Tide is Nisc’s automation engine. An automation is a JSON document, called a reflex: what triggers it, and which function to run. Your app loads reflexes while it runs, so adding or changing one needs no deploy.
pnpm add @niscorp/tide @niscorp/prism @niscorp/strata zod · 0.1.7 · Apache-2.0
{
"id": "sales.report",
"intent": "Email the sales report every day at 18:00.",
"on": {
"clock": {
"every": "day",
"at": "18:00",
"tz": "Europe/Vienna"
}
},
"effect": {
"name": "mail.send",
"input": {
"to": "team",
"report": "sales",
"day": {
"$ref": "$.occurrence.key"
}
}
}
}The time
What ran
The clock here is simulated. Tide takes the time as an argument, so this page passes half an hour in two seconds.
First steps →What can trigger a reflex.
A reflex has one trigger, in its on.
It runs a function in your app.
A reflex names its effect, and Tide calls the function your app registered under that name. So a reflex can only do what your app offers. The input is filled in from what triggered it: the row that was written, the webhook’s body, the day of the schedule.
import { createTide, createMemoryStore } from '@niscorp/tide';
import { evaluate } from '@niscorp/prism';
const tide = createTide({
store: createMemoryStore(),
transform: evaluate,
effects: {
'mail.send': { writes: [], run: (input) => mailer.send(input) },
},
});
await tide.load(reflexes);Give a reflex a select, and the function runs once for each row a query returns: one reminder per member, one charge per subscription. Each row is its own task, and fails or is retried alone.
A failed run is retried.
If your function throws, Tide runs it again: after 1 minute, then 2, then 4.
Example: a mail server that was down at 12:00
Chain reflexes for more than one step.
A reflex runs one function. For a second step, add a second reflex whose trigger is the first one finishing.
Step 1
{
"id": "booking.confirm",
"intent": "Confirm a booking by email.",
"on": {
"fact": {
"entity": "bookings",
"op": "insert"
}
},
"effect": {
"name": "mail.send",
"input": {
"to": {
"$ref": "$.row.member"
}
}
}
}
Step 2
{
"id": "booking.board",
"intent": "Then update the board.",
"on": {
"fact": {
"run": "booking.confirm"
}
},
"effect": {
"name": "board.update"
}
}
Run on this page: a booking was inserted, and Tide called mail.send, then board.update.
Safe for a model to write.
Like every document in Nisc, a reflex can be written by a model. Tide ships the agent for it: it drafts a reflex from a sentence, and checks the draft before your app gets it.
Email the team the sales report every day at 6 pm.
What Tide’s agent did
{
"id": "sales.report",
"intent": "Email the sales report every day at 18:00.",
"effect": {
"name": "mail.send",
"input": {
"to": "team",
"report": "sales"
}
},
"on": {
"clock": {
"every": "day",
"at": "18:00",
"tz": "Europe/Vienna"
}
}
}as when you save it, and resolve it to a Charter principal in actor: your functions then get that principal, and its permissions.preview shows what it would do.No model is called on this page. Both replies are written by hand, and Tide’s agent checked them.
On the server, Moss runs it.
facts configured, each row written through Vex is passed to Tide, so a write can trigger a reflex.What an engineer asks.
policy.retry changes it for one reflex.dedupeKey, and the second delivery is dropped.preview runs the selection and fills in each input. Your function is not called.fire triggers it now, even when it is disabled.advance({ now }), so a test sets the clock.ingest. In a Moss app Vex does that.load with the changed list while the app runs.