API Reference
Every reusable framework capability is exposed through one import — the facade at @/core/facade.ts — as a namespace object. You never reach into core internals; if it is not on this page, it is not part of the public API.
import {
win, // desktop window lifecycle
chrome, // tray, menus, dialogs, desktop chrome setup
pickers, // native open / save / folder dialogs
db, // Drizzle connection + query proxy (init / close)
paginate, // nexgen-style pagination (query / table / model)
sql, // tagged-template SQL for where filters
queue, // bounded in-process async queue
cron, // named cron schedules (croner)
chromium, // Chromium binary + bundled-extensions resolution
pass, // bcrypt hashing
validate, // Zod schemas + runner
files, // operate on any path the user picks
storage, // the app's own private/tmp disks
} from "@/core/facade.ts";Function reference
Each facade member below has its own page — every export linked here is a documented part of the public API.
| Function | Purpose | Guide |
|---|---|---|
win | Desktop window lifecycle: create / open / get / count | Windows |
chrome | Tray, application & context menus, native dialogs, chrome setup | Windows |
pickers | Native open / save / folder dialogs | Windows |
db | Drizzle connection + query proxy (init / close) | Database |
paginate | nexgen-style PaginatedResult envelope, shared by the three paginators below | Database |
paginateQuery | Generic paginator over custom total() / data(limit, offset) callbacks | Database |
paginateTable | Paginate a whole schema table with where / orderBy | Database |
paginateModel | Paginate relational queries (db.query.<table>) with eager loading | Database |
queue | Bounded in-process async queue with create({ concurrency }) | Queue |
cron | Named cron schedules with schedule / stop | Scheduler |
chromium | Chromium binary + bundled extension resolution for Playwright | Playwright |
pass | bcrypt hash / verify | Password |
validate | Zod schemas + run with a 422-shaped failure | Validation |
files | Open / save / upload / download / remove on any user-picked path | Storage |
storage | The app's own private / tmp disks | Storage |
Which one do I use when?
- Windows & desktop —
windrives your windows;chromeadds the menu/tray/dialogs and one-shot chrome setup;pickersgets real OS open/save/folder dialogs. - Data —
dbholds the Drizzle client;paginateis the shared envelope, and you pick which flavor fits:paginateQueryfor fully custom queries,paginateTablefor a whole table,paginateModelfor relational eager-loaded reads.sqlwriteswherefilters. - Background work —
queuethrottles async jobs;cronruns named schedules and stops them all on shutdown. - Security & input —
passhashes passwords;validateruns schemas and throws a structured 422-shaped failure. - Files —
filestouches the user's real file system anywhere;storagekeeps the app's own state inprivate/tmpdisks. - Automation —
chromiumlocates the bundled browser binary and unpacked extensions for Playwright.
Common recipes — combining namespaces
Real features rarely use one namespace. Here is the pattern for a very common case, so you can see how the pieces fit.
Email a list stored in SQLite
Deskapp has no built-in mailer — add a third-party SMTP client and drive it with the facade: db reads the list, queue throttles the sends so the SMTP server is never flooded, cron schedules the blast, and db records the result per row.
Add the SMTP client (example uses nodemailer):
deno add npm:nodemailerA tiny mailer service around it:
// src/core/utils/mailer.ts
import nodemailer from "npm:nodemailer";
import { validate } from "@/core/facade.ts";
const opts = validate.run(validate.z.object({ host: validate.z.string(), port: validate.z.coerce.number().int() }), {
host: Deno.env.get("SMTP_HOST"),
port: Deno.env.get("SMTP_PORT"),
});
const transport = nodemailer.createTransport({
host: opts.host,
port: opts.port,
secure: true,
auth: { user: Deno.env.get("SMTP_USER"), pass: Deno.env.get("SMTP_PASS") },
});
export async function sendEmail(to: string, subject: string, body: string) {
return transport.sendMail({ from: Deno.env.get("SMTP_FROM") ?? "noreply@example.com", to, subject, text: body });
}The controller that sends to the whole list:
import { db, queue, sql } from "@/core/facade.ts";
import * as schema from "@/database/schema.ts";
import { sendEmail } from "@/core/utils/mailer.ts";
export const blast = async (input: { subject: string; body: string }) => {
// 1) read the mailing list from SQLite
const subscribers = await db.query.users.findMany({
where: (t, { eq }) => eq(t.newsletterOptIn, true),
});
// 2) throttle: at most 3 SMTP calls in flight at once
const mailer = queue.create<string>({ concurrency: 3 });
const results = await Promise.allSettled(
subscribers.map((user) =>
mailer.add(async () => {
await sendEmail(user.email, input.subject, input.body);
// 3) record who actually got it
await db.update(schema.users)
.set({ lastNewsletterAt: new Date().toISOString() })
.where(sql`id = ${user.id}`);
return user.email;
}),
),
);
return {
ok: true,
total: subscribers.length,
sent: results.filter((r) => r.status === "fulfilled").length,
};
};Optional: send it on a schedule instead of on demand.
cron.schedule("newsletter", "0 9 * * 1", () => void blast({ subject: "Weekly digest", body: "..." }));Which pieces did you just use? db (read + write), queue (throttle + per-caller error isolation), sql (filter/update), validate (safe env config), cron (schedule). That is the whole point of the facade — compose instead of re-implement.
Rules of the facade
- Everything is a namespace object — import
win, not a bareopenWindow. Grouping makes the code searchable and collisions impossible. - Desktop-only items degrade gracefully —
win.createWindow,chrome.createTrayetc. returnundefined(not throw) when not running underdeno desktop. - Nothing here needs network access — all utilities are in-process.
- Exports stay truthful — the facade only re-exports what is implemented. If a signature is missing here, it does not exist yet.
