Eject
Leave MainSDK for one interface or all of them. mainsdk eject writes plain provider SDK code in place of MainSDK calls and keeps your tests passing.
Status Status is where the work is: Planned (in a milestone, not started), Building (tickets in progress), Preview (usable, may change), then the stability it ships at.
Stability The promise an interface ships with.
- Stable Follows semantic versioning; breaking changes need a new major, 12 months of overlap and a codemod.
- Beta Usable in production; may change before it becomes stable.
- Experimental May change or go away; needs an explicit opt-in in main.yaml.
Replace MainSDK calls for one interface, or all of them, with the provider's own SDK. mainsdk eject keeps your tests passing, writes a report of the guarantees you give up, runs offline and sends nothing anywhere. No lock-in, by design.
Before you start:
- A clean git working tree, so you can review and revert the change.
- Your test suite passing, so you can compare the count after eject.
1. See the plan
Eject always plans first. --dry-run lists every file it would change and changes none.
npx mainsdk eject email --dry-run$ npx mainsdk eject email --dry-run
eject email · driver resend
app/lib/receipts.ts email.send() → resend.emails.send()
app/lib/newsletter.ts email.marketing() → resend.emails.send()
tests/receipts.test.ts @mainsdk/testing fake → plain mock
main.yaml email.key secret:RESEND_API_KEY → process.env.RESEND_API_KEY
no files changed (dry run)2. Eject one interface
npx mainsdk eject emailBefore:
import { email } from "mainsdk";
export async function sendReceipt(to: string, orderId: string) {
await email.send({ to, template: "receipt", data: { orderId } });
}After, with the Resend driver:
import { Resend } from "resend";
const resend = new Resend(process.env.RESEND_API_KEY);
export async function sendReceipt(to: string, orderId: string) {
await resend.emails.send({
from: "orders@pinecart.dev",
to,
subject: "Your receipt",
html: renderReceipt({ orderId }),
});
}How the template is rendered (renderReceipt here) depends on each driver's recipe.
What eject changes:
- Every call to that interface becomes idiomatic code for the provider SDK named by the driver in
main.yaml. secret:references become plain environment variable reads.- Tests that used
@mainsdk/testingfakes are rewritten to the provider's test mode or a plain mock. They are never deleted. EJECT-REPORT.mdlists each guarantee that no longer holds.
A partial eject keeps every other interface working.
3. Verify it worked
Run your tests. They pass, with the same test count as before.
node --testThen read the report and decide how you replace each guarantee:
# Eject report: email (resend)
No longer enforced by MainSDK:
- Unsubscribe link on marketing email (was enforced by types)
- Suppression list checked before every send
- Idempotency keys on sends (retries may send twice)Eject everything
npx mainsdk eject --all --dry-run
npx mainsdk eject --allAfter the last interface, main.yaml, the gates and every mainsdk and @mainsdk/* dependency are removed from the project. Interfaces with a built-in driver, such as Cedar for policy or built-in webhooks, eject to the open-source library code they use.
Next
Was this page helpful?