[{"data":1,"prerenderedAt":595},["ShallowReactive",2],{"fougere:post.list:{}":3,"tutorial-steps":6,"tutorial:\u002Ftutorial\u002F8-split":43},{"items":4,"hasMore":5},[],false,[7,11,15,19,23,27,31,35,39],{"path":8,"step":9,"name":10},"\u002Ftutorial\u002F0-start",0,"Start",{"path":12,"step":13,"name":14},"\u002Ftutorial\u002F1-an-entity",1,"An entity",{"path":16,"step":17,"name":18},"\u002Ftutorial\u002F2-a-source",2,"A source",{"path":20,"step":21,"name":22},"\u002Ftutorial\u002F3-a-rule",3,"A rule",{"path":24,"step":25,"name":26},"\u002Ftutorial\u002F4-a-port",4,"A port",{"path":28,"step":29,"name":30},"\u002Ftutorial\u002F5-a-link",5,"A link",{"path":32,"step":33,"name":34},"\u002Ftutorial\u002F6-a-fact",6,"A fact",{"path":36,"step":37,"name":38},"\u002Ftutorial\u002F7-an-app",7,"An app",{"path":40,"step":41,"name":42},"\u002Ftutorial\u002F8-split",8,"Split",{"id":44,"title":45,"aside":46,"body":47,"code":53,"concept":541,"description":542,"extension":543,"fig":544,"files":545,"grows":559,"heading":574,"install":46,"lede":542,"links":575,"meta":582,"name":42,"navigation":344,"path":40,"seo":583,"slug":584,"stem":585,"step":41,"tryIt":586,"zero":593,"__hash__":594},"tutorial\u002Ftutorial\u002F8-split.md","Mail in its own process",null,{"type":48,"value":49,"toc":50},"minimark",[],{"title":51,"searchDepth":17,"depth":17,"links":52},"",[],[54,77,88,143,175,209,229,240,318,362,372,393,470],{"path":55,"state":51,"lines":56},"fronds\u002Fblog\u002Fentities\u002FPost.ts",[57,60,62,65,68,71,74],{"text":58,"added":5,"keys":59},"import { entity, oneOf, primary, readOnly, text } from '@fougere\u002Fschema';",[],{"text":51,"added":5,"keys":61},[],{"text":63,"added":5,"keys":64},"export default class Post extends entity({",[],{"text":66,"added":5,"keys":67},"  id: primary(),",[],{"text":69,"added":5,"keys":70},"  title: text({ min: 1, max: 160 }),",[],{"text":72,"added":5,"keys":73},"  status: readOnly(oneOf('draft', 'published', { default: 'draft' })),",[],{"text":75,"added":5,"keys":76},"}) {}",[],{"path":78,"state":51,"lines":79},"fronds\u002Fblog\u002Fentities\u002FPostPublished.ts",[80,83,85],{"text":81,"added":5,"keys":82},"import Post from '.\u002FPost.js';",[],{"text":51,"added":5,"keys":84},[],{"text":86,"added":5,"keys":87},"export default class PostPublished extends Post.pick('id', 'title') {}",[],{"path":89,"state":51,"lines":90},"fronds\u002Fblog\u002Fhandlers\u002FPostHandler.ts",[91,94,97,100,102,105,108,111,114,116,119,122,125,128,130,133,135,138,140],{"text":92,"added":5,"keys":93},"import { ErrorCode, FougereError, Repository, type Emit } from '@fougere\u002Fcore';",[],{"text":95,"added":5,"keys":96},"import Post from '..\u002Fentities\u002FPost.js';",[],{"text":98,"added":5,"keys":99},"import type PostPublished from '..\u002Fentities\u002FPostPublished.js';",[],{"text":51,"added":5,"keys":101},[],{"text":103,"added":5,"keys":104},"export default class PostHandler extends Repository\u003CPost> {",[],{"text":106,"added":5,"keys":107},"  constructor(private published: Emit\u003CPostPublished>) {",[],{"text":109,"added":5,"keys":110},"    super();",[],{"text":112,"added":5,"keys":113},"  }",[],{"text":51,"added":5,"keys":115},[],{"text":117,"added":5,"keys":118},"  async publish(id: Post['id']): Promise\u003CPost> {",[],{"text":120,"added":5,"keys":121},"    const post = await this.findById(id);",[],{"text":123,"added":5,"keys":124},"    if (!post) throw new FougereError({ code: ErrorCode.NOT_FOUND, message: 'No such post' });",[],{"text":126,"added":5,"keys":127},"    if (post.status === 'published') throw new FougereError({ code: ErrorCode.CONFLICT, message: 'Already published' });",[],{"text":51,"added":5,"keys":129},[],{"text":131,"added":5,"keys":132},"    await this.published({ id: post.id, title: post.title });",[],{"text":51,"added":5,"keys":134},[],{"text":136,"added":5,"keys":137},"    return this.update(id, { status: 'published' });",[],{"text":112,"added":5,"keys":139},[],{"text":141,"added":5,"keys":142},"}",[],{"path":144,"state":51,"lines":145},"fronds\u002Fmail\u002Fhandlers\u002FMailHandler.ts",[146,149,152,155,157,160,163,165,168,171,173],{"text":147,"added":5,"keys":148},"import type { Fact } from '@fougere\u002Fcore';",[],{"text":150,"added":5,"keys":151},"import type PostPublished from '@fronds\u002Fblog\u002Fentities\u002FPostPublished.js';",[],{"text":153,"added":5,"keys":154},"import Mailer from '..\u002Fservices\u002FMailer.js';",[],{"text":51,"added":5,"keys":156},[],{"text":158,"added":5,"keys":159},"export default class MailHandler {",[],{"text":161,"added":5,"keys":162},"  constructor(private mailer: Mailer) {}",[],{"text":51,"added":5,"keys":164},[],{"text":166,"added":5,"keys":167},"  async onPublished(post: Fact\u003CPostPublished>): Promise\u003Cvoid> {",[],{"text":169,"added":5,"keys":170},"    await this.mailer.send(`Published: ${post.title}`);",[],{"text":112,"added":5,"keys":172},[],{"text":141,"added":5,"keys":174},[],{"path":176,"state":51,"lines":177},"fronds\u002Fmail\u002Fservices\u002FAuditMailer.ts",[178,181,184,186,189,192,194,197,200,202,205,207],{"text":179,"added":5,"keys":180},"import type { Logger } from '@fougere\u002Fcore';",[],{"text":182,"added":5,"keys":183},"import type Mailer from '.\u002FMailer.js';",[],{"text":51,"added":5,"keys":185},[],{"text":187,"added":5,"keys":188},"export default class AuditMailer implements Partial\u003CMailer> {",[],{"text":190,"added":5,"keys":191},"  constructor(private inner: Mailer, private log: Logger) {}",[],{"text":51,"added":5,"keys":193},[],{"text":195,"added":5,"keys":196},"  async send(subject: string): Promise\u003Cvoid> {",[],{"text":198,"added":5,"keys":199},"    this.log.info('sending', subject);",[],{"text":51,"added":5,"keys":201},[],{"text":203,"added":5,"keys":204},"    return this.inner.send(subject);",[],{"text":112,"added":5,"keys":206},[],{"text":141,"added":5,"keys":208},[],{"path":210,"state":51,"lines":211},"fronds\u002Fmail\u002Fservices\u002FConsoleMailer.ts",[212,215,217,220,222,225,227],{"text":213,"added":5,"keys":214},"import Mailer from '.\u002FMailer.js';",[],{"text":51,"added":5,"keys":216},[],{"text":218,"added":5,"keys":219},"export default class ConsoleMailer extends Mailer {",[],{"text":195,"added":5,"keys":221},[],{"text":223,"added":5,"keys":224},"    console.log(`mail: ${subject}`);",[],{"text":112,"added":5,"keys":226},[],{"text":141,"added":5,"keys":228},[],{"path":230,"state":51,"lines":231},"fronds\u002Fmail\u002Fservices\u002FMailer.ts",[232,235,238],{"text":233,"added":5,"keys":234},"export default abstract class Mailer {",[],{"text":236,"added":5,"keys":237},"  abstract send(subject: string): Promise\u003Cvoid>;",[],{"text":141,"added":5,"keys":239},[],{"path":241,"state":51,"lines":242},"apps\u002Fnuxt\u002Fapp\u002Fpages\u002Fposts.vue",[243,246,249,252,254,257,260,263,266,268,271,274,277,280,283,286,289,292,294,297,300,303,306,309,312,315],{"text":244,"added":5,"keys":245},"\u003Cscript setup lang=\"ts\">",[],{"text":247,"added":5,"keys":248},"import { post } from '@fronds\u002Ffacade';",[],{"text":250,"added":5,"keys":251},"import Post from '@fronds\u002Fblog\u002Fentities\u002FPost.js';",[],{"text":51,"added":5,"keys":253},[],{"text":255,"added":5,"keys":256},"const { items } = await useQuery(post, 'list');",[],{"text":258,"added":5,"keys":259},"const { fields, values, errors, submit } = useFormFor(Post);",[],{"text":261,"added":5,"keys":262},"const publish = useCommand(post, 'publish');",[],{"text":264,"added":5,"keys":265},"\u003C\u002Fscript>",[],{"text":51,"added":5,"keys":267},[],{"text":269,"added":5,"keys":270},"\u003Ctemplate>",[],{"text":272,"added":5,"keys":273},"  \u003Cform novalidate @submit.prevent=\"submit\">",[],{"text":275,"added":5,"keys":276},"    \u003Ctemplate v-for=\"field in fields\" :key=\"field.name\">",[],{"text":278,"added":5,"keys":279},"      \u003Cinput v-model=\"values[field.name]\" v-bind=\"field.attrs\" :placeholder=\"field.label\" \u002F>",[],{"text":281,"added":5,"keys":282},"      \u003Csmall v-if=\"errors[field.name]\">{{ errors[field.name] }}\u003C\u002Fsmall>",[],{"text":284,"added":5,"keys":285},"    \u003C\u002Ftemplate>",[],{"text":287,"added":5,"keys":288},"    \u003Cbutton>Write a draft\u003C\u002Fbutton>",[],{"text":290,"added":5,"keys":291},"  \u003C\u002Fform>",[],{"text":51,"added":5,"keys":293},[],{"text":295,"added":5,"keys":296},"  \u003Cul>",[],{"text":298,"added":5,"keys":299},"    \u003Cli v-for=\"row in items\" :key=\"row.id\">",[],{"text":301,"added":5,"keys":302},"      {{ row.title }} — {{ row.status }}",[],{"text":304,"added":5,"keys":305},"      \u003Cbutton v-if=\"row.status === 'draft'\" @click=\"publish.execute({ params: { id: row.id } })\">Publish\u003C\u002Fbutton>",[],{"text":307,"added":5,"keys":308},"    \u003C\u002Fli>",[],{"text":310,"added":5,"keys":311},"  \u003C\u002Ful>",[],{"text":313,"added":5,"keys":314},"  \u003Cp v-if=\"publish.error.value\">{{ publish.error.value.message }}\u003C\u002Fp>",[],{"text":316,"added":5,"keys":317},"\u003C\u002Ftemplate>",[],{"path":319,"state":320,"lines":321},"fougere.config.ts","changed",[322,325,328,331,333,336,339,342,346,349,356,359],{"text":323,"added":5,"keys":324},"import '@fougere\u002Fadapter-rest';",[],{"text":326,"added":5,"keys":327},"import '@fougere\u002Fadapter-sql';",[],{"text":329,"added":5,"keys":330},"import { defineFougere } from '@fougere\u002Fcore';",[],{"text":51,"added":5,"keys":332},[],{"text":334,"added":5,"keys":335},"export default defineFougere({",[],{"text":337,"added":5,"keys":338},"  protocols: { rest: {} },",[],{"text":340,"added":5,"keys":341},"  sources: { main: { type: 'sqlite', path: '.data\u002Fblog.db' } },",[],{"text":343,"added":344,"keys":345},"  fronds: {",true,[],{"text":347,"added":344,"keys":348},"    '@fougere\u002Flog': {},",[],{"text":350,"added":344,"keys":351},"    mail: { remote: 'http:\u002F\u002F127.0.0.1:4100' },",[352,353,354,355],"a","b","c","d",{"text":357,"added":344,"keys":358},"  },",[],{"text":360,"added":5,"keys":361},"});",[],{"path":363,"state":51,"lines":364},"apps\u002Fnuxt\u002Fapp\u002Fapp.vue",[365,367,370],{"text":269,"added":5,"keys":366},[],{"text":368,"added":5,"keys":369},"  \u003CNuxtPage \u002F>",[],{"text":316,"added":5,"keys":371},[],{"path":373,"state":51,"lines":374},"apps\u002Fnuxt\u002Fnuxt.config.ts",[375,378,381,384,387,390],{"text":376,"added":5,"keys":377},"\u002F\u002F https:\u002F\u002Fnuxt.com\u002Fdocs\u002Fapi\u002Fconfiguration\u002Fnuxt-config",[],{"text":379,"added":5,"keys":380},"export default defineNuxtConfig({",[],{"text":382,"added":5,"keys":383},"  compatibilityDate: '2025-07-15',",[],{"text":385,"added":5,"keys":386},"  devtools: { enabled: true },",[],{"text":388,"added":5,"keys":389},"  modules: ['@fougere\u002Fnuxt'],",[],{"text":391,"added":5,"keys":392},"})",[],{"path":394,"state":51,"lines":395},"apps\u002Fnuxt\u002Fpackage.json",[396,399,402,405,408,411,414,417,420,423,426,428,431,434,437,440,443,446,449,452,455,458,460,463,466,468],{"text":397,"added":5,"keys":398},"{",[],{"text":400,"added":5,"keys":401},"  \"name\": \"nuxt\",",[],{"text":403,"added":5,"keys":404},"  \"type\": \"module\",",[],{"text":406,"added":5,"keys":407},"  \"private\": true,",[],{"text":409,"added":5,"keys":410},"  \"scripts\": {",[],{"text":412,"added":5,"keys":413},"    \"build\": \"nuxt build\",",[],{"text":415,"added":5,"keys":416},"    \"dev\": \"nuxt dev\",",[],{"text":418,"added":5,"keys":419},"    \"generate\": \"nuxt generate\",",[],{"text":421,"added":5,"keys":422},"    \"preview\": \"nuxt preview\",",[],{"text":424,"added":5,"keys":425},"    \"postinstall\": \"nuxt prepare\"",[],{"text":357,"added":5,"keys":427},[],{"text":429,"added":5,"keys":430},"  \"dependencies\": {",[],{"text":432,"added":5,"keys":433},"    \"@fougere\u002Fcontainer\": \"latest\",",[],{"text":435,"added":5,"keys":436},"    \"@fougere\u002Fcore\": \"latest\",",[],{"text":438,"added":5,"keys":439},"    \"@fougere\u002Fdefaults\": \"latest\",",[],{"text":441,"added":5,"keys":442},"    \"@fougere\u002Flog\": \"latest\",",[],{"text":444,"added":5,"keys":445},"    \"@fougere\u002Fnuxt\": \"latest\",",[],{"text":447,"added":5,"keys":448},"    \"@fougere\u002Fschema\": \"latest\",",[],{"text":450,"added":5,"keys":451},"    \"nuxt\": \"^4.5.2\",",[],{"text":453,"added":5,"keys":454},"    \"vue\": \"^3.5.43\",",[],{"text":456,"added":5,"keys":457},"    \"vue-router\": \"^5.3.1\"",[],{"text":357,"added":5,"keys":459},[],{"text":461,"added":5,"keys":462},"  \"devDependencies\": {",[],{"text":464,"added":5,"keys":465},"    \"@fougere\u002Fcompiler\": \"latest\"",[],{"text":112,"added":5,"keys":467},[],{"text":141,"added":5,"keys":469},[],{"path":471,"state":51,"lines":472},"package.json",[473,475,478,480,482,485,488,491,493,496,499,501,503,506,509,511,513,515,518,520,522,525,528,531,534,537,539],{"text":397,"added":5,"keys":474},[],{"text":476,"added":5,"keys":477},"  \"name\": \"my-blog\",",[],{"text":406,"added":5,"keys":479},[],{"text":403,"added":5,"keys":481},[],{"text":483,"added":5,"keys":484},"  \"workspaces\": [",[],{"text":486,"added":5,"keys":487},"    \"apps\u002F*\"",[],{"text":489,"added":5,"keys":490},"  ],",[],{"text":409,"added":5,"keys":492},[],{"text":494,"added":5,"keys":495},"    \"typecheck\": \"tsc\",",[],{"text":497,"added":5,"keys":498},"    \"test\": \"vitest run --config tests\u002Fvitest.config.ts\"",[],{"text":357,"added":5,"keys":500},[],{"text":429,"added":5,"keys":502},[],{"text":504,"added":5,"keys":505},"    \"@fougere\u002Fadapter-rest\": \"latest\",",[],{"text":507,"added":5,"keys":508},"    \"@fougere\u002Fadapter-sql\": \"latest\",",[],{"text":435,"added":5,"keys":510},[],{"text":441,"added":5,"keys":512},[],{"text":447,"added":5,"keys":514},[],{"text":516,"added":5,"keys":517},"    \"better-sqlite3\": \"^13.0.3\"",[],{"text":357,"added":5,"keys":519},[],{"text":461,"added":5,"keys":521},[],{"text":523,"added":5,"keys":524},"    \"@fougere\u002Fcli\": \"latest\",",[],{"text":526,"added":5,"keys":527},"    \"@fougere\u002Ftesting\": \"latest\",",[],{"text":529,"added":5,"keys":530},"    \"@types\u002Fnode\": \"^22.19.15\",",[],{"text":532,"added":5,"keys":533},"    \"typescript\": \"^7.0.2\",",[],{"text":535,"added":5,"keys":536},"    \"vitest\": \"^4.1.0\"",[],{"text":112,"added":5,"keys":538},[],{"text":141,"added":5,"keys":540},[],"configuration","Mail is slow, so we run it in its own process. Only where it runs changes, not the code.","md","Your code says what a thing is. One line of config says where it runs.",[546,547,548,549,550,551,552,553,554,555,556,557,558],{"path":55,"state":51},{"path":78,"state":51},{"path":89,"state":51},{"path":144,"state":51},{"path":176,"state":51},{"path":210,"state":51},{"path":230,"state":51},{"path":241,"state":51},{"path":319,"state":320},{"path":363,"state":51},{"path":373,"state":51},{"path":394,"state":51},{"path":471,"state":51},[560,564,568,571],{"key":352,"side":561,"label":562,"detail":563},"in","the page is the same","posts.vue and the blog did not change — only where mail runs",{"key":353,"side":565,"label":566,"detail":567},"out","mail answers on :4100","npx fougere dev mail — the port is read from fronds:",{"key":354,"side":565,"label":569,"detail":570},"the fact crosses","as a JSON-RPC call, from the Nuxt process to mail's",{"key":355,"side":565,"label":572,"detail":573},"AuditMailer runs there","the link went with its frond","Mail *in its own process*",[576,579],{"title":577,"path":578},"Configuration","\u002Fdocs\u002Fconcepts\u002Fconfiguration",{"title":580,"path":581},"Deploy a split","\u002Fdocs\u002Frun\u002Fdeploy",{},{"title":45,"description":542},"8-split","tutorial\u002F8-split",{"command":587,"said":588,"output":592},"npx fougere dev mail   # then publish from localhost:3000\u002Fposts",{"npm":587,"pnpm":589,"bun":590,"yarn":591},"pnpm exec fougere dev mail   # then publish from localhost:3000\u002Fposts","bunx fougere dev mail   # then publish from localhost:3000\u002Fposts","yarn fougere dev mail   # then publish from localhost:3000\u002Fposts","INF [app:mail:AuditMailer] (mail:MailHandler.onPublished) sending Published: Hi","fronds\u002F","ottz6jH27HM9Ohz35vQLl9En9iiHUOlQTWarAKnjPOc",1794396611898]