[{"data":1,"prerenderedAt":256},["ShallowReactive",2],{"fougere:post.list:{}":3,"tutorial-steps":6,"tutorial:\u002Ftutorial\u002F1-an-entity":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":193,"description":194,"extension":195,"fig":196,"files":197,"grows":202,"heading":230,"install":231,"lede":194,"links":236,"meta":246,"name":14,"navigation":104,"path":12,"seo":247,"slug":248,"stem":249,"step":13,"tryIt":250,"zero":254,"__hash__":255},"tutorial\u002Ftutorial\u002F1-an-entity.md","One entity, and a whole CRUD","PATCH \u002Fapi\u002Fblog\u002Fposts\u002F:id with { status: 'published' } passes here: nothing guards it yet. A rule, two steps on, closes it.",{"type":48,"value":49,"toc":50},"minimark",[],{"title":51,"searchDepth":17,"depth":17,"links":52},"",[],[54,83,98,125],{"path":55,"state":56,"lines":57},"fronds\u002Fblog\u002Fentities\u002FPost.ts","new",[58,61,63,69,72,77,80],{"text":59,"added":5,"keys":60},"import { entity, oneOf, primary, text } from '@fougere\u002Fschema';",[],{"text":51,"added":5,"keys":62},[],{"text":64,"added":5,"keys":65},"export default class Post extends entity({",[66,67,68],"c","e","g",{"text":70,"added":5,"keys":71},"  id: primary(),",[],{"text":73,"added":5,"keys":74},"  title: text({ min: 1, max: 160 }),",[75,76],"d","h",{"text":78,"added":5,"keys":79},"  status: oneOf('draft', 'published', { default: 'draft' }),",[],{"text":81,"added":5,"keys":82},"}) {}",[],{"path":84,"state":56,"lines":85},"fronds\u002Fblog\u002Fhandlers\u002FPostHandler.ts",[86,89,92,94],{"text":87,"added":5,"keys":88},"import { Repository } from '@fougere\u002Fcore';",[],{"text":90,"added":5,"keys":91},"import Post from '..\u002Fentities\u002FPost.js';",[],{"text":51,"added":5,"keys":93},[],{"text":95,"added":5,"keys":96},"export default class PostHandler extends Repository\u003CPost> {}",[97],"a",{"path":99,"state":100,"lines":101},"fougere.config.ts","changed",[102,106,109,111,114,118,122],{"text":103,"added":104,"keys":105},"import '@fougere\u002Fadapter-rest';",true,[],{"text":107,"added":5,"keys":108},"import { defineFougere } from '@fougere\u002Fcore';",[],{"text":51,"added":5,"keys":110},[],{"text":112,"added":5,"keys":113},"export default defineFougere({",[],{"text":115,"added":104,"keys":116},"  protocols: { rest: {} },",[117],"b",{"text":119,"added":5,"keys":120},"  fronds: { '@fougere\u002Flog': {} },",[121],"f",{"text":123,"added":5,"keys":124},"});",[],{"path":126,"state":100,"lines":127},"package.json",[128,131,134,137,140,143,146,149,152,155,158,161,164,167,169,172,175,178,181,184,187,190],{"text":129,"added":5,"keys":130},"{",[],{"text":132,"added":5,"keys":133},"  \"name\": \"my-blog\",",[],{"text":135,"added":5,"keys":136},"  \"private\": true,",[],{"text":138,"added":5,"keys":139},"  \"type\": \"module\",",[],{"text":141,"added":5,"keys":142},"  \"scripts\": {",[],{"text":144,"added":5,"keys":145},"    \"typecheck\": \"tsc\",",[],{"text":147,"added":5,"keys":148},"    \"test\": \"vitest run --config tests\u002Fvitest.config.ts\"",[],{"text":150,"added":5,"keys":151},"  },",[],{"text":153,"added":5,"keys":154},"  \"dependencies\": {",[],{"text":156,"added":104,"keys":157},"    \"@fougere\u002Fadapter-rest\": \"latest\",",[],{"text":159,"added":5,"keys":160},"    \"@fougere\u002Fcore\": \"latest\",",[],{"text":162,"added":5,"keys":163},"    \"@fougere\u002Flog\": \"latest\",",[],{"text":165,"added":5,"keys":166},"    \"@fougere\u002Fschema\": \"latest\"",[],{"text":150,"added":5,"keys":168},[],{"text":170,"added":5,"keys":171},"  \"devDependencies\": {",[],{"text":173,"added":5,"keys":174},"    \"@fougere\u002Fcli\": \"latest\",",[],{"text":176,"added":5,"keys":177},"    \"@fougere\u002Ftesting\": \"latest\",",[],{"text":179,"added":5,"keys":180},"    \"@types\u002Fnode\": \"^22.19.15\",",[],{"text":182,"added":5,"keys":183},"    \"typescript\": \"^7.0.2\",",[],{"text":185,"added":5,"keys":186},"    \"vitest\": \"^4.1.0\"",[],{"text":188,"added":5,"keys":189},"  }",[],{"text":191,"added":5,"keys":192},"}",[],"entity","We write a Post entity and the handler that serves its rows, then start the app with npx fougere dev blog. It answers on port 4100 and keeps the rows in memory.","md","Two classes and one line of config. Everything around them is read from these.",[198,199,200,201],{"path":55,"state":56},{"path":84,"state":56},{"path":99,"state":100},{"path":126,"state":100},[203,207,210,213,216,219,222,226],{"key":97,"side":204,"label":205,"detail":206},"in","list · findById · create · update · delete","five operations — a page calls them: useQuery(post, 'list')",{"key":117,"side":204,"label":208,"detail":209},"5 REST routes under \u002Fapi\u002Fblog\u002Fposts","one per operation — because protocols: names rest. npx fougere explain lists them",{"key":66,"side":204,"label":211,"detail":212},"type Post","in the page's client, typed down to its refusals",{"key":75,"side":204,"label":214,"detail":215},"validation","title required, 160 at most — the same rule on the server and in the browser",{"key":67,"side":204,"label":217,"detail":218},"useFormFor(Post)","a form's fields and state, nothing to declare again",{"key":121,"side":204,"label":220,"detail":221},"every call written down","two lines per call in the terminal — @fougere\u002Flog, written by the starter",{"key":68,"side":223,"label":224,"detail":225},"out","posts kept in memory","the default with no sources: — the boot warns, and the next step moves them to SQLite",{"key":76,"side":227,"label":228,"detail":229},"refuses","VALIDATION_FAILED","what an empty title gets back, field by field","One entity, *and a whole CRUD*",{"npm":232,"pnpm":233,"bun":234,"yarn":235},"npm install @fougere\u002Fadapter-rest","pnpm add @fougere\u002Fadapter-rest","bun add @fougere\u002Fadapter-rest","yarn add @fougere\u002Fadapter-rest",[237,240,243],{"title":238,"path":239},"Entity","\u002Fdocs\u002Fconcepts\u002Fentity",{"title":241,"path":242},"Handler","\u002Fdocs\u002Fconcepts\u002Fhandler",{"title":244,"path":245},"Repository","\u002Fdocs\u002Fconcepts\u002Frepository",{},{"title":45,"description":194},"1-an-entity","tutorial\u002F1-an-entity",{"command":251,"said":252,"output":253},"curl -X POST localhost:4100\u002Fapi\u002Fblog\u002Fposts -d '{\"title\":\"\"}'",{"npm":251,"pnpm":251,"bun":251,"yarn":251},"422  VALIDATION_FAILED  title: String is too short (0 \u003C 1).",null,"8kC8QFO1-YSRkfofhzBtZOI_uMHJXnF25rULBWDlJUM",1794396611898]