البيانات التي يملكها المداريّ، وأين تُخزَّن، ومن يحق له قراءتها أو تغييرها.
الكيان هو البيانات التي يملكها المداريّ. يعلن كل مداريّ كيانًا أساسيًا واحدًا بالضبط، وأي كتلة entity ثانية أو ثالثة تصبح كيانًا مساعدًا في المداريّ نفسه. ترتبط السمات بالكيان عبر ->، وتقرؤه وتكتبه عبر @entity.
1entity Invoice [persistent: invoices] {
2 id : string!
3 client : string!
4 amount : money = 0
5 status : "draft" | "sent" | "paid" = draft
6 dueOn : date
7 paidAmount : number = derived (if (= @entity.status "paid") @entity.amount 0)
8}! تعني حقلًا إلزاميًا، و? حقلًا يقبل القيمة الفارغة.= value تحدد قيمة افتراضية. القيم الافتراضية تعابير، و= now تسجّل وقت الإنشاء."a" | "b" | "c" مفردات مغلقة: تعيين أي قيمة أخرى خطأ تحقق.derived (…) يُحسب من حقول أخرى كلما تغيرت، ولا يُخزَّن أبدًا.الأنواع هي الأنواع الأولية المعتادة (string وnumber وint وboolean وdate وtimestamp) إضافة إلى أنواع مُدقَّقة تُعرض وتُتحقق بشكل أفضل: money وemail وurl وphone وfile وduration وicon.
الوسم بين القوسين يحدد مكان حفظ الصفوف:
| الوسم | المعنى |
|---|---|
[runtime] | في الذاكرة طوال عمر الصفحة. حالة العرض والعدّادات ومعالجات الخطوات. |
[persistent: invoices] | صفوف مخزّنة في المجموعة المسماة، تُقرأ بـ fetch وتُكتب بـ persist. |
[persistent: invoices, local] | صفوف مخزّنة تعيش في متصفح الزائر (IndexedDB). لا يحتاج التطبيق إلى خادم. |
[runtime, shared] | إطار واحد مشترك بين كل السمات المرتبطة بالكيان، داخل جلسة زائر واحد. |
[persistent: people, identity] | هذا الكيان هو شكل الزائر المسجّل، أي @user. |
قيمة @entity في السمة هي القيم الافتراضية للكيان، ثم الصف المخزّن المرتبطة به (إن وُجد)، ثم إطارها الخاص، وهو حالة العرض التي يكتبها (set @entity.x v). سمتان على الكيان نفسه غير المشترك لا ترى إحداهما كتابات الأخرى، فاستخدم shared حين ينبغي أن تتشاركا.
يمكن أن يبدأ الكيان المخزّن في المتصفح بصفوف. يكتبها instances صراحة، ويُتحقق من نوع كل صف مقابل الحقول:
NoteList
on Note · states: 2 · transitions: 4NoteForm
on Note · states: 1 · transitions: 2البرنامج أعلاه كآلات حالات، واحدة لكل سمة: كل مربع حالة، وكل سهم حدث ينقلها، والأيقونات على السهم هي التأثيرات التي يشغّلها ذلك الحدث. الرمز ◇ على السهم شرط حارس؛ مرّر المؤشر فوقه لتقرأ الشرط. كيف تقرأ AVL
1app std-orb-docex-notes "1.0.0"
2"std-orb-docex-notes — a stored list with a form: the list loads on mount, the form saves a new note, and a listens route reloads the list."
3orbital NotesOrbital {
4 entity Note [persistent: docex_notes, local] {
5 id : string!
6 title : string!
7 body : string = ""
8 instances [
9 { id: "N-1", title: "Read about the closed circuit", body: "Event, guard, transition, effects, UI." },
10 { id: "N-2", title: "Run orb validate", body: "Zero errors and zero warnings is the bar." }
11 ]
12 }
13
14 trait NoteList -> Note [interaction, collection] {
15 initial: loading
16
17 state loading {
18 INIT -> loading
19 (fetch Note { emit: { success: NotesLoaded, failure: NotesFailed } })
20 (render-ui main { type: skeleton, variant: list, rows: 2 })
21 NotesLoaded -> ready
22 (emit MOVED { event: "NotesLoaded" })
23 (render-ui main { type: data-list, entity: ?data, fields: [{ name: "title", label: "Title", variant: h4 }, { name: "body", label: "Note", variant: body }], emptyTitle: "No notes yet", emptyDescription: "Add one with the form.", emptyIcon: "file-text" })
24 NotesFailed -> ready
25 (emit MOVED { event: "NotesFailed" })
26 (render-ui main { type: alert, variant: error, message: ?error })
27 }
28
29 state ready {
30 RELOAD -> loading
31 (emit MOVED { event: "RELOAD" })
32 (fetch Note { emit: { success: NotesLoaded, failure: NotesFailed } })
33 }
34
35 emits {
36 NotesLoaded {
37 data : [Note]
38 }
39 NotesFailed {
40 error : string
41 }
42 MOVED -> external {
43 event : string!
44 }
45 @description "Each move names the event it took, so the diagram beside the demo can follow it."
46 }
47
48 listens {
49 NoteForm.NOTE_SAVED -> RELOAD
50 }
51 }
52
53 trait NoteForm -> Note [interaction, instance] {
54 initial: editing
55
56 state editing {
57 INIT -> editing
58 (render-ui main { type: form-section, fields: ["title", "body"], submitEvent: SAVE, mode: create, entity: {} })
59 SAVE -> editing
60 (emit MOVED { event: "SAVE" })
61 (persist create Note ?data)
62 (emit NOTE_SAVED)
63 (render-ui main { type: form-section, fields: ["title", "body"], submitEvent: SAVE, mode: create, entity: {} })
64 }
65
66 emits {
67 NOTE_SAVED
68 @description "A note was stored."
69 MOVED -> external {
70 event : string!
71 }
72 @description "Each move names the event it took, so the diagram beside the demo can follow it."
73 }
74
75 listens {
76 SAVE {
77 data : @entity!
78 }
79 }
80 }
81
82 page "/notes" -> NoteList, NoteForm
83}ما الذي يحدث عندما يقع الحدث SAVE:
(persist "create" "Note" @payload.data)(emit "NOTE_SAVED")كيف تتحادث السمات فيما بينها، بالترتيب:
تُعبَّأ الصفوف مرة واحدة في مخزن فارغ، ولا تُكتب فوق صفوف يملكها الزائر. البرنامج الذي يعلن locales [en, ar, sl] يستطيع تعبئة كل لغة على حدة بـ instances ar [ … ] وinstances sl [ … ]، راجع التوطين.
1(fetch Note { emit: { success: NotesLoaded, failure: NotesFailed } })
2(fetch Note { id: ?id })
3(fetch Note { filter: ["=", (object/get @entity status), "open"], limit: 20 })
4(persist create Note ?data)
5(persist update Note (object/merge ?row { done: true }))
6(persist delete Note ?id)يحمّل fetch الصفوف ويبلغ عبر الأحداث المذكورة في emit، ودونها لا شيء يلاحظ التحميل. يُعلَن حدثا النتيجة كلاهما في emits الخاصة بالسمة. داخل filter يعني (object/get @entity field) الصف قيد الاختبار، بينما يعني @entity.field المجرد إطار السمة نفسها.
تحديد نوع حقل بكيان آخر ينشئ علاقة: customerId : Customer (واحد) أو attendees : [Person] (متعدد). الأمر (fetch Order { include: ["customerId"] }) يملؤها. وتعني T.f نوع حقل من كيان آخر، فيكون tagId : Tag.id! نصًا معروفًا بأنه يحمل معرّف وسم.
سياسات الوصول تُكتب في أعلى جسم الكيان، وتُفرض مرة واحدة في طبقة البيانات لكل سمة مرتبطة به:
1entity Session [persistent: sessions] {
2 @read (or ["=", @user.role, "manager"] ["=", (object/get @entity trainerId), @user.id])
3 @update ["=", (object/get @entity trainerId), @user.id]
4 @delete none "sessions are archived, never deleted"
5 trainerId : Person
6 title : string!
7}@user هو الزائر، و@entity هو الصف المعني. غياب السياسة يعني أن الجميع يستطيع تنفيذ تلك العملية، وnone "reason" توثّق إغفالًا مقصودًا. السياسات تحمي البيانات، أما ما تعرضه الشاشة أو تخفيه فيُكتب صراحة في البرنامج بالشروط.
السمات هي آلات الحالات التي تقرأ الكيانات وتغيّرها.