Skip to content

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
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:

FieldTypePurpose
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.
  • 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.

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.

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.