npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@frontera-sdk/functions

v1.50.19

Published

Author Frontera functions: manifest, triggers and the typed handler contract.

Readme

@frontera-sdk/automation

Author a Frontera automation: a named, triggered handler the platform runs on your behalf, deployed with frontera automation deploy.

bun add @frontera-sdk/automation
import { automation } from '@frontera-sdk/automation'

export default automation(
  {
    name: 'late-shipment-digest',
    trigger: { cron: '0 8 * * 1-5' },
    grants: ['blueprint:read'],
  },
  async (ctx) => {
    const late = await ctx.blueprint.query('Shipment', {
      where: { property: 'deliveryStatus', op: 'eq', value: 'late' },
    })
    await ctx.log(`${late.rows.length} late`, { hasMore: late.hasMore })
  },
)

A trigger is { cron } or { manual: true }, never both — the type refuses the ambiguous shape rather than leaving the runner to decide what it means. ctx.log never rejects: telemetry must not be able to fail a run.

Steps

ctx.step.run(name, fn) makes a piece of work durable. The platform stores the result, and if the run is retried the step is not run again — it returns what it returned the first time.

async (ctx) => {
  const overdue = await ctx.step.run('load-overdue', () =>
    ctx.blueprint.query('Invoice', {
      where: { property: 'status', op: 'eq', value: 'overdue' },
    }),
  )

  for (const [i, row] of overdue.rows.entries()) {
    await ctx.step.run(`notify:${i}`, () =>
      ctx.http.fetch({ url: 'https://hooks.example.com/notify', method: 'POST',
        body: JSON.stringify({ invoiceId: row.id }) }),
    )
  }

  return { notified: overdue.rows.length }
}

Three rules, and each one is a real failure mode rather than a style note:

  • Names are unique within a run. The platform memoizes by name, so reusing one would hand back the first step's result. It is refused instead, naming the collision — inside a loop, put the index in the name.
  • Results are JSON. A step's result is stored and replayed, so a Date comes back as a string. Return data, not objects with behaviour.
  • Code outside a step re-runs. After each step the handler restarts from the top, with completed steps returning their stored results. A ctx.http call that is not inside a step therefore fires once per step and spends its call budget every time.

Each step is recorded on the run, and every ctx call made inside one records the step it belongs to — so a run's trail is a tree of named work rather than a flat list.

The Console reads your deployed code and draws the whole shape: every step, both arms of every if with the condition on the edge, and a loop as a single node. A step named with a template shows as its pattern (items:${i}), because one loop in the code is one step in the picture however many times it runs. Each card names what the step reaches — Blueprint, Agent, HTTP — read from the ctx calls in its body, and clicking one shows that step's source.

automation() freezes the manifest it returns, including a copy of the trigger — a later mutation of the object you passed in cannot silently change the schedule the runner registers.

validateManifest is the same check the CLI runs before deploy, exported so you can run it in your own tests: names are kebab-case segments, and a cron expression is parsed rather than pattern-matched.

Testing a handler

createTestContext gives you a ctx to call your handler with, so a branch can be exercised without deploying and waiting for a day with the right data.

import { createTestContext } from '@frontera-sdk/automation'
import handler from './index'

const empty = createTestContext({ grants: ['blueprint:read'] })
expect(await handler.handler(empty.ctx)).toEqual({ handled: 0 })
expect(empty.steps).toEqual(['load', 'nothing-to-do'])

const busy = createTestContext({
  grants: ['blueprint:read', 'agent:ava:run'],
  blueprint: { Invoice: { rows: [{ id: 'INV-1' }], hasMore: false } },
  agents: { ava: () => ({ text: 'looks fine' }) },
})
await handler.handler(busy.ctx)
expect(busy.calls.map((c) => c.kind)).toContain('agent')

It refuses what the platform refuses, in the same words: a grant your manifest does not declare, and a step name used twice. An agent or an HTTP call you did not stub throws rather than answering — a fabricated 200 or an empty agent reply is a test that passes while asserting nothing. An object type you did not stub returns no rows, because that is a real answer and usually the branch worth testing.

What it does not simulate is resumption: in production your handler is re-entered after every step, and here it runs once, straight through.

License

Apache-2.0. See LICENSE.