[{"data":1,"prerenderedAt":264},["ShallowReactive",2],{"fougere:post.list:{}":3,"tutorial-steps":6,"tutorial:\u002Ftutorial\u002F3-a-rule":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":222,"description":223,"extension":224,"fig":225,"files":226,"grows":231,"heading":250,"install":46,"lede":223,"links":251,"meta":255,"name":22,"navigation":60,"path":20,"seo":256,"slug":257,"stem":258,"step":21,"tryIt":259,"zero":46,"__hash__":263},"tutorial\u002Ftutorial\u002F3-a-rule.md","Publishing is not an update",null,{"type":48,"value":49,"toc":50},"minimark",[],{"title":51,"searchDepth":17,"depth":17,"links":52},"",[],[54,80,121,150],{"path":55,"state":56,"lines":57},"fronds\u002Fblog\u002Fentities\u002FPost.ts","changed",[58,62,64,67,70,73,77],{"text":59,"added":60,"keys":61},"import { entity, oneOf, primary, readOnly, text } from '@fougere\u002Fschema';",true,[],{"text":51,"added":5,"keys":63},[],{"text":65,"added":5,"keys":66},"export default class Post extends entity({",[],{"text":68,"added":5,"keys":69},"  id: primary(),",[],{"text":71,"added":5,"keys":72},"  title: text({ min: 1, max: 160 }),",[],{"text":74,"added":60,"keys":75},"  status: readOnly(oneOf('draft', 'published', { default: 'draft' })),",[76],"d",{"text":78,"added":5,"keys":79},"}) {}",[],{"path":81,"state":56,"lines":82},"fronds\u002Fblog\u002Fhandlers\u002FPostHandler.ts",[83,86,89,91,94,99,102,105,109,111,115,118],{"text":84,"added":60,"keys":85},"import { ErrorCode, FougereError, Repository } from '@fougere\u002Fcore';",[],{"text":87,"added":5,"keys":88},"import Post from '..\u002Fentities\u002FPost.js';",[],{"text":51,"added":5,"keys":90},[],{"text":92,"added":60,"keys":93},"export default class PostHandler extends Repository\u003CPost> {",[],{"text":95,"added":60,"keys":96},"  async publish(id: Post['id']): Promise\u003CPost> {",[97,98],"a","b",{"text":100,"added":60,"keys":101},"    const post = await this.findById(id);",[],{"text":103,"added":60,"keys":104},"    if (!post) throw new FougereError({ code: ErrorCode.NOT_FOUND, message: 'No such post' });",[],{"text":106,"added":60,"keys":107},"    if (post.status === 'published') throw new FougereError({ code: ErrorCode.CONFLICT, message: 'Already published' });",[108],"e",{"text":51,"added":60,"keys":110},[],{"text":112,"added":60,"keys":113},"    return this.update(id, { status: 'published' });",[114],"c",{"text":116,"added":60,"keys":117},"  }",[],{"text":119,"added":60,"keys":120},"}",[],{"path":122,"state":51,"lines":123},"fougere.config.ts",[124,127,130,133,135,138,141,144,147],{"text":125,"added":5,"keys":126},"import '@fougere\u002Fadapter-rest';",[],{"text":128,"added":5,"keys":129},"import '@fougere\u002Fadapter-sql';",[],{"text":131,"added":5,"keys":132},"import { defineFougere } from '@fougere\u002Fcore';",[],{"text":51,"added":5,"keys":134},[],{"text":136,"added":5,"keys":137},"export default defineFougere({",[],{"text":139,"added":5,"keys":140},"  protocols: { rest: {} },",[],{"text":142,"added":5,"keys":143},"  sources: { main: { type: 'sqlite', path: '.data\u002Fblog.db' } },",[],{"text":145,"added":5,"keys":146},"  fronds: { '@fougere\u002Flog': {} },",[],{"text":148,"added":5,"keys":149},"});",[],{"path":151,"state":51,"lines":152},"package.json",[153,156,159,162,165,168,171,174,177,180,183,186,189,192,195,198,200,203,206,209,212,215,218,220],{"text":154,"added":5,"keys":155},"{",[],{"text":157,"added":5,"keys":158},"  \"name\": \"my-blog\",",[],{"text":160,"added":5,"keys":161},"  \"private\": true,",[],{"text":163,"added":5,"keys":164},"  \"type\": \"module\",",[],{"text":166,"added":5,"keys":167},"  \"scripts\": {",[],{"text":169,"added":5,"keys":170},"    \"typecheck\": \"tsc\",",[],{"text":172,"added":5,"keys":173},"    \"test\": \"vitest run --config tests\u002Fvitest.config.ts\"",[],{"text":175,"added":5,"keys":176},"  },",[],{"text":178,"added":5,"keys":179},"  \"dependencies\": {",[],{"text":181,"added":5,"keys":182},"    \"@fougere\u002Fadapter-rest\": \"latest\",",[],{"text":184,"added":5,"keys":185},"    \"@fougere\u002Fadapter-sql\": \"latest\",",[],{"text":187,"added":5,"keys":188},"    \"@fougere\u002Fcore\": \"latest\",",[],{"text":190,"added":5,"keys":191},"    \"@fougere\u002Flog\": \"latest\",",[],{"text":193,"added":5,"keys":194},"    \"@fougere\u002Fschema\": \"latest\",",[],{"text":196,"added":5,"keys":197},"    \"better-sqlite3\": \"^13.0.3\"",[],{"text":175,"added":5,"keys":199},[],{"text":201,"added":5,"keys":202},"  \"devDependencies\": {",[],{"text":204,"added":5,"keys":205},"    \"@fougere\u002Fcli\": \"latest\",",[],{"text":207,"added":5,"keys":208},"    \"@fougere\u002Ftesting\": \"latest\",",[],{"text":210,"added":5,"keys":211},"    \"@types\u002Fnode\": \"^22.19.15\",",[],{"text":213,"added":5,"keys":214},"    \"typescript\": \"^7.0.2\",",[],{"text":216,"added":5,"keys":217},"    \"vitest\": \"^4.1.0\"",[],{"text":116,"added":5,"keys":219},[],{"text":119,"added":5,"keys":221},[],"handler","A post is published only once. We write that rule as a method and make status readOnly, so nothing else can change it, not even update.","md","A state changes through an operation, never through a field write.",[227,228,229,230],{"path":55,"state":56},{"path":81,"state":56},{"path":122,"state":51},{"path":151,"state":51},[232,236,239,243,247],{"key":97,"side":233,"label":234,"detail":235},"in","POST \u002Fapi\u002Fblog\u002Fposts\u002F:id\u002Fpublish","the path read off id: Post['id']",{"key":98,"side":233,"label":237,"detail":238},"useCommand(post, 'publish')","typed down to its refusals",{"key":114,"side":240,"label":241,"detail":242},"out","status written","by the operation, the only one that may",{"key":76,"side":244,"label":245,"detail":246},"refuses","status: Read-only","a PATCH carrying status — readOnly takes it out of every input",{"key":108,"side":244,"label":248,"detail":249},"409 CONFLICT","on the card, and in OpenAPI","Publishing is *not an update*",[252],{"title":253,"path":254},"Handler","\u002Fdocs\u002Fconcepts\u002Fhandler",{},{"title":45,"description":223},"3-a-rule","tutorial\u002F3-a-rule",{"command":260,"said":261,"output":262},"curl -X POST localhost:4100\u002Fapi\u002Fblog\u002Fposts\u002F$ID\u002Fpublish   # twice",{"npm":260,"pnpm":260,"bun":260,"yarn":260},"409  CONFLICT  Already published","RaKrY-_T_QoO7oAUqBmzKefJ1714wwnpFmJtJKo7aY8",1794396611898]