Skip to content

Orchestrator boundaries

Everything between Nwire and the outside world. Five first-class primitives so Studio sees every boundary as a sticky on the EventStorming canvas.

defineExternalCall — HTTP / RPC outbound

ts
const chargeStripe = defineExternalCall({
  name: "stripe.create-payment-intent",
  target: { provider: "stripe", endpoint: "/v1/payment_intents" },
  request: ChargePaymentIntentSchema,
  response: PaymentIntentSchema,
  idempotencyKey: (req) => `charge-${req.orderId}-${req.amount}`,
  slo: { p95LatencyMs: 600, successRate: 0.995 },
  retry: { max: 3, backoff: "exponential" },
});

// Bind a transport at boot (HTTP client / Stripe SDK / test mock):
app.runtime.registerExternalCallExecutor(chargeStripe, async (req, meta) => {
  return await stripe.paymentIntents.create({
    amount: req.amount,
    idempotency_key: meta.idempotencyKey,
  });
});

// Call it from a handler:
defineHandler(chargeOrder, async (input, ctx) => {
  const intent = await ctx.externalCall(chargeStripe, { amount: input.amount, orderId: input.id });
  return OrderCharged({ orderId: input.id, intentId: intent.id });
});

Runtime: retries automatically with backoff, threads idempotency key, emits external.call.started/.completed/.failed telemetry per attempt.

defineInboundWebhook — HTTP callbacks IN

ts
const stripeWebhook = defineInboundWebhook({
  name: "stripe.payment-events",
  source: "stripe",
  path: "/webhooks/stripe",
  verifySignature: stripeSignatureVerifier,
  dedupe: { window: "24h", keyFrom: (msg) => msg.id },
  schema: StripeWebhookSchema,
  discriminator: "type",
  routes: {
    "payment_intent.succeeded": confirmPayment,
    "payment_intent.payment_failed": markPaymentFailed,
  },
});

The HTTP wire mounts the path, verifies the signature, dedups via inbox, discriminates on the body field, dispatches the right action.

defineOutbox — transactional outbox

ts
const ordersOutbox = defineOutbox({
  name: "orders",
  publishes: [OrderPlacedEvent, OrderShippedEvent],
  flushIntervalMs: 200,
  maxBatch: 100,
});

Events written to the outbox in the same DB transaction as state. A flusher drains to the cross-service bus. At-least-once delivery; readers handle dedup via inbox.

defineInbox — at-least-once dedup

ts
const ordersInbox = defineInbox({
  name: "orders",
  window: "7d",
  on: [confirmPayment, markPaymentFailed],
});

Tracks message ids seen in the last 7 days; second arrival short-circuits.

defineCron — scheduled actions

ts
const dailyBillingSummary = defineCron({
  name: "billing.daily-summary",
  schedule: "0 2 * * *",            // every day at 02:00 UTC
  dispatches: generateBillingSummary,
  timezone: "UTC",
});

A scheduler dispatches the bound action at each fire time; runtime emits cron.fired with lateByMs.

Why first-class

Generic observability sees each external call as another HTTP span. Nwire's declarative primitives let:

  • Studio render each boundary as a distinct sticky on the canvas
  • Idempotency be enforced consistently across providers
  • SLOs be declared per provider/endpoint and scored against observed reality
  • Retries be applied by the runtime, not buried in handlers
  • Webhooks be verified + deduped + routed without route boilerplate

See also

MIT licensed.