แบ็กเอนด์อิสระ: เอกสารอ้างอิง API
หน้านี้อธิบาย API การยืนยันตัวตน การดีพลอย และการย้อนกลับของแบ็กเอนด์อิสระที่สร้างขึ้น หน้านี้อธิบายซอร์สที่สร้างให้ ไม่ได้ยืนยันว่าการดีพลอยบน Cloudflare ผ่านการตรวจรับแล้ว แบ็กเอนด์อิสระคืออะไรและสร้างอย่างไร ดู ภาพรวม ส่วนไฟล์ แพ็กเกจ และการแปลงตาราง ดู รายละเอียดการทำงาน
ID ที่สร้าง เช่น Article.findMany ระบุ operation ส่วน HTTP route ใช้ชื่อโมเดลตัวพิมพ์เล็ก เช่น /api/blogpost รายการ operation ที่รองรับการปรับแต่งมีเฉพาะ operation ที่มีการรักษาความปลอดภัยสร้างไว้
Method/path ใต้ /api/<model> |
Operation | ผลลัพธ์ |
|---|---|---|
GET / |
findMany |
อาร์เรย์ของแถว |
GET /paginated |
findManyPaginated |
{ data, total, hasMore } |
GET /unique |
findUnique |
แถวหรือ null |
POST / |
create |
แถว สถานะ 201 |
PUT / |
update |
แถว |
DELETE / |
delete |
แถว |
ตารางเชื่อมมีเพียงการอ่านสามแบบ การเขียนรายการลิงก์ทำผ่านโมเดลเจ้าของ (ด้านล่าง) ตารางแปลรับ create, update และ delete ของ editor ด้วย ไม่มี bulk, aggregate/count/groupBy, upsert, updateEach และ operation อื่นที่ไม่ได้ตั้งค่า การปิด operation นำออกจากการทำงานและ OpenAPI แต่ยังสงวนพาธไว้ก่อนเชื่อมต่อ raw route ที่เจ้าของกำหนด การอ่านไม่มี POST alias
การอ่านส่ง argument ผ่าน query parameter โดยใช้ JSON สำหรับค่าแบบโครงสร้าง การเขียนส่งออบเจ็กต์ argument ของ Prisma ใน JSON body ต้องเข้ารหัส query แบบโครงสร้างสำหรับ URL
GET /api/blogpost?take=25&skip=25GET /api/blogpost/unique?where={"id":"item-id"}POST /api/blogpost {"data":{"id":"new-post","featured":false}}PUT /api/blogpost {"where":{"id":"new-post"},"data":{"featured":true}}DELETE /api/blogpost {"where":{"id":"new-post"}}นี่ไม่ใช่ API สาธารณะ /v1 ของ CMS ผู้ใช้ได้รับผลแบบ Prisma ไม่ใช่สัญญา { data, page } ของ CMS
การเขียนความสัมพันธ์และคำแปล
หัวข้อที่มีชื่อว่า “การเขียนความสัมพันธ์และคำแปล”ID ความสัมพันธ์หนึ่งเป้าหมายเขียนบนแถวเหมือนคอลัมน์อื่น เป้าหมายที่ไม่มีอยู่ได้ 422 relation_target_missing
ลิงก์พาเรนต์แบบลำดับชั้น (ความสัมพันธ์หนึ่งเป้าหมายไปยังโมเดลของตัวเองที่ทำเครื่องหมาย ลำดับชั้น (แม่/ลูก)) ไม่วนเป็นวง การสร้างหรือแก้ไขที่ทำให้แถวเป็นพาเรนต์ของตัวเอง หรือย้ายแถวไปอยู่ใต้ลูกหลานของตัวเอง ได้ 422 relation_hierarchy_cycle พร้อม field (คอลัมน์ <field>_id) และ targets เส้นทางสร้างและแก้ไขที่สร้างขึ้นและ trusted route ตรวจก่อนเขียน trigger ของ SQLite ใน migrations/ ปฏิเสธการเขียนเดียวกันภายในฐานข้อมูล การเขียนสองครั้งที่แข่งกันจึงสำเร็จพร้อมกันไม่ได้ ครั้งที่ถูกปฏิเสธได้คำตอบเดียวกัน ทุก migration ลบ trigger เหล่านี้ก่อนการเปลี่ยนแปลงและสร้างใหม่หลังจากนั้น แถวราก การย้ายพาเรนต์ และการล้างพาเรนต์ (null) เป็นการเขียนปกติ ID ของโมเดลอื่น ของไซต์อื่น หรือที่ไม่มีอยู่ ได้ relation_target_missing แถวภาษาไม่เก็บพาเรนต์
ความสัมพันธ์แบบรายการมีหนึ่ง route ต่อฟิลด์บนเจ้าของ: PUT /api/<owner>/relations/<field> body ระบุแถวเจ้าของ และระบุรายการทั้งหมดตามลำดับหรือการเปลี่ยนแปลง
PUT /api/article/relations/tags {"where":{"id":"post-1"},"set":["tag-2","tag-1"]}PUT /api/article/relations/tags {"where":{"id":"post-1"},"connect":["tag-3"],"disconnect":["tag-1"]}POST /api/articletranslation {"data":{"article_id":"post-1","lang":"fr","title":"Bonjour"}}setแทนที่รายการตามลำดับที่ให้connectต่อท้ายdisconnectนำออก และ ID ที่ไม่ได้ลิงก์ถือว่าเสร็จแล้ว ใช้setร่วมกับอีกสองแบบไม่ได้- รายการผลลัพธ์ถูกตรวจด้วยกฎของ CMS: ไม่ซ้ำ (
relation_duplicate_target) ความบังคับและminItems(relation_too_few)maxItems(relation_too_many) เป้าหมายที่มีอยู่จริง (relation_target_missing) ระบบตรวจจำนวนรายการในทุกคำขอ การลบเป้าหมายจะลบลิงก์ จึงอาจทำให้รายการต่ำกว่าminItems; จากนั้น route จะตอบrelation_too_fewจนกว่าการเขียนจะทำให้รายการกลับมาอยู่ในขอบเขต รายการที่เท่ากับที่บันทึกไว้จะไม่ถูกเขียนซ้ำและตอบกลับด้วยรายการที่บันทึกไว้ - เป้าหมาย one-to-many ที่เจ้าของอื่นถืออยู่ถูกปฏิเสธด้วย
409 relation_target_taken - ลิงก์ถูกเขียนใหม่ใน database batch เดียว ถ้ารายการเปลี่ยนหลังจากอ่าน จะไม่มีการเขียนและตอบ
409 relation_changed_concurrently - route สืบทอด
<Owner>.update: access mode, permission, hook ของ operation และกลุ่ม การปิด<Owner>.updateปิด route นี้ พาธยังถูกสงวนไว้
แถว locale สร้างพร้อม row key และภาษา ภาษาต้องเป็นภาษาที่ไซต์ถูกสร้างด้วย (422 unknown_locale): ภาษาที่ตั้งค่าไว้ของ Blog หรือภาษาที่มีอยู่ในเนื้อหาที่เผยแพร่ ไซต์ประเภท Website ไม่ได้กำหนดรายการภาษาคงที่ไว้ ภาษาที่ยังไม่มีเนื้อหาเผยแพร่จึงต้อง generate ใหม่ แถวต้องมีอยู่ (422 translation_row_missing) และหนึ่งแถวมีแถว locale เดียวต่อภาษา (409 locale_exists) การ update เปลี่ยนเฉพาะค่าที่แปล การเขียนคำแปลสืบทอด <Model>.update และการลบสืบทอด <Model>.delete
ฟิลด์ที่อนุญาตหลายโมเดล
หัวข้อที่มีชื่อว่า “ฟิลด์ที่อนุญาตหลายโมเดล”ฟิลด์ความสัมพันธ์อนุญาตได้มากกว่าหนึ่งโมเดล ฟิลด์แบบนี้เก็บในตาราง join หนึ่งตารางต่อโมเดลที่อนุญาต เช่น ArticleRelatedTag และ ArticleRelatedPerson โดยแต่ละตารางมี foreign key ไปยังโมเดลของตนที่ลบลิงก์ไปพร้อมกับเป้าหมาย แถวของทุกตารางใช้ลำดับรายการเดียวกันผ่าน position รายการจึงผสมโมเดลได้อิสระ ฟิลด์หนึ่งเป้าหมายแบบผสม (manyToOne, oneToOne) เก็บแบบเดียวกัน ไม่ใช่ในคอลัมน์ ลิงก์พาเรนต์ (ลำดับชั้น) อนุญาตได้โมเดลเดียว
-
การอ้างอิงคือ
{"model":"<model slug>","id":"<row id>"}ในset,connect,disconnectและrelationsของการสร้างแบบอะตอมมิก ID เปล่าถูกปฏิเสธด้วย422 relation_target_model_requiredเมื่อฟิลด์อนุญาตหลายโมเดล โมเดลที่ฟิลด์ไม่อนุญาตตอบ422 relation_target_model_not_allowedและ ID ที่ไม่ใช่แถวของโมเดลที่ระบุ หรือไม่ใช่แถวเลย ตอบ422 relation_target_missingฟิลด์ที่อนุญาตโมเดลเดียวยังรับ ID เปล่า และรับ{ model, id }ที่ใช้โมเดลของตนด้วย -
คำตอบแสดงการอ้างอิง
{ "model", "id" }ตามลำดับ ในtargetsของข้อผิดพลาด การอ้างอิงของฟิลด์แบบนี้เขียนเป็นmodel:id -
กฎเดียวกับรายการทั่วไปใช้กับรายการผสมทั้งรายการ: ไม่ซ้ำ
minItemsและmaxItemsเป้าหมาย one-to-many ที่มีเจ้าของเดียว (409 relation_target_taken) และการเขียนหนึ่งครั้งใน batch เดียว ฟิลด์หนึ่งเป้าหมายถือการอ้างอิงเดียวข้ามทุกโมเดลของมัน รวมถึงเมื่อเขียนแข่งกัน การเขียนที่อ่านรายการเก่ากว่าตอบ409 relation_changed_concurrently -
GET /api/<model>/files/<field>/<row id>ให้บริการไฟล์ส่วนตัวเมื่อ<field>_urlของแถวเก็บตัวบ่งชี้private-file:Worker อ่านจาก binding R2PRIVATE_FILESส่วนเซิร์ฟเวอร์ Node (npm run serve:local) อ่านจากไดเรกทอรีแบบ absolute ที่มีอยู่จริงซึ่งระบุในPRIVATE_FILES_DIRECTORYโดยชนิดเนื้อหาของไฟล์เก็บไว้ใน<key>.meta.jsonข้างไฟล์ สืบทอด<Model>.findUniqueได้แก่โหมดการเข้าถึง สิทธิ์ hook และความพร้อมใช้งาน รวมถึงการอ่านแถวแบบมี guard ของผู้เรียกเอง ตอบ404สำหรับแถวที่ไม่มีตัวบ่งชี้ วัตถุที่บัคเก็ตไม่มี และแถวที่ผู้เรียกอ่านไม่ได้ หากไม่มีทั้ง binding และไดเรกทอรี จะตอบ503 private_files_unavailableและไม่ย้อนไปใช้ที่เก็บสาธารณะ คำตอบเป็นprivate, no-storeรูปภาพและ PDF แสดงในหน้า อย่างอื่นดาวน์โหลด -
สื่อในข้อความแบบจัดรูปแบบใช้เส้นทางส่วนตัวเดียวกันพร้อม
?media=<media id>เส้นทางจะค้นหาตัวบ่งชี้private-file:ที่อยู่ในฟิลด์ข้อความของรายการจริง ไม่รับคีย์ที่เก็บจากผู้เรียก&variant=thumbและ&variant=previewเลือกภาพตัวอย่าง WebP ของไฟล์เจ้าของ หากไม่มีภาพตัวอย่างจะส่งต้นฉบับ ฟังก์ชันprivateRichText(html, endpoint, rowId)ที่ส่งออกจะใส่ URL เหล่านี้ใน HTML มาตรฐานก่อนแสดงผล เส้นทางไฟล์ยังตรวจสิทธิ์อ่านรายการใหม่ทุกครั้ง -
GET /api/<owner>/relations/<field>?id=<owner id>อ่านรายการเรียงลำดับพร้อมโมเดลของแต่ละการอ้างอิง (เพิ่ม&locale=สำหรับรายการที่มีลิงก์แยกตามภาษา) สืบทอด<Owner>.findUniqueและฟิลด์ private ตอบเฉพาะบรรณาธิการ การอ่านตาราง join ตารางเดียว เช่นGET /api/articlerelatedtagคืนเฉพาะแถวของโมเดลนั้นpositionคือตำแหน่งในรายการทั้งหมด -
รายการที่เผยแพร่แล้วซึ่งลิงก์ไปยังโมเดลที่ฟิลด์ไม่อนุญาตจะบล็อกการสร้าง (
invalid_reference) เช่นเดียวกับโมเดลที่ไม่มีบนไซต์ (relation_target_absent)
สร้างแถวพร้อมรายการในการเขียนครั้งเดียว
หัวข้อที่มีชื่อว่า “สร้างแถวพร้อมรายการในการเขียนครั้งเดียว”เจ้าของที่มีความสัมพันธ์แบบรายการอย่างน้อยหนึ่งรายการมี POST /api/<owner>/with-relations ด้วย route นี้สร้างแถวและสมาชิกของรายการไปพร้อมกัน POST /api/<owner> แบบปกติยังสร้างแถวที่ไม่มีรายการ และ PUT /api/<owner>/relations/<field> ยังใช้แก้รายการภายหลัง
POST /api/article/with-relations {"data":{"id":"post-1","title":"Hello","author_id":"person-1"},"relations":{"tags":["tag-2","tag-1"],"topics":["topic-1","topic-2"]}}POST /api/note/with-relations {"data":{"id":"note-1"},"relations":{"related":{"en":["tag-1"],"fr":["tag-2","tag-3"]}}}dataคือ body ของการสร้างแบบปกติ ถูกตรวจด้วย create guard shape และ hook เดียวกับPOST /api/<owner>idที่ไม่ระบุจะถูกสร้างให้relationsระบุฟิลด์รายการ รายการร่วมคืออาร์เรย์ของ ID ตามลำดับ รายการที่มีลิงก์แยกตามภาษาคือออบเจ็กต์จากภาษาไปยังอาร์เรย์ สิ่งอื่นใน body ตอบ400 invalid_request- ทุกฟิลด์รายการของเจ้าของถูกตรวจ และฟิลด์ที่ไม่ระบุนับเป็นว่าง รายการที่บังคับ (
minItems) ต้องระบุ มิฉะนั้นตอบ422 relation_too_fewสำหรับรายการที่มีลิงก์แยกตามภาษา จะตรวจเฉพาะภาษาที่ระบุ - ก่อนเขียน route ตรวจกฎของ CMS: ไม่ซ้ำ (
relation_duplicate_target)minItemsและmaxItems(relation_too_few,relation_too_many) เป้าหมายที่มีอยู่ (relation_target_missing) เป้าหมาย one-to-many ที่เจ้าของอื่นถืออยู่ (409 relation_target_taken) ลิงก์พาเรนต์ที่ชี้ไปยังแถวที่กำลังสร้าง (relation_hierarchy_cycle) ID ที่เป็นแถวอยู่แล้ว (409 record_exists) และ ID ความสัมพันธ์หนึ่งเป้าหมายในdata(relation_target_missing) - แถวและลิงก์ทั้งหมดถูกเขียนใน database batch เดียวบน D1 และใน transaction เดียวบน SQLite ถ้าคำสั่งใดล้มเหลว จะไม่เก็บคำสั่งใดเลย: ไม่มีแถวที่ไม่มีลิงก์และไม่มีลิงก์ที่ไม่มีแถว การสร้างสองรายการที่อ้างเป้าหมาย one-to-many เดียวกันพร้อมกันสำเร็จทั้งคู่ไม่ได้ รายการหนึ่งตอบ
201ที่เหลือตอบ409 relation_target_takenและไม่ทิ้งอะไรไว้ ความล้มเหลวที่การตรวจมองไม่เห็น เช่นค่า unique ที่การเขียนอื่นยึดไปก่อน ตอบ409 write_conflict - ความสำเร็จตอบ
201พร้อม{ "id", <columns>, <field>: [ids] }ฟิลด์ private ถูกตัดออก เหมือนคำตอบการเขียนอื่นทั้งหมด - route สืบทอด
<Owner>.create: access mode, permission, hook ของ operation และกลุ่ม ตอบ403 forbiddenแก่ผู้เรียกที่สร้างไม่ได้ การปิด<Owner>.createลบ route นี้ ถ้าcreateของเจ้าของมี operation override route ตอบ501 atomic_create_unavailableเพราะ override แทนที่การเขียนที่ route นี้ทำ หากไม่มีตัวรัน batch จะตอบ503 relation_writes_unavailableOpenAPI ทำเครื่องหมาย route ด้วยx-atomic-create - ไม่เขียนแถวคำแปล สร้างด้วย
POST /api/<model>translation
การ deploy รัน scripts/check-relation-ids.mjs ก่อน apply migration ฐานข้อมูลที่ migrate โดย generator รุ่นก่อนอาจมี ID ความสัมพันธ์ที่ไม่ชี้ไปยังแถวใด และ migration ที่เพิ่ม foreign key จะล้มทั้งหมด การตรวจแสดงทุกรายการ (โมเดล คอลัมน์ ID แถว เป้าหมายที่หายไป) พร้อมวิธีแก้ และปฏิเสธด้วย relation_ids_dangling ก่อน migrate ใดๆ npm run db:init และ npm run data:load -- --database <file> รันการตรวจเดียวกันกับฐานข้อมูลเดียวกันก่อน migration ในเครื่อง การสร้าง standalone backend ในเครื่องลงฐานข้อมูลเป้าหมายที่มีอยู่ก็ปฏิเสธแบบเดียวกัน
การตรวจความพร้อมของโมเดลจะปฏิเสธรูปแบบความสัมพันธ์ที่การเขียนที่สร้างขึ้นทำไม่ได้: ขีดจำกัดที่ไม่มีรายการใดผ่าน (relation_bounds_unsatisfiable) โมเดลเป้าหมายที่ไม่มีในไซต์ (relation_target_absent) และความสัมพันธ์หนึ่งเป้าหมายแบบบังคับที่วนกลับมาที่โมเดลตัวเอง (relation_required_cycle) รายการที่เผยแพร่แล้วซึ่งลิงก์พาเรนต์วนเป็นวงอยู่แล้วถูกปฏิเสธด้วย relation_hierarchy_cycle พร้อมชื่อรายการ ตัวสร้างเนื้อหา แสดงหมายเหตุเดียวกันที่ฟิลด์ความสัมพันธ์
การตรวจสอบข้อความ
หัวข้อที่มีชื่อว่า “การตรวจสอบข้อความ”ฟิลด์ข้อความที่สร้างขึ้นใช้กฎ minLength, maxLength และ pattern ตามที่กำหนดไว้ รูปแบบรองรับคุณสมบัติ Unicode เช่น \p{L} และ \p{N} และยังคงข้อจำกัดของรูปแบบที่ตั้งใจให้รับเฉพาะ ASCII เมื่อเปลี่ยนกฎ ต้องสร้างและดีพลอยใหม่ การตรวจสอบข้อความที่ผู้ใช้กำหนดเป็นโค้ดจะทำให้การสร้างถูกปฏิเสธด้วย unsupported_text_validation ระบบไม่รันโค้ดนั้นและไม่ละทิ้งกฎ กฎข้อความที่ไม่ถูกต้อง เช่น รูปแบบที่คอมไพล์ไม่ได้หรือค่าต่ำสุดสูงกว่าค่าสูงสุด จะทำให้การสร้างถูกปฏิเสธด้วย invalid_field_validation
คิวรีพิกัด
หัวข้อที่มีชื่อว่า “คิวรีพิกัด”Route รายการที่สร้างรองรับ filter[location][within_radius]=13.75,100.5,1000 (ละติจูด ลองจิจูด เมตร) และ filter[location][within_bbox]=13,100,14,101 (ใต้ ตะวันตก เหนือ ตะวันออก) รวมจุดบนขอบเขตด้วย ถ้าค่าตะวันตกมากกว่าตะวันออก ขอบเขตจะข้ามเส้นแอนติเมริเดียน เรียงใกล้สุดด้วย sort=location&origin=13.75,100.5; sort=-location กลับทิศทาง ระเบียนที่ไม่มีพิกัดอยู่ท้ายสุด และใช้ ID แถวตัดสินเมื่อระยะเท่ากัน รัศมีใช้สูตร Haversine หน่วยเมตร
ตัวเลือกเหล่านี้ผ่าน route การยืนยันตัวตน guard ของแถว และสิทธิ์คิวรีฟิลด์เดิม ฟิลด์พิกัดที่ปิดไว้จะตอบ 403 spatial_field_denied ตัวเลือกไม่เปิดฟิลด์หรือ operation ที่ถูกถอดโดย customization หรือคิวรีที่บันทึกไว้ ห้ามใช้ sort ร่วมกับ orderBy; พิกัดหรือรูปแบบไม่ถูกต้องตอบ 400 spatial_query_invalid
Runtime SQLite ภายในเครื่องและ D1 ที่สร้างมี driver adapter คิวรีพิกัดที่ไม่มี adapter ตอบ 501 spatial_adapter_required มีการแปลง SQL สำหรับ PostgreSQL และ MySQL ด้วย แต่ต้องตรวจรับ runtime ของผู้ให้บริการเหล่านั้นก่อน deploy การกรองและเรียงลำดับเกิดก่อน SQL pagination และการนับใช้เงื่อนไขเดียวกัน
Shared reference ต้องใช้ grant ปัจจุบันจากไซต์ต้นทาง การสร้างที่มีการอ้างอิงเหล่านี้ตอบ shared_references_require_live_delivery ให้ลบหรือเปลี่ยนก่อนสร้างแบ็กเอนด์แยกอิสระ relation ปกติภายในไซต์ยังรองรับ
ต้นฉบับที่เก็บไว้ตามภาษา
หัวข้อที่มีชื่อว่า “ต้นฉบับที่เก็บไว้ตามภาษา”<field>_id ยังคงเป็น ID สื่อ และ <field>_url เป็นที่อยู่ต้นฉบับเริ่มต้นที่โอนแล้ว ฟิลด์ที่มีสื่อยังมี JSON <field>_originals: ภาษาที่แสดง → ID สื่อ → ข้อมูลและ URL ที่โอนแล้วของต้นฉบับที่เก็บไว้นั้น ฟิลด์ component และ rich text ที่ใช้ร่วมกันเก็บค่าที่เขียนเพียงชุดเดียว พร้อมต้นฉบับของแต่ละภาษาที่แสดง เมทาดาทาของฟิลด์ส่วนตัวและ component ที่มีฟิลด์ย่อยส่วนตัวยังคงเป็นส่วนตัว
นำเข้า mediaOriginal จาก src/media-original.ts ใช้ mediaOriginal(row, 'cover', 'fr') เพื่ออ่านต้นฉบับของ cover_id; ระบุ ID สื่อเป็นอาร์กิวเมนต์ที่สี่สำหรับ component หรือ rich text ถ้าไม่มีภาษาหรือไฟล์นั้นจะคืน null การสร้างใช้ผล fallback ที่บันทึกในเวอร์ชันเผยแพร่ ไม่เลือกไฟล์ใหม่จากคลัง Exit โอนแต่ละออบเจ็กต์ที่เก็บไว้แยกกัน และเปลี่ยนที่อยู่โดยคง ID สื่อ
การยืนยันตัวตน guards และข้อผิดพลาด
หัวข้อที่มีชื่อว่า “การยืนยันตัวตน guards และข้อผิดพลาด”นโยบายกำหนดว่า route ยอมรับ anonymous, session หรือ service caller โมเดลที่ไม่ตั้งค่าใช้ service Read token เก็บเป็นแฮชและ model scope ใน ACCESS_CONFIG route ที่ไม่มีโมเดลและ /api/openapi.json ต้องมี wildcard scope Service write ที่ไม่มี writer authenticator ตอบ 503 writes_unavailable ขาดข้อมูลรับรองตอบ 401 และสิทธิ์ถูกปฏิเสธตอบ 403 Better Auth แบบเลือกเปิดให้ session ทั้งในเครื่องและ Worker runtime ในเครื่องและ Worker รับ service bearer API_BEARER_TOKEN ตัวเดียวกัน
Managed deployment สร้าง ACCESS_CONFIG ทุกครั้งที่ดีพลอยจากสองแหล่ง: token ที่ CMS ออกให้ในไฟล์ที่สร้าง access/read-tokens.json และ vars.ACCESS_CONFIG ของคุณเองใน wrangler.jsonc ระบบดีพลอยการรวมกันของทั้งสอง หากไม่มีทั้งคู่จะใช้รายการว่างที่ถูกต้อง (tokens: []) ทั้งสองแหล่งถูกตรวจด้วยโมดูลกฎเดียวกัน src/access-config-rules.js ซึ่ง Worker ใช้ตอนเริ่มทำงานด้วย: เวอร์ชัน 1, เว็บไซต์นี้, ทุก token ระบุเว็บไซต์นี้, digest SHA-256 แบบ hex ตัวพิมพ์เล็ก, model scope, ไม่มีสิทธิ์เขียน, ไม่มี id token ซ้ำ และไม่มี digest ซ้ำ ค่าของคุณที่ไม่ถูกต้องจะถูกปฏิเสธด้วย owned_access_config_invalid ไฟล์ที่สร้างที่ไม่ถูกต้องด้วย generated_access_tokens_invalid id เดียวกันในทั้งสองแหล่งด้วย access_token_id_conflict และ digest เดียวกันในทั้งสองแหล่งด้วย access_config_union_invalid ทั้งหมดนี้ก่อนเปลี่ยนฐานข้อมูลหรือ Worker รายการของคุณเองจะไม่ถูกลบ ค่าที่ตั้งไว้เฉพาะในแดชบอร์ด Cloudflare จะถูกแทนที่ในการดีพลอยครั้งถัดไป จึงควรเก็บรายการของคุณไว้ใน wrangler.jsonc commit เฉพาะ digest ของ secret แบบสุ่มที่ยาวเท่านั้น ห้าม commit ตัว secret นอก generated deployment flow หาก ACCESS_CONFIG หายหรือไม่ถูกต้อง ระบบจะปฏิเสธการเริ่มทำงาน
Read token ที่ออกจาก CMS
หัวข้อที่มีชื่อว่า “Read token ที่ออกจาก CMS”การตั้งค่า → ซอร์สโค้ดและการเผยแพร่ → โทเคนอ่าน API ที่ deploy แล้ว ต้องมีสิทธิ์ site:infra
- ออก token ตั้งชื่อและเลือกโมเดลหนึ่งตัวขึ้นไป secret คือ
dw_art_ตามด้วยอักขระ base64url 43 ตัว แสดงเพียงครั้งเดียวและไม่ถูกเก็บไว้ที่ใด CMS เก็บเฉพาะ digest SHA-256 ผู้เรียกส่งAuthorization: Bearer <secret>CMS ไม่ออก token*ให้ token แบบ wildcard อยู่ในvars.ACCESS_CONFIGของคุณเอง - ส่งมอบ ไม่มีอะไรไปถึงแบ็กเอนด์ที่ดีพลอยแล้วจนกว่าคุณจะสร้างใหม่และดีพลอย การออกหรือเพิกถอน token ทำให้เว็บไซต์ถูกทำเครื่องหมายว่าล้าสมัย การสร้างใหม่เขียน token ที่ใช้งานอยู่ (เฉพาะ digest) ลงใน
access/read-tokens.jsonโดยจับคู่แต่ละ scope กับชื่อโมเดลที่สร้าง (และตารางคำแปลของโมเดลนั้น) - เพิกถอน การเพิกถอนไม่ดีพลอย แบ็กเอนด์ที่ดีพลอยแล้วยังยอมรับ token ที่ถูกเพิกถอนจนกว่าจะสร้างใหม่และดีพลอยครั้งถัดไป แบ็กเอนด์ที่แยกตัวแล้วอยู่นอกการควบคุมของ CMS
- หมุนเวียน ออก token ทดแทน ดีพลอย ย้ายผู้เรียกไปใช้ secret ใหม่ เพิกถอน token เก่า แล้วดีพลอยอีกครั้ง ไม่มีการสลับในที่เดิม
- โมเดล scope คือ id ของโมเดล การเปลี่ยนชื่อโมเดลจึงไม่เปลี่ยนอะไร การลบโมเดล หรือการย้อนการนำเข้าที่สร้างโมเดลนั้น จะนำโมเดลออกจากทุก token token ที่ไม่เหลือโมเดลจะถูกเพิกถอน ไม่ถูกขยายขอบเขต
- site API token ของ CMS (
site_api_token) ไม่ถูกคัดลอกไปยังแบ็กเอนด์ที่สร้างเลย - การสร้างจะปฏิเสธ token ที่ scope ไม่ตรงกับโมเดลที่สร้างอีกต่อไป (
token_scope_not_emitted) และการสร้างแบบโมเดลเดียวจะปฏิเสธขณะที่ยังมี token ที่ใช้งานอยู่ (read_tokens_need_site_graduation)
guard shape สำหรับ reader อนุญาตเงื่อนไขเท่ากับบน id ส่วน shape สำหรับ editor อนุญาตเงื่อนไขเท่ากับบนคอลัมน์ของแถวที่สร้าง ฟิลด์ส่วนตัวของโมเดลไม่อยู่ใน select ของ reader (การอ่านแบบ anonymous และ read token) และในคำตอบของการเขียนทุกครั้ง คอลัมน์คอมโพเนนต์ที่มีฟิลด์ส่วนตัวอยู่ที่ระดับใดก็ตามด้านล่างจะถูกตัดออกทั้งคอลัมน์ และตารางเชื่อมของความสัมพันธ์แบบรายการที่เป็นส่วนตัวตอบเฉพาะ editor ตารางของโมเดลรายการเดียวเก็บหนึ่งแถว: ดัชนีไม่ซ้ำปฏิเสธแถวที่สอง และ route สร้างตอบ 409 single_entry_exists รายการทั่วไปเรียงตาม id ค่าเริ่มต้น 25 สูงสุด 100 แถว ตารางแปลเรียงภาษาแล้ว id ตารางเชื่อมเรียงตำแหน่งแล้ว id ค่าเริ่มต้น 100 สูงสุด 200 ไม่รับ selector/operator ที่ไม่รองรับ D1 แยกต่อเว็บไซต์ Artifact ปฏิเสธ SITE_ID ที่ไม่ตรง ไม่ได้อ้างว่าแยก tenant ระดับแถว
ฟิลด์คอมโพเนนต์ถูกย้ายเป็นค่า JSON ที่ฝังอยู่หนึ่งค่า ซ้อนได้สูงสุด 3 ระดับใต้คอมโพเนนต์ราก ซึ่งเป็นขีดจำกัดเดียวกับที่ CMS บังคับเมื่อสร้างนิยาม ลำดับที่ลึกกว่านั้นจะถูกปฏิเสธก่อนสร้างด้วย component_nesting_too_deep โดยระบุคอมโพเนนต์และเส้นทางคอมโพเนนต์ที่นำไปถึง การอ้างอิงสื่อภายในคอมโพเนนต์ที่ซ้อนกันจะถูกพบในทุกระดับ
การปรับแต่งเอาการยืนยันตัวตน สิทธิ์ การเลือก variant, guards หรือการปกป้องข้อผิดพลาดออกไม่ได้ ส่วนประกอบที่สร้างจะ resolve guard ก่อน hook ตรวจ guard shape หลังจาก resolve แล้ว และปิดการข้าม guard ผ่าน environment โดยไม่ขึ้นกับ owned config ตัวเลือก guard อื่นที่รองรับของระบบยังเป็นข้อมูลเข้าของการสร้าง allowE2EGuardBypass ยังถูกเลิกใช้ใน upstream และไม่ใช่ทางข้ามของ override ที่นี่
ข้อผิดพลาดของ route รักษาการ normalize ของ upstream ข้อความที่เปิดเผย SQL การเรียก Prisma หรือพาธในเครื่องถูกแทนด้วย request_refused โดยคงสถานะไว้ ข้อผิดพลาด owned ที่ไม่ถูกจัดการตอบ request_failed ถ้าตั้ง commit ไว้จะส่งใน x-artifact-commit
การตั้งค่า guard
หัวข้อที่มีชื่อว่า “การตั้งค่า guard”การตั้งค่า → ซอร์สโค้ดและการเผยแพร่ → การป้องกัน API ที่สร้าง ยังคงเป็นอินพุตนโยบาย generator ระดับการติดตั้ง ชั้นประกอบ owned ล็อกค่าความปลอดภัยต่อไปนี้ การประกาศ owned ลดความเข้มงวดไม่ได้ Variant reader/editor เดิมยังเป็นกลไกเลือก variant เพียงชุดเดียว
| การตั้งค่า | Pipeline customization ที่สร้าง |
|---|---|
requireGuardShape |
true |
validateGuardShapes |
true |
requireDefaultVariantOptIn |
รักษานโยบายที่เลือกในการติดตั้ง |
enableUpdateEach |
false |
guardResolutionOrder |
before-hooks |
allowE2EGuardBypass |
false |
validateResolvedShapes |
true |
OpenAPI
หัวข้อที่มีชื่อว่า “OpenAPI”GET /api/openapi.json ที่ยืนยันตัวตนแล้วรวม schema จาก upstream และ operation ที่เปิดกับ declarative route ที่ตรวจ schema Override ที่คงสัญญาเดิมใช้ schema คำขอ/ผลลัพธ์เดิม Route ที่กำหนดเองระบุ operation ของสิทธิ์และโมเดลถ้าต้องการ ระบบตรวจ schema subset ที่รองรับทั้งขาเข้าและขาออก Raw Hono route ไม่มีสัญญา OpenAPI ที่สร้างให้ การมีเอกสารไม่ได้เปิด route ให้เรียกแบบไม่ยืนยันตัวตน
การยืนยันตัวตนและสิทธิ์ของอาร์ติแฟกต์แบบเลือกเปิด
หัวข้อที่มีชื่อว่า “การยืนยันตัวตนและสิทธิ์ของอาร์ติแฟกต์แบบเลือกเปิด”ที่ การตั้งค่า → ซอร์สโค้ดและการเผยแพร่ → การยืนยันตัวตน แคช และการเปิดเผยของ standalone เปิด Better Auth ครั้งเดียวสำหรับทั้งแบ็กเอนด์ แล้วเลือก รหัสผ่าน, ลิงก์เข้าสู่ระบบ (Resend), Google หรือ GitHub ค่าเริ่มต้นคือปิด จึงไม่สร้าง dependency, runtime, route, ตาราง, migration, การตั้งค่าผู้ให้บริการ หรือ secret ของ Better Auth บัญชี CMS, session, invitation, password hash และข้อมูลรับรองผู้ให้บริการไม่ถูกย้าย โมเดลแอปชื่อ User แยกจากตารางตัวตนของอาร์ติแฟกต์
| โหมดสิทธิ์ | ผู้เรียกที่ยอมรับ |
|---|---|
public |
anonymous principal ที่มีชนิดชัดเจน ไม่ต้องมีข้อมูลรับรอง |
session |
session ปัจจุบันจาก Better Auth ของอาร์ติแฟกต์นี้ |
service |
API_BEARER_TOKEN หรือ scoped token ที่อ่านโมเดลนั้นได้ |
session_or_service |
session ของอาร์ติแฟกต์หรือ service authentication |
โหมดสิทธิ์ของแต่ละโมเดลและ override ของ operation ตั้งที่หน้าโมเดล → API แยก การบันทึกต้องมี site:infra นโยบายเก็บตาม id ของโมเดล จึงตามไปเมื่อเปลี่ยน slug และถูกลบพร้อมโมเดล การบันทึกระบุ revision ที่จะแทนที่ หน้าจอที่ล้าสมัยได้ 409 policy_changed_concurrently slug ที่ให้ชื่อโมเดลเดียวกัน (blog-post และ blog_post) จะถูกปฏิเสธเมื่อสร้างหรือเปลี่ยนชื่อโมเดล โมเดลที่ไม่มีการตั้งค่าใช้ service อย่างชัดเจน operation ที่ไม่มี override สืบทอดค่าของโมเดล ใช้ ID ตรงตัว เช่น User.findMany, User.findManyPaginated, User.findUnique, User.create, User.update, User.delete operation ที่ถูกลบทำให้ validation ปฏิเสธ และชนิดที่สร้างปฏิเสธ ID เก่าด้วย การเปิดปิด operation ยังอยู่ใน owned registry นโยบายสิทธิ์ไม่เปิด operation ที่ปิดไว้ แต่ละนโยบายต้องการ session โดยไม่ผูกกับวิธีเข้าสู่ระบบ
ตรวจ authentication และ permission ก่อน hooks และ overrides เสมอ Guards ขอบเขตความเป็นเจ้าของเดิม และการป้องกันข้อผิดพลาดยังบังคับใช้ Public create ปฏิเสธฟิลด์ระบบ เจ้าของ tenant บทบาท administrator รหัสผ่าน verification และลิงก์ผู้ให้บริการ รวมถึงข้อมูลซ้อน มันสร้างข้อมูลแอป ส่วน signup ต้องผ่าน route ของ Better Auth เท่านั้น Raw route ใช้ service Declarative route ต้องระบุ access.mode และ access.operation โดย model scope เป็นตัวเลือก OpenAPI แสดงโหมดแต่ละ operation
| รากติดตั้งเสริม | แพ็กเกจ | บล็อก | เวอร์ชันตรงตัว |
|---|---|---|---|
root เฉพาะเมื่อเปิด |
better-auth |
dependencies |
1.6.25 |
เมื่อเปิดจะสร้าง src/better-auth.ts การตรวจผู้ให้บริการ กฎจำกัดการเข้าใช้งานและ lockout เดิม และ migration แยกสำหรับตารางโครงสร้างพื้นฐาน _artifact_auth_* ตารางเหล่านี้ไม่มี CRUD endpoint ทั้ง Node/SQLite และ Worker/D1 ใช้วิธีที่เลือก ขอบเขตรหัสผ่าน ตัวตน OAuth ที่ตรวจแล้ว token แบบแฮช การเพิกถอน session ผ่านฐานข้อมูล อายุสูงสุดเจ็ดวัน และการตรวจ origin สำหรับการเปลี่ยนข้อมูลเหมือนกัน ไม่เก็บ access/refresh/ID token ของผู้ให้บริการ และไม่เปิดเผยรายละเอียดข้อผิดพลาดภายใน
รัน npm run auth:init เพื่อสร้าง secret สุ่มใหม่ใน .dev.vars ที่ถูก ignore โดยคง secret เดิมของอาร์ติแฟกต์ Node โหลดด้วย node --env-file=.dev.vars src/serve.local.mjs และ Wrangler โหลดโดยตรง ใช้ db:init ก่อน กำหนด ARTIFACT_ORIGIN เป็น origin ของ API เช่น http://127.0.0.1:8787 ในเครื่อง origin ระยะไกลต้องเป็น HTTPS การ deploy ใช้ hostname เป้าหมายที่ตรวจแล้ว ไม่ใช้ origin ของ CMS
| วิธีที่เลือก | runtime value ที่ต้องมี | callback เป้าหมาย |
|---|---|---|
| รหัสผ่าน | BETTER_AUTH_SECRET |
route รหัสผ่านภายใต้ /api/auth |
| ลิงก์เข้าสู่ระบบ (Resend) | BETTER_AUTH_SECRET, RESEND_API_KEY, MAIL_FROM |
<ARTIFACT_ORIGIN>/api/auth/magic-link/verify |
BETTER_AUTH_SECRET, GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET |
<ARTIFACT_ORIGIN>/api/auth/callback/google |
|
| GitHub | BETTER_AUTH_SECRET, GITHUB_CLIENT_ID, GITHUB_CLIENT_SECRET |
<ARTIFACT_ORIGIN>/api/auth/callback/github |
ลงทะเบียน callback ใน OAuth application ของผู้รับอาร์ติแฟกต์ Resend ต้องมีโดเมนผู้ส่งที่ยืนยันแล้ว ต้องมีข้อมูลรับรองเฉพาะวิธีที่เลือก preflight จะหยุดก่อน deploy หากขาดค่าเหล่านี้ Actions secret ใช้ชื่อ runtime พร้อมคำนำหน้า ARTIFACT_ ซึ่งหลีกเลี่ยงคำนำหน้า GITHUB_ ที่ GitHub สงวนไว้ด้วย การส่งมอบสร้าง secret อาร์ติแฟกต์ใหม่แล้วเข้ารหัสด้วย public key ของเป้าหมาย หากชื่อ secret นั้นมีอยู่ที่ปลายทางแล้ว ระบบจะเก็บค่าเดิมไว้โดยไม่เปลี่ยนค่า npm run backend:deliver อ่านข้อมูลผู้ให้บริการจาก environment ARTIFACT_* เท่านั้น Secret ไม่เข้าซอร์ส manifest generation input digest vars ที่ commit log หรือ deployment ledger
Regeneration รักษา owned/shared file การเปิด auth ใน shared package เดิมหรือเปลี่ยนวิธีต้องสร้างใหม่เพื่ออัปเดต dependency และ binding ของ workflow ผู้เรียก การปิด auth ใน destination เดิมถูกปฏิเสธ ให้ใช้ปลายทางใหม่และเก็บตัวตนกับประวัติ migration เดิม การเปลี่ยนเฉพาะสิทธิ์มีผลต่อ generation-source และ manifest digest
verify:deployment เคารพนโยบาย public อ่านแบบ anonymous, service ใช้ bearer, session-only ต้องมี session ปัจจุบันของเป้าหมายใน ARTIFACT_VERIFY_SESSION_COOKIE หากไม่มีจะตอบ verify_session_required ไม่รายงานว่าการตรวจที่ไม่ได้ทำผ่านแล้ว ในการ deploy ครั้งแรกให้สร้างฐานเป้าหมาย สมัครผ่าน auth route แล้วตรวจซ้ำด้วย session นั้น อย่าเก็บ session อายุสั้นในซอร์สหรือหลักฐาน การยอมรับผ่านเบราว์เซอร์และ deployment จริงยังเป็นคนละขั้นตอน
การดูแลบัญชีของแอปพลิเคชัน
หัวข้อที่มีชื่อว่า “การดูแลบัญชีของแอปพลิเคชัน”ปิดเป็นค่าเริ่มต้น เมื่อเปิด Better Auth อาร์ติแฟกต์สามารถเปิด admin plugin ของ Better Auth สำหรับบัญชีของแอปพลิเคชันเองได้ บัญชี บทบาท และ session อยู่ในฐานข้อมูลของแอปพลิเคชัน และอำนาจเป็นของผู้ดูแลแอปพลิเคชัน CMS ของ Dee Wan ไม่เก็บสำเนาของบัญชี ไม่เกี่ยวข้องกับคำขอ และไม่จำเป็นต้องทำงาน เมื่อไม่เปิดการผสานรวมนี้ จะไม่มี plugin, route, migration, คอลัมน์ หรือ binding ถูกสร้าง
เปิดใช้ เพิ่ม "accountAdmin": true ในการตั้งค่า authentication ของไซต์ แล้วสร้างใหม่และดีพลอย: PATCH /api/sites/<site-id>/standalone-auth พร้อม {"authentication":{"enabled":true,"methods":["password"],"accountAdmin":true}} และสิทธิ์ site:infra การบันทึกที่ไม่ระบุ accountAdmin คงค่าปัจจุบัน ในหน้าผู้ดูแลคือสวิตช์ “การจัดการบัญชีในแอปพลิเคชันที่สร้างขึ้น” ที่ การตั้งค่า → ซอร์สโค้ดและการเผยแพร่ → การยืนยันตัวตน แคช และการเปิดเผยของ standalone และจะปิดใช้งานขณะ Better Auth ปิดอยู่ การตั้งค่านี้เพิ่ม migration migrations/<number>_artifact_auth_admin.sql: เพิ่มคอลัมน์ role, banned, banReason และ banExpires ใน _artifact_auth_user และ impersonatedBy ใน _artifact_auth_session หมายเลขคือหมายเลขว่างถัดไปในสายของ migration ที่อาร์ติแฟกต์มีอยู่แล้วเมื่อเปิดการตั้งค่าครั้งแรก (0004 สำหรับอาร์ติแฟกต์ใหม่) การสร้างใหม่คงไฟล์และหมายเลขนั้นไว้ และ migration ที่ใช้แล้วจะไม่ถูกเปลี่ยนหมายเลข การปิดการตั้งค่าภายหลังลบ route และหน้าจอ แต่คงคอลัมน์และไฟล์ migration ไว้ การเปิดอีกครั้งใช้ไฟล์เดิม
ใครเป็นผู้ดูแล ผู้ใช้ที่ role เป็น admin หรือมี ID อยู่ใน runtime binding ที่เลือกได้ ARTIFACT_ADMIN_USER_IDS (คั่นด้วยจุลภาค ไม่เกิน 50 ID) บัญชีใหม่ใช้บทบาทเริ่มต้นที่ตั้งไว้ เว้นแต่ผู้ดูแลจะเลือกบทบาทอื่น list-users และ get-user เพิ่ม access (admin หรือ user) และ adminSource (role, configuration, both หรือ null) ให้ทุกบัญชี โดยคำนวณทุกคำขอจากบทบาทที่เก็บไว้และ binding ปัจจุบัน binding ไม่เปลี่ยนบทบาทที่เก็บไว้ และหน้าบัญชีแสดงทั้งสองอย่าง เพื่อสร้างผู้ดูแลคนแรก ให้สมัครผ่าน /api/auth/sign-up/email อ่าน ID ของคุณจาก GET /api/auth/get-session ตั้ง binding เป็น Worker secret ของเป้าหมาย (หรือในสภาพแวดล้อมของเซิร์ฟเวอร์ Node) แล้วรีสตาร์ท ผู้ดูแลคนถัดไปเลื่อนขั้นด้วย set-role ฐานข้อมูลป้องกันการลบ ปิดกั้น หรือลดสิทธิ์ผู้ดูแลที่ใช้งานได้คนสุดท้าย ส่วน binding ยังคงเป็นช่องทางกู้คืนการเข้าถึงแยกต่างหาก workflow ดีพลอยที่สร้างขึ้นไม่จัดการ binding นี้
ทุกการเรียกใช้ cookie ของ session ผู้ดูแลเอง และการเปลี่ยนแปลงต้องมาจาก ARTIFACT_ORIGIN เหมือนการเรียก auth อื่น พาธอยู่ใต้ /api/auth/admin/
| การดำเนินการ | เมธอดและพาธ | Body หรือ query | ผลลัพธ์ |
|---|---|---|---|
| แสดงบัญชี | GET /api/auth/admin/list-users |
limit (ไม่เกิน 100 ค่าเริ่มต้น 100), offset, searchValue, searchField, sortBy |
{ users, total } |
| สร้างบัญชี | POST /api/auth/admin/create-user |
{ name, email, role?, password? } |
Better Auth |
| ลบบัญชี | POST /api/auth/admin/remove-user |
{ userId } |
Better Auth |
| อ่านบัญชีเดียว | GET /api/auth/admin/get-user |
id |
บัญชี |
| แก้โปรไฟล์ | POST /api/auth/admin/update-user |
{ userId, data } โดย data มีเฉพาะ name, image หรือ email |
บัญชีที่อัปเดต |
| ตั้งสิทธิ์ของแอปพลิเคชัน | POST /api/auth/admin/set-role |
{ userId, role } โดย role เป็น admin หรือ user |
บัญชีที่อัปเดต |
| แบน | POST /api/auth/admin/ban-user |
{ userId, banReason?, banExpiresIn? } (วินาที) |
ทำเครื่องหมายบัญชีว่าถูกแบนและยุติทุก session |
| เลิกแบน | POST /api/auth/admin/unban-user |
{ userId } |
ล้างการแบน |
| แสดง session ของบัญชี | POST /api/auth/admin/list-user-sessions |
{ userId } |
{ sessions } |
| เพิกถอนหนึ่ง session | POST /api/auth/admin/revoke-user-session |
{ sessionToken } |
{ success: true } |
| เพิกถอนทุก session | POST /api/auth/admin/revoke-user-sessions |
{ userId } |
{ success: true } |
บัญชีที่ถูกแบนลงชื่อเข้าใช้ไม่ได้จนกว่าจะเลิกแบนหรือ banExpiresIn ครบกำหนด การลงชื่อเข้าใช้ตอบ 403 authentication_failed
การปฏิเสธ ทุกการปฏิเสธตอบ { "error": "account_admin_refused", "code": "<code>" } พร้อมสถานะด้านล่าง
| สถานะ | code |
เมื่อใด |
|---|---|---|
| 401 | ไม่มี code หรือ code ของ Better Auth | ไม่มี session ที่ใช้ได้ |
| 403 | YOU_ARE_NOT_ALLOWED_TO_… |
ผู้ใช้ที่ลงชื่อเข้าใช้ไม่ใช่ผู้ดูแล หรือ origin ไม่ถูกต้อง |
| 404 | USER_NOT_FOUND |
ไม่มีบัญชีนั้น |
| 404 | ACCOUNT_ADMIN_OPERATION_UNSUPPORTED |
พาธหรือเมธอดอื่นใดใต้ /api/auth/admin/: ตั้งรหัสผ่าน สวมรอย หยุดสวมรอย ตรวจสิทธิ์ |
| 400 | ACCOUNT_ADMIN_PROFILE_FIELD_UNSUPPORTED |
update-user ที่มีฟิลด์อื่นนอกจาก name, image, email หรือไม่มีฟิลด์เลย |
| 400 | YOU_CANNOT_BAN_YOURSELF |
ผู้ดูแลแบนบัญชีของตัวเอง |
| 400 | YOU_ARE_NOT_ALLOWED_TO_SET_NON_EXISTENT_VALUE |
set-role ด้วยบทบาทที่ไม่รู้จัก |
บทบาทผู้ดูแลมีเฉพาะสิทธิ์ที่การดำเนินการเหล่านี้ใช้ ดังนั้นการดำเนินการนอกตารางจึงใช้ไม่ได้แม้คำขอจะไปถึง plugin การสร้างและลบบัญชีใช้ API ของ Better Auth โดยตรง ส่วน trigger ในฐานข้อมูลป้องกันการลบผู้ดูแลคนสุดท้ายและบัญชีที่มีข้อมูลแอปพลิเคชันอ้างถึง แม้มีคำขอพร้อมกัน ตาราง _artifact_auth_* ไม่มี CRUD endpoint ทั่วไป
หน้าบัญชี อาร์ติแฟกต์ให้บริการหน้าสำหรับการดำเนินการเหล่านี้ที่ /admin/accounts บน origin ของตัวเอง หน้านี้ใช้เซสชันผู้ดูแลของแอปพลิเคชันเองในเบราว์เซอร์ด้วย cookie ของหน้านั้นเอง และเรียกเฉพาะการดำเนินการในตาราง รวมถึง get-session, sign-in/email และ sign-out ของอาร์ติแฟกต์ เมื่อยังไม่ได้ลงชื่อเข้าใช้ หน้าจะแสดงฟอร์มลงชื่อเข้าใช้หากเลือกวิธี password หากใช้วิธีอื่นให้ลงชื่อเข้าใช้ผ่านแอปพลิเคชันก่อน ผู้ใช้ที่ลงชื่อเข้าใช้แต่ไม่ใช่ผู้ดูแลจะเห็นการปฏิเสธ บัญชีที่มีสิทธิ์ผู้ดูแลจะแสดงบทบาทที่เก็บไว้ และแยกต่างหากเป็น “Administrator (role)”, “Administrator (configured)” หรือ “Administrator (role and configured)” ผู้ดูแลค้นหาบัญชีด้วยอีเมล แก้ไขโปรไฟล์ ตั้งบทบาท แบนและเลิกแบนบัญชี และดูและเพิกถอนเซสชันของบัญชีได้ หน้านี้ไม่เก็บข้อมูลบัญชีของตัวเองและไม่มีที่อยู่ของ CMS จึงทำงานได้เมื่อ CMS ออฟไลน์ เมื่อปิดการผสานรวม จะไม่มี route นี้ ในหน้าผู้ดูแล Dee Wan เมื่อบันทึกสวิตช์เป็นเปิดแล้วและการดีพลอยบันทึก hostname แล้ว (การตั้งค่า → ซอร์สโค้ดและการเผยแพร่ → ขั้นสูง — build webhook และการ deploy → แบ็กเอนด์ Standalone) ลิงก์ “เปิดการจัดการบัญชี” ใต้สวิตช์จะเปิด https://<hostname>/admin/accounts ในแท็บใหม่ ก่อนมี hostname หน้าผู้ดูแลจะแสดง “ปรับใช้แอปพลิเคชันเพื่อบันทึกที่อยู่ของแอป” CMS ไม่เคย proxy cookie ของแอปพลิเคชัน: หน้าที่ CMS ให้บริการเรียกการดำเนินการเหล่านี้ไม่ได้ เพราะ cookie เซสชันของอาร์ติแฟกต์เป็นของ origin ของอาร์ติแฟกต์และรายการ origin ที่เชื่อถือมีเพียง origin นั้น ใช้ curl หรือไคลเอนต์ของคุณเองบน origin ของอาร์ติแฟกต์ได้เช่นกัน
บทบาทในแอปพลิเคชัน
หัวข้อที่มีชื่อว่า “บทบาทในแอปพลิเคชัน”ใน การตั้งค่า → ซอร์สโค้ดและการเผยแพร่ เปิดการเข้าสู่ระบบแอปพลิเคชันและการดูแลบัญชี
ส่วน บทบาทในแอปพลิเคชัน ใช้เพิ่มบทบาท เช่น customer หรือ staff เลือกบทบาทเมื่อสมัคร
และเลือกการทำงาน API ที่อนุญาตสำหรับแต่ละโมเดล บันทึกแล้วสร้างและดีพลอยแอปพลิเคชันใหม่
ผู้ดูแลกำหนดบทบาทเหล่านี้ให้บัญชีได้ที่ /admin/accounts
รองรับสูงสุด 32 บทบาท รวม admin และ user ชื่อยาว 1–64 ตัว เริ่มด้วยอักษรอังกฤษตัวพิมพ์เล็ก
และใช้เฉพาะอักษรอังกฤษตัวพิมพ์เล็ก ตัวเลข ขีดกลางหรือขีดล่าง บทบาทเมื่อสมัครต้องมีอยู่และห้ามเป็น
admin บทบาทผู้ดูแลยังใช้ได้ทุกการทำงาน ทุกการทำงาน เป็นสิทธิ์ที่เลือกอย่างชัดเจน
หากไม่ได้เลือก จะใช้ได้เฉพาะ ID การทำงานที่ระบุ รายการว่างไม่อนุญาตการทำงานใด หากไม่ตั้งค่าส่วนนี้
พฤติกรรม admin/user เดิมจะไม่เปลี่ยน
การตั้งค่าเก็บใน backend_policy.authentication.roles:
{"default":"customer","grants":{"admin":["*"],"user":[],"customer":["Article.findMany","Article.findUnique"]}}ข้อจำกัดนี้ใช้กับเซสชันแอปพลิเคชันในการทำงาน API ที่สร้างขึ้น การทำงานสาธารณะยังเป็นสาธารณะ และข้อมูลรับรองบริการยังใช้ขอบเขตเดิม การอ่านที่เชื่อถือได้และการเขียนคำแปลสืบทอดการทำงาน ที่กำหนดไว้ บทบาทและบัญชี CMS แยกจากแอปพลิเคชัน การเปลี่ยนบทบาทมีผลกับคำขอถัดไปโดยไม่ต้อง ให้ CMS ออนไลน์ ผู้ดูแลที่ระบุในค่ากู้คืนยังเข้าถึงได้ บทบาทที่ลบหรือไม่รู้จักไม่อนุญาตการทำงานใด การสร้างและดีพลอยใหม่ตามปกตินำการตั้งค่านี้ไปด้วยโดยไม่เขียนทับโค้ดที่คุณดูแล
สร้างและลบบัญชี เปิด /admin/accounts แล้วเลือก New account กรอกชื่อ อีเมล บทบาทเริ่มต้น และรหัสผ่านหากเปิดใช้การเข้าสู่ระบบด้วยรหัสผ่าน การสร้างบัญชีใช้บริการ Better Auth เดิม ไม่ต้องสมัครผ่านหน้าสาธารณะหรือส่งอีเมล ในหน้าบัญชี เลือก Delete account เพื่อยืนยันการลบที่ย้อนกลับไม่ได้และยุติทุกเซสชัน หากได้รับ 409 ACCOUNT_ADMIN_LINKED_RECORDS ให้เปลี่ยนหรือลบความสัมพันธ์ในตารางและฟิลด์ที่ระบุก่อน ฐานข้อมูลป้องกันการลบข้อมูลแอปที่เชื่อมโยงแบบต่อเนื่อง ไม่อนุญาตให้ลบบัญชีตัวเอง (YOU_CANNOT_REMOVE_YOURSELF) หรือผู้ดูแลที่ใช้งานได้คนสุดท้าย (ACCOUNT_ADMIN_LAST_ADMINISTRATOR)
Snapshot, migration, ดีพลอย และย้อนกลับ
หัวข้อที่มีชื่อว่า “Snapshot, migration, ดีพลอย และย้อนกลับ”data/published-snapshot.json เป็นไฟล์ที่สร้างและตรวจ digest db:init ใช้ migration ในเครื่องก่อนโหลด ส่วน data:load โหลดไปยัง SQLite หรือ D1 ที่ระบุ การโหลด snapshot เดิมซ้ำไม่เปลี่ยนอะไร ระบบปฏิเสธฐานข้อมูลที่มี snapshot ของเว็บไซต์หรือ artifact อื่น ไบต์ของ snapshot ที่ถูกแก้ และการโหลดที่ไม่ครบ snapshot ที่มีเนื้อหาต่างไปจะแทนที่ทุกตารางที่มันเป็นเจ้าของ: การโหลดลบแถวของตารางแล้วเขียนแถวของ snapshot แถวที่ไม่มีใน snapshot จะหายไป ไม่ว่าใครเป็นผู้เขียน และระบบไม่ปฏิเสธการโหลดด้วยเหตุนี้ ตารางที่ generator ไม่ได้สร้างไม่ถูกแตะ เก็บข้อมูลของแอปในตารางของแอปเอง การปฏิเสธ observed_row_set_mismatch เป็นของการสร้างในเครื่องลงฐานข้อมูล --db แบบใช้แล้วทิ้งของมันเอง ไม่ใช่ของ data:load
npm run db:init -- --database ./standalone.dbการสร้างใหม่ตรวจเจ้าของซอร์ส สร้าง schema/app/security/site สร้าง Prisma, guard และ route ใหม่ ตัด path ของเครื่องที่ใช้สร้างออก และตรวจการปรับแต่ง Migration เพิ่มต่อประวัติ การเปลี่ยนที่ทำลายข้อมูลหรือโครงสร้างไม่เข้ากันต้องผ่านขั้นตอนตรวจอย่างชัดเจน manifest ระบุความเป็นเจ้าของไฟล์ฉบับสุดท้ายต้องตรงกับชุดไฟล์ที่สร้างเสร็จแล้ว
การสร้างใหม่ในเครื่อง
หัวข้อที่มีชื่อว่า “การสร้างใหม่ในเครื่อง”npm run regenerate รัน scripts/regenerate.mjs แล้วตามด้วย prisma:generate, portable และ customization:check อาร์ติแฟกต์ทั้งไซต์ต้องใช้ข้อมูลเข้าสองอย่าง:
definitions.jsonที่รากของรีโพซิทอรี: นิยามของไซต์ตามที่ CMS ส่งให้ Git ไม่ติดตามไฟล์นี้ และ build ที่ระบบจัดการจะลบมันก่อน commit หากไม่มีไฟล์ การสร้างใหม่จะปฏิเสธด้วยdefinitions_missingGENERATION_SOURCE_VERSION: เวอร์ชันที่นิยามเหล่านั้นอธิบาย หากไม่มี การสร้างใหม่จะปฏิเสธด้วยgeneration_source_absentค่าที่ต่างจากdata.generationSourceVersionในไฟล์จะถูกปฏิเสธด้วยgeneration_source_version_mismatch
ขณะเชื่อมต่อ CMS ส่งนิยามให้เฉพาะ build token ลงชื่อเข้าใช้หน้าผู้ดูแลด้วยสิทธิ์ site:deploy แล้วเรียก POST /api/sites/<site-id>/build/token คำตอบมี build_token และ generation_source_version token มีอายุสั้นและอ่านได้เฉพาะนิยามของเวอร์ชันนั้น DEE_WAN_URL คือต้นทาง CMS เดียวกับที่ workflow ที่ระบบจัดการใช้ หากไซต์เปลี่ยนหลังจากออก token การดึงนิยามจะตอบ 409 source_moved
curl -sS -o definitions.json -H "Authorization: Bearer <build_token>" "$DEE_WAN_URL/api/sites/<site-id>/definitions"export GENERATION_SOURCE_VERSION=<generation_source_version>node scripts/install-dependencies.mjsnpm run regeneratenpm run migrate:emitnpm run typechecknpm run buildnpm run files:manifestrm definitions.jsonหลังแยกตัว เมื่อไม่มี CMS การสร้างใหม่ทำได้จาก definitions.json ที่คุณดึงไว้ก่อนหน้าเท่านั้น เช่น ด้วยขั้นตอนขณะเชื่อมต่อด้านบนก่อนปลดระวาง เก็บสำเนานั้นไว้นอกรีโพซิทอรี: มันคือโมเดลเนื้อหาทั้งหมดของไซต์ การสร้างใหม่จากสำเนานั้นสร้างไฟล์ generated ของเวอร์ชันเดิมขึ้นใหม่ และไม่นำเนื้อหาที่ใหม่กว่ามา
cp <kept-definitions.json> definitions.jsonexport GENERATION_SOURCE_VERSION="$(node -p "require('./definitions.json').data.generationSourceVersion")"node scripts/install-dependencies.mjsnpm run regeneratenpm run migrate:emitnpm run typechecknpm run buildnpm run files:manifestrm definitions.jsonการสร้างใหม่ปฏิเสธไฟล์ generated ที่ถูกแก้ หากคุณแก้ไฟล์ generated หลังแยกตัว ให้เลิกสร้างใหม่
อาร์ติแฟกต์ --model ต้องใช้ MODEL_VERSION_ID และนิยามของโมเดลนั้นแทน หากไม่มีตัวแปรนี้จะปฏิเสธด้วย model_version_absent route /definitions ของ CMS ส่งเฉพาะนิยามของไซต์ ตัวอ่าน manifest แบบโมเดลเดียวอ่าน payload นี้ แล้วบันทึกและตรวจ settings ของ generator guard ของอาร์ติแฟกต์นั้นอีกครั้ง
deploy-from-repository.yml เลือก commit แน่นอน ติดตั้งแพ็กเกจที่ตรวจแล้ว ตรวจ/บิลด์ ระบุ D1 ใช้ migrations/ ด้วย wrangler d1 migrations apply ดีพลอย API ตั้ง secret เขียน โหลด snapshot ใน commit และล้างแคช ตรวจเนื้อหา/สื่อ แล้วดีพลอยเว็บไซต์ verify:deployment เทียบแถวที่ให้บริการและ commit ส่วน verify:media ตรวจข้อมูลที่ commit ไว้เพื่อหาแหล่งอ้างอิงสื่อที่ CMS ยังเป็นเจ้าของ git_sha เก่าเลือกโค้ดรุ่นก่อน ไม่ใช่การกู้ฐานข้อมูลหรือสื่ออัตโนมัติ ต้องเก็บประวัติ migration ที่เข้ากันได้ ข้อมูลสำรอง และวัตถุของเจ้าของก่อนย้อนกลับ
แคชการตอบกลับและการล้างแคชอธิบายไว้ใน แคชการตอบกลับ
ข้อจำกัดที่ทราบ
หัวข้อที่มีชื่อว่า “ข้อจำกัดที่ทราบ”D1 ไม่รองรับ interactive transaction การอ่านแถวและ total ของ pagination จึงแยกกันและอาจไม่ตรงเมื่อมีการเขียนพร้อมกัน Raw Hono handler ต้องใช้ guard shape อย่างชัดเจนกับ Prisma query การมี extended client อย่างเดียวไม่ได้จำกัดทุก query และ raw SQL ไม่มี guard การตรวจรับ Worker ในเครื่องกับการตรวจรับดีพลอยเป็นคนละขั้น ซอร์สโค้ดและชุดทดสอบที่เขียนไว้เพียงอย่างเดียวไม่ได้พิสูจน์ว่าผ่านขั้นใดแล้ว
ขั้นตอนถัดไป
หัวข้อที่มีชื่อว่า “ขั้นตอนถัดไป”ไฟล์ แพ็กเกจ และการแปลงตารางที่อยู่เบื้องหลัง API นี้ ดูใน รายละเอียดการทำงาน
ยังไม่เปิดตัว (pre-MVP) และยังไม่มีเวอร์ชันเสถียร
คอมมิตซอร์สโค้ด: fe23db481c422b40e317b92fc98116b4c0c26a87 (มีการแก้ไขที่ยังไม่คอมมิต)
วันที่สร้างเอกสาร (UTC): 2026-10-18T09:22:34.630Z
คอมมิตสำหรับอัปเกรด: ระบุไม่ได้: มีการแก้ไขที่ยังไม่คอมมิต. บันทึกการเปลี่ยนแปลง