@jsm-mit/rabbit-motoko-package
v0.12.0
Published
Wrapper TypeScript package for Rabbit Motoko Canister.
Readme
Rabbit Motoko Package
A TypeScript library for interacting with the rabbit-motoko task-queue canister on the
Internet Computer. The wrapper is the only sanctioned way to talk to the canister: it
hides HttpAgent/idlFactory wiring, unwraps the canister's Framework.Result<T> into
"resolves with T or throws a known Error", and converts candid opt/variant/
Principal shapes into idiomatic TypeScript.
Installation
npm install @jsm-mit/rabbit-motoko-packageUsage
Initialization
import { TasksActor, AdminActor, ChannelsActor } from '@jsm-mit/rabbit-motoko-package';
const canisterId = 'your-canister-id';
const tasksActor = new TasksActor(canisterId, identity); // identity optional
const adminActor = new AdminActor(canisterId, identity);
const channelsActor = new ChannelsActor(canisterId, identity);In a browser bundle import from @jsm-mit/rabbit-motoko-package/browser: the same
API without RabbitTaskWorker, whose pigeon dependency pulls in Node-only modules.
Permissions model
A fresh canister has no admins — the first caller of
adminActor.registerAsAdminAsyncUnsafe() claims the role. Task permissions are
per channel: an admin creates a channel (createChannelAsyncUnsafe) and opens
its 10-minute signup window (openChannelForSignupAsyncUnsafe); during the
window any principal enrolls itself via signUpForChannelAsyncUnsafe, choosing
canAddTasks (create tasks) and/or canWorkOnTasks (poll + claim + complete).
Admins can also grant/revoke levels directly and audit members
(getChannelMembersAsyncUnsafe).
Adding a task
const taskId = await tasksActor.addTaskAsyncUnsafe({
channel: 'my-channel',
commonId: 'operation-42',
payload: 'task payload data',
parentIds: [12n], // optional — task waits until every parent completes
});Polling, claiming and completing
const availableIds = await tasksActor.getAvailableTaskIdsAsyncUnsafe('my-channel');
const task = await tasksActor.claimTaskAsyncUnsafe({
id: availableIds[0],
timeoutNanos: BigInt(5 * 60 * 1_000_000_000),
});
// Only the principal that claimed the task may complete it
await tasksActor.completeTaskAsyncUnsafe({
id: task.id,
message: 'task completed successfully',
});Background worker
import { RabbitTaskWorker } from '@jsm-mit/rabbit-motoko-package';
const worker = new RabbitTaskWorker('my-channel', 15_000, tasksActor);
worker.tasks$.subscribe(task => { /* process claimed TaskView */ });
worker.run();Error model
Every ...AsyncUnsafe method throws one of exactly three errors — switch on
error.message, details are always on error.cause:
CanisterError— the canister returned a business#err;cause = { errorKey, errorMessage, logs }(e.g.errorKey: "NotAuthorized","NotFound","InvalidData","ForcedUpdate").CriticalCanisterError— transport failure or a canister trap;cause = { logs, rawError }.CallRefusedAtInspectionStage— the canister'sinspectgate refused the call before execution (oversized payload or anonymous caller).
...AsyncSafe variants never throw and resolve with a documented fallback value.
API
TasksActor— task-queue domain:addTaskAsyncUnsafe,getAvailableTaskIdsAsyncUnsafe,claimTaskAsyncUnsafe,completeTaskAsyncUnsafe,getTasksAsyncUnsafe,getTasksLast24hAsyncUnsafe,getTasksByCommonIdAsyncUnsafe,getTasksByChannelAndCommonIdAsyncUnsafe,getTaskAsyncUnsafe,getTaskAsyncSafe.AdminActor— admin allowlist and diagnostics:registerAsAdminAsyncUnsafe,addAdminAsyncUnsafe,removeAdminAsyncUnsafe,getAdminsAsyncUnsafe,setLoggingEnabledAsyncUnsafe,whoAmIAsyncUnsafe,getLogsAsyncUnsafe,clearLogsAsyncUnsafe.ChannelsActor— channel registry and permissions:createChannelAsyncUnsafe,openChannelForSignupAsyncUnsafe,signUpForChannelAsyncUnsafe,grantChannelPermissionsAsyncUnsafe,revokeChannelPermissionsAsyncUnsafe,getChannelMembersAsyncUnsafe,getChannelsAsyncUnsafe.RabbitTaskWorker— interval-based polling worker emitting claimed tasks ontasks$.- Task data is returned as
TaskView(opts flattened to optionals, principals as text, status as"Available" | "Claimed" | "Completed" | "Expired"); raw candid types remain available from.and./declarations/*exports.
Development
See CLAUDE.md for the canister-change workflow (declarations sync, wrapper updates)
and the integration-test policy. The scripts below deploy to mainnet and spend cycles —
human-run only; Claude never runs them.
npm run test-suite/npm run test-access-restrictions/npm run test-smoke— always onshort-living, the crafting table's short-lived test canister:scripts/run-tests.shreinstalls the current../rabbit-motokobuild there (npm run craft:deploy -- short-living --lease 3in that repo, no prompt), then runs the entry point withCANISTER_IDset to theshort-livingid from~/motoko-crafting-table/canister_ids.json. No.env: every identity is generated per run. A longer lease:npm run test-suite -- --lease 10.npm run redeploy -- [slot] [reinstall|upgrade] [--lease <min>] [--force]— deploys without tests throughcraft:deploy/craft:upgradein../rabbit-motoko. Defaults:short-living,reinstall. No.env, no prompt.
Requirements
- Node.js 18+
- TypeScript 5+
License
ISC
Author
@jsm-mit
Publishing
Bump the version in package.json by hand, then npm run publish-public.
scripts/publish-public.sh refuses unless this repo is on master equal to
origin/master with nothing uncommitted but the bump, the version is new on the
registry, and ../rabbit-motoko is on a clean master equal to its origin/master.
It then reinstalls that master into short-living (npm run craft:deploy -- short-living --lease 2)
and refuses unless dfx canister metadata <short-living id> candid:service equals the committed
declarations/rabbit-motoko-backend/rabbit-motoko-backend.did (trailing whitespace
ignored) and the smoke test (tests/test-smoke.ts: setup, then one write and one read per
domain) passes against that same deploy. Next scripts/stamp-canister-commit.sh checks the canister repo again, rebuilds
the canister, regenerates the declarations and refuses if that changes what is committed
here; only then it writes rabbitMotoko.{repo, repoUrl, commit} into package.json.
Last: build, publish, and commit and push Version bump to <version> (when nothing is
left to commit, that is not a failure). A failed publish takes the stamp out.
