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

@drakkar.software/expo-conductor

v0.4.1

Published

Define tasks in an Expo app with rich execution policies (time, notifications, FCM/push, alarms, background) — priority, resource weight, recurrence and constraints. Native-first engine on Android (Kotlin) and iOS (Swift) with a Web implementation.

Readme

expo-conductor

Define tasks in an Expo app with rich execution policies and many triggers — priority, resource weight, recurrence and constraints — backed by a native-first engine on Android (Kotlin) and iOS (Swift), with a Web implementation.

See the full documentation and architecture overview at the repo root, and the shared behavior contract.

Quick start

import Conductor, { Priority, TaskResult } from '@drakkar.software/expo-conductor';

// Register JS handlers at MODULE scope (not inside a component) so they survive a headless
// relaunch — same rule as expo-task-manager's defineTask.
Conductor.defineTask('refresh', async () => TaskResult.NEW_DATA);

await Conductor.schedule({
  id: 'refresh',
  priority: Priority.HIGH,
  weight: 'moderate',
  triggers: [{ type: 'recurrence', recurrence: { kind: 'interval', everyMs: 900000 } }],
  policy: { constraints: { network: 'any' }, retry: { maxAttempts: 3, backoffMs: 30000 } },
});

Handlers, headless execution & push

  • JS handler: runs while the app is alive (foreground/background). Register it at module scope. It does not run after the app is terminated.
  • Native handler (handler.type: 'native', registered via ExpoConductorModule.registerHandler): runs headless, including after termination.
  • Rust handler (handler.type: 'rust'): dispatched over C ABI FFI to a Rust function registered via conductor_register(name, handler) in a conductor_app_init() call. Runs wherever the native layer runs (headless included). Enable with { "enableRust": true } in the config plugin options; see Rust handlers.
  • Push trigger: send a raw FCM v1 data-only message (Android) or a silent APNs push with conductorTask as a top-level peer of aps (iOS) — see the push message format. Use a native handler for push tasks that must run while terminated. Treat push data as untrusted.
  • Notifications: the notification trigger shows a notification on iOS and Android (Android channel auto-created). Call Conductor.requestPermissions() first.

Rust handlers

# Cargo.toml — your app's crate (depends on the lib's rlib, not cdylib/staticlib)
[package]
name = "my_app_rust"              # unique package name — no naming conflict with the dep
[lib]
name = "my_app_rust"              # artifact name: set rustLibName to this value in app.json
crate-type = ["rlib", "cdylib", "staticlib"]

[dependencies]
# default-features = false disables the no-op conductor_app_init symbol so your own
# conductor_app_init (below) is the only one compiled into the final .so.
conductor_ffi = { path = "path/to/packages/expo-conductor/rust", default-features = false }
// src/lib.rs
use conductor_ffi::{conductor_register, Handler};
use std::sync::Arc;

#[no_mangle]
pub extern "C" fn conductor_app_init() {
    conductor_register("my-task", Arc::new(|_task_id, _data| "success"));
}

On Android the lib calls conductor_app_init() automatically when the .so is loaded (RustTaskBridge.init). On iOS call ConductorRustBridge.appInit() from your AppDelegate (or from a helper class registered via a config plugin). The demo shows a complete example in apps/demo/rust/ and apps/demo/native-src/DemoHandlers.swift.

Enable in app.json with the matching rustLibName (must match your crate's [lib] name):

["@drakkar.software/expo-conductor", {
  "enableRust": true,
  "rustLibName": "my_app_rust"
}]

Build steps:

# Android (requires cargo-ndk + Android NDK):
cargo ndk -t arm64-v8a -t x86_64 -o android/app/src/main/jniLibs \
  --manifest-path path/to/your/rust/Cargo.toml build --release

# iOS (requires the ios targets + Xcode):
cargo build --manifest-path path/to/your/rust/Cargo.toml \
  --target aarch64-apple-ios --release
cargo build --manifest-path path/to/your/rust/Cargo.toml \
  --target aarch64-apple-ios-sim --release

Execution history, firedBy & reconciliation

import Conductor from '@drakkar.software/expo-conductor';
import { foldHistory, reconcile } from '@drakkar.software/expo-conductor';

// Raw lifecycle events persisted by the native layer (survives headless runs).
const events = await Conductor.getHistory();

// Fold events into paired records: { taskId, firedAt, firedBy, result, durationMs, ... }
// `firedBy` is 'manual' for explicit runNow() calls, or a TriggerType string when
// the scheduler fired it (e.g. 'alarm', 'recurrence', 'push').
const records = foldHistory(events);

// Compare expected occurrences against actual records.
const tasks = await Conductor.getTasks();
const { matched, missed, unexpected, aborted } = reconcile(tasks, records, {
  now: Date.now(),
  windowMs: 24 * 60 * 60_000,
});

missed = expected occurrences with no matching record (task never ran or ran outside tolerance). aborted = matched records whose result is 'failed' or 'error'. unexpected = records with no expected occurrence (e.g. a manually triggered one-shot).

Reconciliation is exact for time, recurrence, and alarm triggers. For background, push, and appState triggers it is advisory — the OS decides timing, not the scheduler.

v0.4.0 additions

  • firedBy on onTaskExecute, onTaskComplete, onTaskError event payloads and on TaskExecutionRecord — reports which trigger source caused the run ('manual' for runNow(), or a TriggerType string).
  • NotificationTrigger.recurring — re-arms the notification inSeconds after each delivery; clock drift doesn't accumulate.
  • AlarmTrigger.windowMs (Android only) — use AlarmManager.setWindow for battery-efficient batching instead of setExact.
  • ContinuedProcessingTrigger (type: 'userInitiatedBackground', iOS 26+ only) — BGContinuedProcessingTask; must originate from a direct user interaction.
  • Silent APNs → BGProcessingTask chain (iOS) — a silent push matching a push trigger with bgProcessing:true on a companion background trigger submits a full BGProcessingTask slot.
  • FCM Doze-bypass foreground service (Android) — policy.foreground: true + FCM push starts a foreground service instead of a WorkManager job. Requires enableForegroundService: true in the config plugin.

See the full documentation for triggers, policy/weight/priority, the config plugin options (enableFcm, enablePush, enableExactAlarms, useExactAlarmClock, backgroundTaskIdentifiers, enableRust, rustLibName, enableForegroundService), platform support matrix, and the device test guide.

Scripts

  • pnpm --filter expo-conductor test — Jest (engine + orchestration + API)
  • pnpm --filter expo-conductor typecheck
  • pnpm --filter expo-conductor build
  • pnpm test:kotlin / pnpm test:swift — native engine tests against the shared fixtures
  • pnpm test:rust — Rust glue crate unit tests (host, no NDK needed)
  • pnpm test:rust:demo — demo Rust crate (5 archetype handlers)