Skip to content

Nodo: action ​

Una scrittura per record a metà grafo, con un record in uscita su cui il grafo prosegue: un upload che restituisce un id, un aggiornamento che restituisce un esito, più passi in ordine sullo stesso record (tagga → notifica → sposta).

0/5
noeva — external noevaworkspace Noeva mail — source mailIMAP carica — action caricanoevaUploadAction sposta — action spostaimapSpostaAction registro — sink registroPostgreSQL falliti — dead-letter fallitidead-letter
Metodo.action(from, id, action, { deadLetter?, fromPort?, outputSchema?, purpose?, recordReport?, writeReport? })
Ingressi1
Uscite≥1 (fan-out libero)
Dead-lettersì, per record
CodiceAction<I, O> e actionTransform in src/core/action.ts

Quando usarlo ​

Se la scrittura…Nodo
è l'ultima cosa che succede al recordsink
ha un risultato che serve a valleaction
è uno di più passi in ordine sullo stesso recordpiù action in catena
è in realtà una letturalookup

Il contratto ​

ts
interface Action<I, O> {
  readonly name: string;
  readonly descriptor: ConnectorDescriptor; // obbligatorio: un'action fa IO per definizione
  run(record: I, ctx: RunContext): Promise<O>;
}

Esempio ​

ts
const carica: Action<Mail, MailCaricata> = {
  name: "carica-su-noeva",
  descriptor: {
    kind: "noeva-upload",
    details: [{ label: "cartella", value: cartellaId }],
  },
  run: async (mail, ctx) => ({
    ...mail,
    fileId: await client.upload(mail.eml, ctx.signal),
  }),
};

graph("posta-in-arrivo")
  .source("mail", imapSource(/* … */))
  .action("mail", "carica", carica, {
    deadLetter: uploadFalliti,
    outputSchema: mailCaricataSchema,
  })
  .action("carica", "sposta", imapSpostaAction(/* … */)) // eseguita solo se l'upload è riuscito
  .sink("sposta", registro)
  .build();

Action pronte ​

ActionCosa faScheda
noevaUploadActionCarica un file su Noeva con elaborazione AI, fileId in uscitanoeva
imapFlagAction, imapSpostaActionFlag e spostamento di un messaggio (idempotente via Message-ID)imap
smtpActionInvia una mail e prosegue con l'esitosmtp
mailActionsmtpAction o sendgridAction, scelta dalla configurazionesendgrid
sendgridActionInvia una mail via API SendGrid e prosegue con l'esitosendgrid
httpActionScrive su un'API REST, dichiarativa come il lookuphttp
jiraCreateIssue, jiraUpdateIssue, jiraTransitionCrea, aggiorna o porta in uno stato una issue Jira, con il testo in markdownjira
jiraUpsertIssueCrea o aggiorna una issue Jira per ID esterno, senza duplicatijira
jiraAddComment, jiraAddWorklogAggiunge un commento o un worklog a una issue, senza doppioni fra le runjira
jiraLinkIssuesCollega due issue Jira con un tipo di link, senza duplicarlojira
processoEsternoActionLancia un programma sui file del record: file e riepilogo JSONprocess

Comportamento ​

  • Sequenziale: un record alla volta, in ordine. Più action in catena eseguono i loro passi uno dopo l'altro per ogni record.

  • Idempotenza a carico dell'azione: un retry della run la ripete. Un'azione che non lo tollera si difende da sé (una chiave naturale, un controllo prima di scrivere) e lo dichiara nel JSDoc. Un invio email non è idempotente: va messo dopo il commit, e con una chiave per un Message-ID deterministico.

  • Niente retry sul nodo: ripetere una scrittura non è senza effetti. Un'action che sa di essere ripetibile può dichiararlo da sé — httpAction lo fa con idempotente, e senza quella dichiarazione build() rifiuta la policy. Le action Jira lo sanno da sole: si ritentano quelle idempotenti, e una retry su jiraCreateIssue è un ConfigError. jiraUpsertIssue ritenta la ricerca e l'aggiornamento, mai la creazione.

  • In lavorazione, nei contatori dal vivo: il record in volo. Vale uno mentre l'azione non ha risposto e zero appena il risultato esce; essendo sequenziale, non supera mai uno. Fermo a uno vuol dire che il sistema esterno sta rispondendo lento.

Errori ​

CasoEsito
l'azione lancia un errore qualunque (un fetch fallito)diventa ActionError, isolabile come ogni EtlError
l'azione lancia un EtlErrorpassa così com'è
con deadLetteril record esce come DeadLetter, lo stream continua
sotto un try-catchramo catch
altrimentifail-fast

Nel DAG e nel report ​

Il nodo mostra il descriptor dell'azione, senza segreti. outputSchema dà le colonne del risultato, senza validarlo: se il risultato viene da un sistema esterno lo valida l'azione, o un validate() a valle.

Di default il report dice solo se il nodo è riuscito. Per sapere che cosa ha scritto, e per quale record, c'è recordReport: una riga per record riuscito, che l'app mostra come tabella (righe per record).

Noeva è un marchio registrato di 4D S.R.L.