แบ็กเอนด์อิสระ: เอกสารอ้างอิง 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
การอ่านข้อมูลจาก API ที่ดีพลอยแล้ว
หัวข้อที่มีชื่อว่า “การอ่านข้อมูลจาก API ที่ดีพลอยแล้ว”เลือก API ให้ตรงงาน
หัวข้อที่มีชื่อว่า “เลือก API ให้ตรงงาน”Dee Wan ใช้ prisma-generator-express สร้างแบ็กเอนด์อิสระ แต่ความสามารถทั้งหมดของตัวสร้างมีมากกว่าชุด route ที่โปรเจกต์ Dee Wan เปิดไว้ ทั้งสามแบบมีสัญญาต่างกัน:
| API | รูปแบบคำขอ | คำตอบ |
|---|---|---|
API ของ CMS ที่ทำงานอยู่ /v1/<model> |
filter[...], tag, relation, fields, expand, sort และเคอร์เซอร์ที่ต้องส่งคืนโดยไม่แก้ไข |
เนื้อหาที่เผยแพร่แล้วใน { contracts, data, page } |
/api/<model> ของแบ็กเอนด์อิสระ |
อาร์กิวเมนต์ Prisma โดยเข้ารหัสค่าที่มีโครงสร้างเป็น JSON และตรวจตามกฎ reader/editor ที่สร้างไว้ | แถวข้อมูล Prisma; /paginated คืน { data, total, hasMore } |
| Route ที่ผูกกับคำขอที่บันทึกไว้ | where, orderBy, take, skip ที่ผ่านการตรวจ โดยกำหนดฟิลด์ผลลัพธ์ method และ path ไว้ตายตัว |
ผลลัพธ์ Prisma ของ operation ที่ผูกไว้ |
คู่มือ API สาธารณะ มีตัวอย่างคำขอ CMS แบบครบถ้วน การส่ง where, include, distinct หรือ groupBy ของ Prisma ไปที่ /v1 ไม่ได้เปิดความสามารถเหล่านั้น โทเค็นไซต์ของ CMS ใช้แทนข้อมูลยืนยันตัวตนของแบ็กเอนด์อิสระไม่ได้
เรียก route มาตรฐานที่สร้างให้
หัวข้อที่มีชื่อว่า “เรียก route มาตรฐานที่สร้างให้”ผู้อ่านทั่วไปกรองแถวหลักด้วย id.equals ได้ ผู้แก้ไขกรองตามค่าที่เท่ากันของคอลัมน์ที่สร้างให้ได้ รายการหลักเรียงตาม id ค่าเริ่มต้นคือ 25 แถว สูงสุด 100 แถว รายการคำแปลเรียงตาม lang แล้วตาม id ตารางเชื่อมเรียงตาม position แล้วตาม id ค่าเริ่มต้น 100 แถว สูงสุด 200 แถว การแบ่งหน้าไปข้างหน้าใช้ take ที่เป็นบวกและ skip ที่ไม่ติดลบ
เลือกคอลัมน์ที่สร้างให้ได้ และสำหรับโมเดลที่มีคำแปล เลือกฟิลด์ซ้อนใน translations.select ได้ ผู้อ่านไม่ได้รับคอลัมน์ส่วนตัว คำแปลที่แนบมากับแถวหลักมีค่าเริ่มต้น 25 รายการ สูงสุด 50 รายการ หากต้องการมากกว่านั้น ให้เรียก route แบบแบ่งหน้าของตารางคำแปลโดยตรง การที่ Prisma รองรับความสัมพันธ์ไม่ได้หมายความว่า API จะยอมให้ขยายความสัมพันธ์อื่นได้อย่างอิสระ
ตัวอย่างนี้สมมติว่ามีโมเดล Article พร้อมฟิลด์แปล title และ body ตรวจชื่อจริงจากสคีมา Prisma และ OpenAPI ที่สร้างให้ เพราะคีย์ฟิลด์ใน CMS อาจไม่ตรงกับชื่อคอลัมน์ โทเค็นต้องอ่าน Article ได้ตามนโยบายของระบบที่ดีพลอยแล้ว
const args = { where: { id: { equals: 'article-1' } }, select: { id: true, translations: { select: { id: true, lang: true, title: true, body: true }, take: 10, }, }, orderBy: { id: 'asc' }, take: 25, skip: 0,};const url = new URL('/api/article/paginated', apiOrigin);for (const [key, value] of Object.entries(args)) { url.searchParams.set(key, typeof value === 'object' ? JSON.stringify(value) : String(value));}const response = await fetch(url, { headers: { Authorization: `Bearer ${serviceToken}` },});if (!response.ok) throw new Error(`API refused the request: ${response.status}`);const { data, total, hasMore } = await response.json();ใช้ curl --get --data-urlencode เพื่อเข้ารหัสวงเล็บ ช่องว่าง และเครื่องหมายคำพูด:
curl --get 'https://api.example.com/api/article/paginated' \ -H "Authorization: Bearer $ARTIFACT_API_TOKEN" \ --data-urlencode 'where={"id":{"equals":"article-1"}}' \ --data-urlencode 'orderBy={"id":"asc"}' \ --data-urlencode 'take=25' \ --data-urlencode 'skip=0'ขณะที่ hasMore เป็น true ให้เพิ่ม skip ตามขนาดหน้าที่ขอ โดยคงตัวกรองและลำดับเดิม /api/article คืนอาร์เรย์โดยไม่มีโครงสร้างห่อแบบ CMS ส่วน /unique คืนหนึ่งแถวหรือ null ค่า total ในคำตอบแบบแบ่งหน้านับทุกแถวที่ตรงเงื่อนไข ไม่ใช่แค่หน้านี้ ใน D1 การอ่านแถวและจำนวนรวมเป็นคนละคำขอ จึงอาจไม่ตรงกันเมื่อมีการเขียนพร้อมกัน การแบ่งหน้าด้วย offset ไม่ใช่สแนปช็อตที่คงที่ตลอดการอ่าน
ระบบเลือกกฎ reader/editor จากตัวตนที่ตรวจสอบแล้ว อย่าส่ง header เพื่อเลือก variant เอง ไม่ว่าจะเป็นการเข้าถึงสาธารณะ เซสชันแอป หรือโทเค็นบริการ operation ต้องเปิดใช้งานและคำขอต้องผ่านกฎเสมอ ดูส่วนการยืนยันตัวตน กฎตรวจคำขอ และข้อผิดพลาด
ใช้คำขอที่บันทึกไว้สำหรับการอ่านที่ซับซ้อนขึ้น
หัวข้อที่มีชื่อว่า “ใช้คำขอที่บันทึกไว้สำหรับการอ่านที่ซับซ้อนขึ้น”บันทึกคำขอที่แอปต้องใช้ในพื้นที่คำขอของโมเดล ผูกกับ method/path ที่เชื่อถือได้ แล้วสร้างและดีพลอยใหม่ findMany ที่บันทึกไว้ระบุ operator ของฟิลด์ เงื่อนไขเชิงตรรกะและความสัมพันธ์ การเลือกฟิลด์ซ้อน ลำดับ และขอบเขตการแบ่งหน้าได้ ความลึกของความสัมพันธ์สูงสุด 2 ระดับ และ take ต้องเป็นบวกไม่เกิน 100 ค่าที่รับไว้จะเป็นทั้งค่าเริ่มต้นและค่าสูงสุด
Operator ขึ้นกับชนิดฟิลด์ ข้อความรองรับความเท่ากัน การอยู่ในรายการ การเปรียบเทียบ contains, startsWith, endsWith; ตัวเลขและวันที่รองรับการเปรียบเทียบ; Boolean รองรับความเท่ากัน; enum รองรับการอยู่ในรายการ ความสัมพันธ์รองรับ some/every/none หรือ is/isNot และใช้ AND/OR/NOT จัดกลุ่มเงื่อนไขได้ ตัวกรอง scalar list ต้องมีสคีมาและ database connector ที่รองรับ ฟิลด์ JSON และไบนารีไม่ใช่อินพุตตัวกรองทั่วไป ตัวแปลงปฏิเสธโครงสร้างที่ไม่รองรับหรือกำกวม เช่น ไม่รวมสมาชิกอาร์เรย์ที่มีโครงสร้างต่างกันให้กลายเป็นสิทธิ์ที่กว้างขึ้น SQLite/D1 ไม่รับ mode: "insensitive" ของ Prisma แบบ PostgreSQL
แต่ละ route ผูกกับคำขอเดียว ผู้เรียกเปลี่ยนได้เฉพาะโครงสร้างตัวกรอง ลำดับ และการแบ่งหน้าที่อนุญาต ขอฟิลด์ชุดใหม่ด้วย select/include เพิ่ม operator เลือกคำขออื่นด้วยชื่อ หรือเกินขอบเขตไม่ได้ Route ของ findUnique รับ selector ที่ประกาศไว้จาก path ค่าตัวอย่างในคำขอไม่ใช่ข้อจำกัดผู้เช่าหรือเจ้าของข้อมูล ข้อจำกัดเหล่านั้นต้องมาจากบริบทเซิร์ฟเวอร์ที่เชื่อถือได้ ดู รูปแบบคำขอและกฎที่สร้างให้
ตัวอย่างเช่น บันทึก Post.findMany ที่มี where: { title: { contains: "Fir" } }, orderBy: { title: "asc" }, select: { id: true, title: true }, take: 2 แล้วผูก GET /api/posts/recent คำขอต่อไปนี้เปลี่ยนค่าตัวกรองและทิศทางที่อนุญาต ลดขนาดหน้า และยังได้รับอาร์เรย์ที่มีเฉพาะ id กับ title:
curl --get 'https://api.example.com/api/posts/recent' \ -H "Authorization: Bearer $ARTIFACT_API_TOKEN" \ --data-urlencode 'where={"title":{"contains":"Sec"}}' \ --data-urlencode 'orderBy={"title":"desc"}' \ --data-urlencode 'take=1'take=3, select จากผู้เรียก หรือเงื่อนไขใหม่ที่ยังไม่ผ่านการตรวจ ไม่ตรงกับสัญญาของ route นี้ ข้อกำหนดว่าให้อ่านเฉพาะรายการเผยแพร่แล้วหรือเฉพาะผู้เช่าของตนต้องเป็นข้อจำกัดที่เชื่อถือได้ ไม่ใช่เพียงค่าตัวอย่างในตัวกรอง
ความสามารถทั้งหมดของตัวสร้างสำหรับแอปที่ดูแลเอง
หัวข้อที่มีชื่อว่า “ความสามารถทั้งหมดของตัวสร้างสำหรับแอปที่ดูแลเอง”ต่อไปนี้เป็นความสามารถของไลบรารี prisma-generator-express ไม่ใช่รายการ route มาตรฐานของ Dee Wan ใช้ได้เมื่อแอปกำหนด operation การยืนยันตัวตน สิทธิ์ และรูปแบบ guard ที่เข้ากันไว้อย่างชัดเจน ในโปรเจกต์ Dee Wan ที่ยังเชื่อมกับ CMS ให้ใช้จุดขยายที่ระบุไว้ อย่าแก้ไฟล์ที่สร้างให้อัตโนมัติหรือปิดกฎบังคับ ความสามารถของฐานข้อมูลและ adapter ยังมีผล ดู คู่มือไลบรารี และ OpenAPI ของโปรเจกต์เพื่อทราบสิ่งที่เปิดให้เรียกจริง
| อาร์กิวเมนต์หรือ operation ของ Prisma | ใช้ทำอะไรและมีขอบเขตอย่างไร |
|---|---|
where |
รวม AND, OR, NOT และเงื่อนไข scalar; ใช้ some, every, none กับความสัมพันธ์หลายรายการ และ is, isNot กับความสัมพันธ์หนึ่งรายการ ทุกฟิลด์และ operator ต้องได้รับอนุญาต |
select |
คืนเฉพาะฟิลด์ scalar และความสัมพันธ์ซ้อนที่เลือกไว้ รวม ID หรือ foreign key เมื่อแอปต้องใช้สร้างลิงก์หรือเชื่อมข้อมูล |
include |
แนบความสัมพันธ์เข้ากับผลลัพธ์โมเดล ห้ามใช้ select และ include ที่ระดับเดียวกัน แต่กำหนดชุดฟิลด์ภายในความสัมพันธ์ได้ |
omit |
ตัดฟิลด์ที่ระบุ เมื่อเวอร์ชัน Prisma, connector และกฎ route รองรับ ไม่สามารถข้ามข้อจำกัดฟิลด์ของเซิร์ฟเวอร์ได้ |
_count ของความสัมพันธ์ |
ขอจำนวนความสัมพันธ์ที่เลือกด้วย _count.select เมื่อ operation และ guard อนุญาต ต่างจากการเรียก root endpoint count แยกต่างหาก |
| อาร์กิวเมนต์ความสัมพันธ์ซ้อน | กรอง เรียง และแบ่งหน้ารายการลูกแยกกัน where ภายในจำกัดลูกที่คืนมา หากต้องการตัดแถวแม่ ต้องมีเงื่อนไขความสัมพันธ์ใน where ระดับหลัก |
orderBy |
ออบเจ็กต์เดียวหรืออาร์เรย์สำหรับหลายคีย์ เพิ่มคีย์ที่ไม่ซ้ำท้ายสุดเพื่อให้การแบ่งหน้ามีลำดับแน่นอน ต้องเป็นฟิลด์หรือการเรียงความสัมพันธ์ที่สคีมารองรับ |
take, skip |
จำกัดขนาดผลลัพธ์และกำหนด offset ภายใต้ขอบเขตของ route และ guard ค่า take ติดลบใช้ย้อนหน้าได้เฉพาะเมื่อเปิดรองรับ |
cursor |
Selector ที่ไม่ซ้ำ มักใช้กับ skip: 1 เพื่อไม่คืนแถวเคอร์เซอร์ซ้ำ เป็น selector ของ Prisma ไม่ใช่เคอร์เซอร์ทึบของ CMS /v1 |
distinct |
ค่าที่ไม่ซ้ำของฟิลด์ scalar ที่ระบุ ไม่ได้เปลี่ยนฟิลด์อื่นทั้งหมดในผลลัพธ์ให้เป็น SQL GROUP BY |
count |
จำนวนแถวที่ตรงเงื่อนไข หรือการนับตามฟิลด์ที่ตั้งไว้ ไม่ใช่การคืนแถวหรือกลุ่มข้อมูล |
aggregate |
_count, _sum, _avg, _min, _max ตามชนิดฟิลด์ที่โมเดลรองรับ |
groupBy |
ฟิลด์ by พร้อม aggregate และตัวเลือก having, ลำดับ, การแบ่งหน้า where กรองแถวก่อนจัดกลุ่ม ส่วน having กรองกลุ่ม ต้องเป็นไปตามกฎของ Prisma |
findFirst, findUnique และแบบ strict |
อ่านแถวแรกที่ตรงเงื่อนไขหรืออ่านด้วย selector ที่ไม่ซ้ำ แบบ strict เกิดข้อผิดพลาดเมื่อไม่พบแถว แบบปกติอาจคืน null |
mode: "insensitive", operator ของ scalar list และ aggregate บางชนิดขึ้นกับ connector คำขอ PostgreSQL จึงนำไปใช้กับ SQLite/D1 ไม่ได้เพียงเพราะใช้ Prisma เหมือนกัน แบ็กเอนด์มาตรฐานที่ Dee Wan สร้างไม่เปิด count, aggregate, groupBy, cursor, distinct หรือ POST สำหรับการอ่าน
ตัวอย่างคำขอซ้อนหลายส่วน
หัวข้อที่มีชื่อว่า “ตัวอย่างคำขอซ้อนหลายส่วน”ตัวอย่างเพื่อการอธิบายนี้สมมติว่าเป็นแอป PostgreSQL ที่ตั้งค่าแยกเอง มีโมเดล Article, Author, Category, Comment บทความมี author หนึ่งรายการและรายการ categories, comments; ความคิดเห็นมี author กฎของ route ที่ผ่านการตรวจต้องอนุญาตคำขอนี้อย่างชัดเจน ชื่อเหล่านี้เป็นตัวอย่าง ไม่ได้อ้างถึงไซต์ Dee Wan ที่กำลังทำงานอยู่
const query = { where: { AND: [ { published: { equals: true } }, { publishedAt: { gte: '2026-01-01T00:00:00.000Z' } }, { categories: { some: { slug: { equals: 'architecture' } } } }, { author: { is: { active: { equals: true } } } }, ], OR: [ { title: { contains: 'design', mode: 'insensitive' } }, { title: { contains: 'city', mode: 'insensitive' } }, ], }, select: { id: true, title: true, publishedAt: true, author: { select: { id: true, name: true } }, categories: { select: { id: true, slug: true }, orderBy: { slug: 'asc' }, take: 5 }, comments: { where: { approved: { equals: true } }, orderBy: [{ createdAt: 'desc' }, { id: 'desc' }], take: 3, select: { id: true, body: true, author: { select: { id: true, name: true } }, }, }, }, orderBy: [{ publishedAt: 'desc' }, { id: 'desc' }], take: 10, skip: 0,};ตัวกรองหลักกำหนดให้มีหมวด architecture และผู้เขียนที่ยัง active ส่วนความคิดเห็นจำกัดแยกเป็นสามรายการล่าสุดที่อนุมัติแล้ว บทความที่ไม่มีความคิดเห็นดังกล่าวยังตรงเงื่อนไขได้ แต่ละบทความจะมีผู้เขียน หมวด และความคิดเห็นซ้อนตามชุดฟิลด์ที่ขอ กฎการเผยแพร่และการแยกผู้เช่าจากเซิร์ฟเวอร์ที่เชื่อถือได้ต้องยังบังคับใช้ แม้ผู้เรียกจะเปลี่ยนค่าตัวกรองที่อนุญาต
สำหรับ route ของไลบรารีที่ตั้งค่าไว้และใช้ prefix โมเดลมาตรฐาน ให้เข้ารหัสอาร์กิวเมนต์ระดับบนที่มีโครงสร้างเป็น JSON:
const url = new URL('/article/', apiOrigin);for (const [key, value] of Object.entries(query)) { url.searchParams.set(key, JSON.stringify(value));}const response = await fetch(url, { headers: authorizationHeaders });ไลบรารีรองรับการอ่านด้วย JSON body เมื่อเปิดใช้: POST /article/read สำหรับ findMany หรือ POST /article/paginated สำหรับการอ่านแบบแบ่งหน้า วิธีนี้หลีกเลี่ยง URL ที่ยาวเกินไป แต่ไม่เปลี่ยนสิทธิ์ guard หรือต้นทุนคำขอ โปรเจกต์มาตรฐานของ Dee Wan ปิดทางเลือกเหล่านี้ไว้ อย่าใส่โทเค็นลับใน URL
จำนวนรวม การคำนวณรวม และการจัดกลุ่ม
หัวข้อที่มีชื่อว่า “จำนวนรวม การคำนวณรวม และการจัดกลุ่ม”เป็น endpoint แยกของไลบรารีที่ต้องเปิดใช้อย่างชัดเจน ตัวอย่างต่อไปนี้เป็น body ของ POST read ที่เปิดไว้ โดยใช้สคีมาตัวอย่างเดียวกัน หากใช้ GET ให้ส่งอาร์กิวเมนต์แบบ JSON ใน query parameter
POST /article/count{"where":{"published":{"equals":true}}}
POST /article/aggregate{"where":{"published":{"equals":true}},"_count":{"_all":true},"_max":{"publishedAt":true}}
POST /article/groupby{"by":["authorId"],"where":{"published":{"equals":true}},"_count":{"_all":true},"orderBy":{"authorId":"asc"},"take":20}count คืนผลการนับของ Prisma, aggregate คืนออบเจ็กต์ aggregate, groupBy คืนอาร์เรย์ของกลุ่ม โดยไม่เพิ่ม contracts/page ของ CMS เฉพาะ findManyPaginated ที่เพิ่ม { data, total, hasMore } ค่า hasMore นี้ออกแบบสำหรับการแบ่งหน้าไปข้างหน้าด้วย offset และ take ที่เป็นบวก ไม่ใช่การนำทางแบบ cursor หรือย้อนหน้าทั่วไป
เมื่อใช้ distinct แบบแบ่งหน้า ไลบรารีอาจค้นหาค่าที่ไม่ซ้ำภายใต้ขอบเขตที่ตั้งไว้ หรือถอยไปนับแถวแบบไม่ใช้ distinct การอ่าน distinct ที่ผ่าน guard ก็ใช้การนับแบบไม่ใช้ distinct ภายใต้ guard เช่นกัน ดังนั้น total อาจมากกว่าจำนวนผลลัพธ์ที่ไม่ซ้ำ ตรวจ distinctCountLimit และการตั้งค่าแหล่งข้อมูลการนับของ route ก่อนถือว่าทุกยอดรวมแม่นยำ ตัวเลือก findManyPaginatedMode กำหนดตอนสร้างว่าจะอ่านแยกพร้อมกันหรือใช้ interactive transaction ซึ่ง D1 ไม่รองรับ
การส่งผลลัพธ์เป็นระยะผ่าน SSE มีเฉพาะ target Express ของไลบรารี ไม่ใช่โหมดสตรีมของ Hono/D1 API ที่ Dee Wan สร้าง การมีฟีเจอร์ใน README ของตัวสร้างไม่ได้ยืนยันว่า route นั้นดีพลอย เปิดใช้ หรือผ่านการตรวจรับในโปรเจกต์ของคุณแล้ว
ก่อนใช้ในระบบจริง
หัวข้อที่มีชื่อว่า “ก่อนใช้ในระบบจริง”- ตรวจ OpenAPI ของระบบที่ดีพลอย สคีมา operation ที่เปิด และกฎคำขอที่มีผลจริง
- ขอเฉพาะฟิลด์ที่ต้องใช้ และจำกัดจำนวนแต่ละความสัมพันธ์แบบหลายรายการแยกกัน
- ใช้ลำดับที่แน่นอน และคงข้อมูลระบุคำขอทั้งหมดเมื่อเปลี่ยนหน้าหรือสร้างคีย์แคช
- ตรวจ HTTP status ก่อนอ่านผลลัพธ์สำเร็จ และคงข้อผิดพลาดที่เซิร์ฟเวอร์ปรับรูปแบบไว้
- เก็บโทเค็นบริการบนเซิร์ฟเวอร์ ในเบราว์เซอร์ใช้เซสชันแอปที่ตั้งค่าไว้
- หลังแก้คำขอ สคีมา หรือนโยบายสิทธิ์ ให้สร้างและดีพลอยใหม่ ระบบเดิมยังใช้สัญญาเดิมจนกว่าจะถูกแทนที่
การเขียนความสัมพันธ์และคำแปล
หัวข้อที่มีชื่อว่า “การเขียนความสัมพันธ์และคำแปล”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 หน่วยเมตร
ใช้ filter[location][within_polygon] กับ GeoJSON Polygon ที่เข้ารหัส URL เช่น {"type":"Polygon","coordinates":[[[100,13],[101,13],[101,14],[100,14],[100,13]]]} พิกัดของรูปหลายเหลี่ยมเรียงเป็นลองจิจูด ละติจูด รวมขอบนอก แต่ไม่รวมพื้นที่ช่องว่างภายในและขอบของช่องว่าง รองรับรูปหลายเหลี่ยมเว้า จำกัดวงแหวนปิด 16 วง รวมไม่เกิน 256 คู่พิกัดโดยนับจุดปิดด้วย และข้อมูลขาเข้าไม่เกิน 16 KiB ระบบปฏิเสธวงแหวนที่ไม่มีพื้นที่หรือตัดตัวเอง รวมถึงช่องว่างที่ตัดกันหรือไม่ได้อยู่ภายในขอบนอกอย่างสมบูรณ์ ขอบเป็นเส้นตรงบนระนาบลองจิจูด/ละติจูด ไม่ข้ามเส้นเมริเดียน 180 องศาให้อัตโนมัติ ไม่รับ MultiPolygon ดู คิวรีเชิงพื้นที่.
ตัวเลือกเหล่านี้ผ่าน 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 ให้เรียกแบบไม่ยืนยันตัวตน
การยืนยันตัวตนและสิทธิ์ของอาร์ติแฟกต์แบบเลือกเปิด
หัวข้อที่มีชื่อว่า “การยืนยันตัวตนและสิทธิ์ของอาร์ติแฟกต์แบบเลือกเปิด”ที่ การตั้งค่า → ซอร์สโค้ดและการเผยแพร่ → ตัวเลือกสำหรับนักพัฒนา → แบ็กเอนด์ที่สร้างขึ้น เปิด 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 คีย์ระบบเชื่อมต่อที่มีสิทธิ์ หรือคีย์อ่านโมเดลนั้น |
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 คงค่าปัจจุบัน ในหน้าผู้ดูแลคือสวิตช์ “การจัดการบัญชีในแอปพลิเคชันที่สร้างขึ้น” ที่ การตั้งค่า → ซอร์สโค้ดและการเผยแพร่ → ตัวเลือกสำหรับนักพัฒนา → แบ็กเอนด์ที่สร้างขึ้น และจะปิดใช้งานขณะ 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?, data? } |
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 ที่มีฟิลด์ซึ่งไม่ได้ประกาศไว้ หรือไม่มีฟิลด์เลย |
| 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 แล้ว (การตั้งค่า → ซอร์สโค้ดและการเผยแพร่ → ตัวเลือกสำหรับนักพัฒนา → แบ็กเอนด์ที่สร้างขึ้น) ลิงก์ “เปิดการจัดการบัญชี” ใต้สวิตช์จะเปิด https://<hostname>/admin/accounts ในแท็บใหม่ ก่อนมี hostname หน้าผู้ดูแลจะแสดง “ปรับใช้แอปพลิเคชันเพื่อบันทึกที่อยู่ของแอป” CMS ไม่เคย proxy cookie ของแอปพลิเคชัน: หน้าที่ CMS ให้บริการเรียกการดำเนินการเหล่านี้ไม่ได้ เพราะ cookie เซสชันของอาร์ติแฟกต์เป็นของ origin ของอาร์ติแฟกต์และรายการ origin ที่เชื่อถือมีเพียง origin นั้น ใช้ curl หรือไคลเอนต์ของคุณเองบน origin ของอาร์ติแฟกต์ได้เช่นกัน
คีย์สำหรับระบบเชื่อมต่อ
หัวข้อที่มีชื่อว่า “คีย์สำหรับระบบเชื่อมต่อ”เมื่อเปิดการจัดการบัญชีแอปแล้ว ให้ไปที่ /admin/accounts → Machine credentials เลือก
New credential ตั้งชื่อระบบเชื่อมต่อ เลือกการดำเนินการของแต่ละโมเดล และกำหนดวันหมดอายุถ้าต้องการ
แอปแสดงคีย์สุ่มที่ขึ้นต้นด้วย dwk_ เพียงครั้งเดียว ให้เก็บก่อนออกจากหน้า แล้วส่งผ่าน
Authorization: Bearer <secret> ปุ่ม Rotate เปลี่ยนคีย์นั้นทันที ส่วน Revoke เพิกถอนทันที
โดยไม่กระทบระบบอื่น คีย์ที่หมดอายุหรือถูกเพิกถอนแล้วเปลี่ยนผ่าน Rotate ไม่ได้ ต้องออกคีย์ใหม่
แอปที่สร้างขึ้นเป็นเจ้าของคีย์และเก็บเฉพาะแฮชไว้ในฐานข้อมูล ทุกคำขอตรวจสถานะการเพิกถอนและวันหมดอายุ
แม้แยกแอปออกจาก CMS แล้วหรือ CMS ออฟไลน์ สิทธิ์ระบุ operation ID ที่แน่นอน เช่น GradArticle.create
หรือ GradArticle.update ไม่รับ * นโยบายการเข้าถึง การเปิดใช้ operation ข้อจำกัดแถวและฟิลด์
และโค้ดที่คุณเพิ่มเองยังมีผล คีย์บริการเข้า route ที่ต้องใช้เซสชันผู้ใช้ไม่ได้ การแก้ไขคำแปลใช้สิทธิ์
update/delete ของโมเดลหลัก ส่วน API_BEARER_TOKEN และคีย์อ่านอย่างเดียวใน ACCESS_CONFIG ยังใช้ได้
การจัดการคีย์ต้องใช้เซสชันผู้ดูแลของแอป และคำขอที่เปลี่ยนข้อมูลต้องมาจาก origin ของแอป คีย์ระบบเชื่อมต่อใช้สร้างหรือจัดการคีย์อื่นไม่ได้
| เมธอดและเส้นทาง | คำขอ | ผลลัพธ์ |
|---|---|---|
GET /api/auth/machine/operations |
— | { operations: [{ id, model, operation }] } ของ operation ที่เลือกได้ |
GET /api/auth/machine/list?offset=0 |
offset ที่ไม่ติดลบ | { credentials, total, offset, limit: 50 } ไม่มีคีย์หรือแฮช |
POST /api/auth/machine/create |
{ name, permissions, expiresAt? }; วันหมดอายุเป็น Unix timestamp ในอนาคต หน่วยมิลลิวินาที หรือ null |
201 { credential, token } |
POST /api/auth/machine/rotate |
{ id } |
{ credential, token } แสดงคีย์ครั้งเดียว |
POST /api/auth/machine/revoke |
{ id } |
{ id, revoked: true } |
operation ที่ไม่รู้จัก สิทธิ์ไม่ถูกต้อง และวันหมดอายุในอดีตจะถูกปฏิเสธ หากเปลี่ยนคีย์เดียวกันพร้อมกัน
หรือพยายามเปลี่ยนคีย์ที่ไม่ทำงานแล้ว ระบบตอบ 409 ให้โหลดรายการใหม่ก่อนลองอีกครั้ง
ใช้เซสชันและการตรวจผู้ดูแลของ Better Auth เวอร์ชันที่ตรึงไว้ ไม่ต้องเพิ่มปลั๊กอิน API key ที่ยังไม่ได้ติดตั้ง
ระบบเพิ่ม migrations/<number>_artifact_machine.sql ต่อจากประวัติ migration เดิม โดยไม่แก้ไฟล์การยืนยันตัวตนและการจัดการบัญชีที่ใช้ไปแล้ว การสร้างแอปใหม่ใช้ชื่อ migration ของคีย์ที่บันทึกไว้เดิม
ฟิลด์โปรไฟล์เพิ่มเติม
หัวข้อที่มีชื่อว่า “ฟิลด์โปรไฟล์เพิ่มเติม”กำหนดฟิลด์บัญชีของแอปในไฟล์ src/owned/profile-fields.json ที่คุณเป็นเจ้าของภายในแอปที่สร้างขึ้น
ไฟล์เริ่มต้นว่างเปล่า และการสร้างแอปใหม่จะเก็บไว้:
{ "customerId": { "type": "string", "editable": "admin", "required": true, "default": "unassigned", "maxLength": 80 }, "locale": { "type": "string", "editable": "user", "default": "en", "options": ["en", "th"] }, "subscribed": { "type": "boolean", "editable": "user", "default": false }}รองรับชนิด string, number, boolean และ date ซึ่งเป็นวันที่ปฏิทิน YYYY-MM-DD
แต่ละฟิลด์ต้องระบุ editable: "user" หรือ "admin" ฟิลด์แบบ admin ไม่รับค่าจากการสมัครหรือการแก้ไข
โปรไฟล์ด้วยตนเอง แม้ส่งค่าว่างหรือ false ผู้ดูแลแอปแก้ไขได้ทั้งสองแบบ ตัวเลือกเพิ่มเติมคือ label,
required, default, ค่า min/max สำหรับตัวเลข และ maxLength/options สำหรับข้อความ
กำหนดได้สูงสุด 32 ฟิลด์ และใช้ชื่อฟิลด์ระบบด้านตัวตน บทบาท หรือเซสชันซ้ำไม่ได้ ชื่อต้องไม่ซ้ำกันเมื่อไม่แยกตัวพิมพ์ใหญ่และเล็กตามกฎของ SQLite การเปลี่ยนเฉพาะตัวพิมพ์ของชื่อฟิลด์เดิมหรือคอลัมน์ที่ยังเก็บไว้จะถูกปฏิเสธ
หลังแก้ไข ให้รัน npm run auth:profile เพื่อตรวจการตั้งค่า สร้าง migration ของแอปสำหรับคอลัมน์ใหม่
และเขียนชนิดข้อมูลใน src/generated/profile-types.ts ซึ่งส่งออก ApplicationProfile ผ่าน
src/profile-fields.ts ด้วย ฟิลด์บังคับใหม่ต้องมีค่าเริ่มต้นที่ถูกต้องเพื่อเติมบัญชีเดิม
npm run auth:profile ยังบันทึกสถานะสคีมาใน src/generated/profile-schema.json ให้คอมมิตไฟล์สถานะ
ชนิดข้อมูล และ migration พร้อมกัน ส่วน npm run typecheck และ npm run db:init ตรวจว่าการเตรียม
เสร็จแล้วโดยไม่สร้าง migration ต้องนำ migration ที่คอมมิตไว้ไปใช้กับฐานข้อมูลปลายทางก่อนเริ่มแอป
ที่บิลด์ใหม่ โดยไม่เปลี่ยนสคีมาฐานข้อมูล CMS
หน้าบัญชีแสดงฟิลด์ใน New account และ Profile ผู้ดูแลส่งค่าที่ประกาศไว้ผ่าน data ของ
create-user และแก้ไขด้วย update-user ร่วมกับ name, image, email ได้ การอ่านบัญชีคืนฟิลด์
ที่ประกาศไว้ ผู้ใช้ส่งฟิลด์แบบ user ไปที่ /api/auth/sign-up/email และ /api/auth/update-user ได้
ฟิลด์ที่ไม่ได้ประกาศ ค่าไม่ถูกต้อง และการแก้ไขที่ไม่ได้รับอนุญาตจะถูกปฏิเสธ
การเตรียมซ้ำไม่สร้าง migration ซ้ำ การนำฟิลด์ออกจากไฟล์จะซ่อนฟิลด์แต่เก็บข้อมูลเดิม การเพิ่มกลับด้วย
สคีมาเดิมใช้ค่าเดิมได้ การเปลี่ยนชนิดหรือสถานะบังคับต้องทำ migration ของแอปเองและแก้บันทึก
src/generated/profile-schema.json ให้ตรงกัน ระบบเตรียมอัตโนมัติจะปฏิเสธจนกว่าจะเปลี่ยนอย่างชัดเจน
การเปลี่ยน default มีผลกับบัญชีที่สร้างใหม่ ไม่เปลี่ยนค่าเดิม
หากการเตรียมหยุดกลางทาง ให้รัน npm run auth:profile อีกครั้ง บันทึกงานที่ค้างจะกู้คืน migration เดิมก่อน
แล้วจึงเตรียมการเปลี่ยน declaration ใหม่ โหมด --check จะปฏิเสธเมื่อมีงานค้างโดยไม่เปลี่ยนไฟล์
การสร้างใหม่เก็บบันทึกนี้ไว้ อย่าลบเพื่อข้ามขั้นตอนเตรียม
บทบาทในแอปพลิเคชัน
หัวข้อที่มีชื่อว่า “บทบาทในแอปพลิเคชัน”ใน การตั้งค่า → ซอร์สโค้ดและการเผยแพร่ เปิดการเข้าสู่ระบบแอปพลิเคชันและการดูแลบัญชี
ส่วน บทบาทในแอปพลิเคชัน ใช้เพิ่มบทบาท เช่น 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 --complete โดยถือสิทธิ์เขียนแบบเฉพาะตลอดการสร้างซอร์ส รัน prisma:generate ปรับไคลเอนต์ และตรวจส่วนขยาย สมุดบันทึกการกู้คืนครอบคลุมทุกขั้นตอน อาร์ติแฟกต์ทั้งไซต์ต้องใช้ข้อมูลเข้าสองอย่าง:
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) และยังไม่มีเวอร์ชันเสถียร
คอมมิตซอร์สโค้ด: e207e63239a627fbd2ae9c414ef7c6eb19fcfe5a (มีการแก้ไขที่ยังไม่คอมมิต)
วันที่สร้างเอกสาร (UTC): 2026-10-21T09:28:24.214Z
คอมมิตสำหรับอัปเกรด: ระบุไม่ได้: มีการแก้ไขที่ยังไม่คอมมิต. บันทึกการเปลี่ยนแปลง