Composition model
A Notewright instance is composed, not configured. You call defineApp once with
exactly one notes source and one follow-up sink. There are no environment
selectors like NOTES_PROVIDER or FOLLOWUP_PROVIDER; the providers are chosen
in code and the composition is type-checked.
flowchart TD
NOTES[Notes source<br/>extension] --> APP
SINK[Follow-up sink<br/>extension] --> APP
APP{{defineApp}} --> FETCH[Worker fetch handler]
APP --> DOS[Durable Objects<br/>FollowUpAgent · SpeakerContextAgent]
APP --> EXT[extensions<br/>plain data read by doctor]
class NOTES,SINK external
defineApp
Section titled “defineApp”import { defineApp } from "@notewright/core";import { granola } from "@notewright/granola-notes-provider";import { linear } from "@notewright/linear-follow-up-provider";
const app = defineApp({ notes: granola, followUp: linear });export default app;
export const { FollowUpAgent, SpeakerContextAgent } = app.durableObjects;export { GranolaSyncAgent } from "@notewright/granola-notes-provider";defineApp({ notes, followUp }) returns a plain object:
| Field | Type | Purpose |
|---|---|---|
fetch | (request, env) => Promise<Response> | The Worker handler; set as the default export. |
durableObjects | { FollowUpAgent, SpeakerContextAgent } | The core DO classes to re-export by their class_name. |
extensions | { notes, followUp } | The composed providers as plain data; read by notewright doctor. |
Why composition, not configuration
Section titled “Why composition, not configuration”- Type safety. A provider whose shape does not match the contract is a compile error. You cannot deploy an instance that references a provider it does not have.
- No dead provider code. Only the providers you import are bundled. There is no registry of optional providers shipped in core.
- Keys stay yours. Because each instance is its own deployment, provider credentials live only in your Cloudflare account.
Runtime fragments
Section titled “Runtime fragments”A provider can contribute runtime requirements (Durable Object bindings and DO
migrations) through a runtime fragment, and a notes source can declare a sync
binding so the /sync route forwards to its sync DO. These fragments are how the
Granola provider adds the GranolaSyncAgent DO and its migration. The canonical
binding and migration set lives in wrangler.jsonc; see
Durable Objects and the
Configuration reference.
The doctor contract
Section titled “The doctor contract”Because extensions is plain, Effect-free data, notewright doctor can import
your composed entry under Bun, read the active providers, and diff their declared
runtime requirements against wrangler.jsonc without executing any request. See
doctor preflight.