تخطي إلى المحتوى
Architecture Decision Record

إدارة البيانات العامة

نمط composable مركزي يوفر وصولاً موحداً لبيانات الجلسة في أي مكان بالتطبيق — بدون نسخ البيانات أو مزامنة يدوية.

نظرة عامة

المبدأ الأساسي

useGlobal يقرأ مباشرة من useAuth().data عبر computed — بدون نسخ البيانات أو sync يدوي. لو البيانات موجودة أصلاً في مصدر reactive — لا تنسخها.

composables/core/useGlobal.js

مسار تدفق البيانات

كيف تنتقل البيانات من تسجيل الدخول إلى أي مكان في التطبيق
تسجيل الدخول
POST /api/auth/login → يرجع access_token
useLogin → signIn(credentials) → nuxt-auth يحفظ الـ token في cookie
Session Fetch
GET /api/auth/profile → GET /global (External API)
nuxt-auth → getSession() → النتيجة تتخزن في useAuth().data (reactive ref)
useGlobal() Composable
يقرأ مباشرة من useAuth().data عبر computed
لا نسخ — لا sync — لا ازدواجية. يصدّر: user, lists, employees, findInList, refreshGlobal, session
المستهلكون
Components, Composables, Render Functions
Navbar, GlobalSelect, form.js, schema.js, columns.js — كلها تقرأ من نفس المصدر

هيكل بيانات الجلسة

البيانات التي يرجعها الـ API بعد تسجيل الدخول
Session Data StructureJSON
{
  loginName: "ahmed",
  name: "أحمد محمد",
  privateCompanyMode: true,
  list: {
    employees: [
      { id: 1, name: "أحمد" },
      { id: 2, name: "محمد" }
    ],
    departments: [
      { id: 1, name: "IT" }
    ],
    categories: [...],
    // ... قوائم أخرى
  }
}
1
POST /api/auth/login → يرجع access_token
2
GET /api/auth/profile → server proxy يضرب GET /global بالـ token
3
النتيجة تتخزن في useAuth().data

مرجع الـ API

كل الخصائص والدوال التي يصدّرها useGlobal
استيراد واستخدامjs
const { user, lists, employees, findInList, refreshGlobal, session } = useGlobal()
userComputedRef

بيانات المستخدم الحالي — loginName و name.

const { user } = useGlobal()

// في template
{{ user.loginName }}  // → "ahmed"
{{ user.name }}       // → "أحمد محمد"
listsComputedRef

كل الـ lookup lists التي يرجعها الـ API — employees, departments, categories, إلخ.

const { lists } = useGlobal()

const departments = computed(
  () => lists.value.departments || []
)
employeesComputedRef

اختصار مباشر لـ lists.value.employees — لأن قائمة الموظفين هي الأكثر استخداماً.

const { employees } = useGlobal()

// بدلاً من:
const employees = computed(
  () => lists.value.employees || []
)
findInList(list, id, idKey?)Function

يبحث عن عنصر بالـ ID في أي قائمة. يقبل idKey اختياري للبحث بحقل مخصص.

const { employees, findInList } = useGlobal()

const emp = findInList(employees, 5)
// → { id: 5, name: "أحمد" }

const dept = findInList(departments, 'IT', 'code')
refreshGlobal()Function

يحدث كل البيانات العامة عبر إعادة استدعاء getSession() — يُستخدم بعد إضافة/تعديل/حذف عنصر.

const { refreshGlobal } = useGlobal()

await service.create(newEmployee)
await refreshNuxtData(service.CACHE_KEY)
refreshGlobal()
sessionRef

البيانات الخام للـ session كما هي — للحالات التي تحتاج وصول مباشر لحقول غير موجودة في الـ computed.

const { session } = useGlobal()

const privateMode = computed(
  () => session.value?.privateCompanyMode
)

أمثلة الاستخدام

كيف يُستخدم useGlobal في أماكن مختلفة من التطبيق
في Component (Navbar)vue
<script setup>
const { user } = useGlobal()
</script>

<template>
  <UUser :name="user.loginName" :description="user.name" />
</template>
في Module Composable (form.js)js
export const useEmployeesForm = () => {
  const { lists, findInList } = useGlobal()
  const departments = computed(
    () => lists.value.departments || []
  )
  return { departments }
}
في Columns (h() render)js
export const useEmployeesColumns = () => {
  const { findInList, employees } = useGlobal()

  const createColumns = () => {
    return computed(() => [{
      accessorKey: 'departmentId',
      header: 'القسم',
      cell: ({ row }) => {
        const dept = findInList(departments, row.original.departmentId)
        return dept?.name || '—'
      }
    }])
  }
  return { createColumns }
}
عبر GlobalSelect Componentvue
<!-- يكفي تحديد اسم القائمة -->
<GlobalSelect
  list="employees"
  label="الموظف"
  v-model="state.employeeId"
/>

الآلية الداخلية

كيف يعمل useGlobal من الداخل
Implementationjs
export const useGlobal = () => {
  const { data: session, getSession } = useAuth()

  const user = computed(() => ({
    loginName: session.value?.loginName || '',
    name: session.value?.name || ''
  }))

  const lists = computed(() => session.value?.list || {})
  const employees = computed(() => lists.value.employees || [])

  const refreshGlobal = () => getSession()

  const findInList = (list, id, idKey = 'id') =>
    list.value?.find(item => item[idKey] === id) || null

  return { user, lists, employees, refreshGlobal, findInList, session }
}
لماذا computed وليس نسخ؟
useAuth().data ──computed──→ useGlobal ──read──→ أي مكان
  • لو session تتحدث (بعد getSession()) → كل مكان يتحدث فوراً
  • لا watchers، لا actions، لا manual sync
  • مستحيل تكون البيانات قديمة — لأنها تُقرأ من المصدر مباشرة

دورة تحديث البيانات

كيف تتحدث البيانات بعد إضافة عنصر جديد
إضافة عنصر جديد
service.create(data) → POST /api/employees
تحديث الجدول المحلي
await refreshNuxtData(service.CACHE_KEY)
تحديث الـ lookup lists
refreshGlobal() → getSession() → GET /global
التحديث التلقائي
useAuth().data يتحدث → كل computed في useGlobal يتحدث → كل مستهلك يتحدث

القرارات التصميمية

لماذا اخترنا هذا النمط وليس البدائل الأخرى
لماذا ليس Pinia أو useState؟
المعيارPinia / useStateuseGlobal (computed)
مصدر البياناتنسخة منفصلة (sync مطلوب)قراءة مباشرة من useAuth
تحديث البياناتيدوي (action/watcher)تلقائي (computed)
خطر بيانات قديمةممكن — لو نسيت الـ syncمستحيل — لا sync أصلاً
Dependencies@pinia/nuxt + setupصفر
SSRيحتاج إعداديعمل تلقائياً (يرث من useAuth)
يعمل في composablesنعمنعم
يعمل في h() renderنعمنعم
لماذا ليس provide/inject؟
الجانبprovide/injectuseGlobal
النطاقيعتمد على component treeيعمل في أي مكان
في composables مستقلةلا يعمل (form.js, columns.js)يعمل
في render functionsلا يعمليعمل
في componentsيعمليعمل
متى تستخدم كل حل؟
الحالةالحل المناسب
بيانات من مصدر reactive موجود (useAuth)computed + composable
بيانات ينشئها الكلاينت (shopping cart, wizard)Pinia
بيانات معقدة مع mutations كثيرةPinia
بيانات بسيطة مشتركة بين صفحتينuseState