Skip to content
Changelog

Report a bug

Write Safely

A write changes something outside the run: a Slack message, a Linear issue, or a row in an API. Networks fail, and runs can run again after a repair. Plan each write so that a second attempt is safe.

Each write needs an effectKey. Build it from values that stay the same for the same logical change:

TriggerGood keyBad key
An event`issue:${event.id}``issue:${Date.now()}`
A schedule`report:${event.scheduledAt}``report:${Math.random()}`
An item in a list`notify:${order.id}``notify:${index}`

Use one or more of these methods:

  1. Use the provider’s idempotency key. Many APIs accept an Idempotency-Key header. Send the effect key as its value.
  2. Read before you write. Look for an item from an earlier attempt. For example, find an issue with the event ID in its title.
  3. Use update instead of create. An update with the same values has the same result each time.
  4. Save progress in a checkpoint. Record which items are done, so the next run skips them.

A timeout or a lost connection does not mean that the write failed. The provider can have done it.

  • Do not send the write again at once.
  • Read the destination to find out what happened.
  • If you cannot find out, fail the run with a clear message. A person or the repair process can then check it.
const created = await ctx.write.call(createIssue, issueSchema, { effectKey: `issue:${event.id}` });
if (created.isErr()) {
if (created.error.code === "timeout" || created.error.code === "network") {
return err(new TypeApiError(`Issue for ${event.id} has an unknown result. Check Linear before you retry.`, "network"));
}
return err(created.error);
}

ctx.mutativeAction records a step in run history before and after it runs:

const created = await ctx.mutativeAction(
() => ctx.write.call(createIssue, issueSchema, { effectKey: `issue:${event.id}` }),
{ id: "create-issue", message: `Create a Linear issue for ${event.id}` },
(issue) => ({ message: `Created ${issue.id}`, data: { id: issue.id } }),
);
  • Run history shows what the code tried to do and what happened.
  • Check the provider response inside the action, so the log shows a real success.
  • Action logs do not retry, and they do not undo earlier steps.
  • If the after log fails to save, the error says “Do not repeat the action”. The write can be complete.

Type saves the checkpoint and the output only after the method succeeds. A write happens at the time that you call it. Thus:

  • A write that succeeds, followed by a failure later in the method, is not undone.
  • The next run starts from the old checkpoint, and can try the same write again.
  • After a failure, the repair’s verification run uses the same event again, with real access. See Reconcile before verification.

Put the most important write last, or make every write safe to repeat.

In fixtures, Type gives your code the responses that you supply. No real write happens. Fixtures prove how your code handles a response. They do not prove that the provider accepts the write.