واجهات GraphQL
المقاييس المخصصة
إنشاء أنواع مقياس مخصصة
تمثل أنواع المقياس القيم الورقية البدائية في GraphQL. بينما يوفر GraphQL مقاييس مدمجة مثل String وInt وFloat وBoolean وID، يمكنك إنشاء مقاييس مخصصة لأنواع البيانات المتخصصة مثل DateTime وEmail وURL وJSON.
المقاييس المدمجة
يتضمن GraphQL خمسة أنواع مقياس افتراضية تشكل أساس مخططك.
المقاييس المدمجة:
scalar String # تسلسل أحرف UTF-8
scalar Int # عدد صحيح 32 بت مع إشارة
scalar Float # قيمة نقطة عائمة مزدوجة الدقة مع إشارة
scalar Boolean # صحيح أو خطأ
scalar ID # معرف فريد (يتم تسلسله كسلسلة)
type User {
id: ID! # مقياس ID مدمج
name: String! # مقياس String مدمج
age: Int # مقياس Int مدمج
rating: Float # مقياس Float مدمج
isActive: Boolean # مقياس Boolean مدمج
}
متى يتم إنشاء مقاييس مخصصة
المقاييس المخصصة مفيدة عندما تحتاج إلى منطق محدد للتحقق من الصحة أو التسلسل أو التحليل لنوع بيانات يُستخدم عبر مخططك.
حالات الاستخدام للمقاييس المخصصة:
- DateTime: سلاسل التاريخ/الوقت بتنسيق ISO 8601 مع دعم المنطقة الزمنية
- Email: عناوين البريد الإلكتروني المصادق عليها
- URL: عناوين URL المصادق عليها مع البروتوكول
- JSON: كائنات JSON عشوائية
- PhoneNumber: أرقام هواتف مصادق عليها
- Currency: قيم نقدية بدقة
- UUID: معرفات فريدة عالميًا
الإعلان عن المقاييس المخصصة
يتم الإعلان عن المقاييس المخصصة في مخططك تمامًا مثل الأنواع المدمجة.
إعلان المخطط:
scalar DateTime
scalar Email
scalar URL
scalar JSON
scalar PhoneNumber
type User {
id: ID!
name: String!
email: Email! # مقياس Email مخصص
website: URL # مقياس URL مخصص
phoneNumber: PhoneNumber
createdAt: DateTime! # مقياس DateTime مخصص
metadata: JSON # مقياس JSON مخصص
}
type Post {
id: ID!
title: String!
publishedAt: DateTime
author: User!
}
تنفيذ مقياس DateTime
يتعامل مقياس DateTime مع تحليل التاريخ/الوقت والتحقق من الصحة والتسلسل.
تنفيذ مقياس DateTime (Node.js):
import { GraphQLScalarType, Kind } from 'graphql';
const DateTimeScalar = new GraphQLScalarType({
name: 'DateTime',
description: 'سلسلة تاريخ-وقت ISO 8601 (مثل 2024-01-15T10:30:00Z)',
// التسلسل إلى العميل (DB -> العميل)
serialize(value) {
if (value instanceof Date) {
return value.toISOString();
}
if (typeof value === 'string') {
return new Date(value).toISOString();
}
throw new Error('يجب أن يكون DateTime كائن Date أو سلسلة ISO');
},
// التحليل من إدخال العميل (العميل -> الخادم)
parseValue(value) {
if (typeof value === 'string') {
const date = new Date(value);
if (isNaN(date.getTime())) {
throw new Error('تنسيق DateTime غير صالح');
}
return date;
}
throw new Error('يجب أن يكون DateTime سلسلة');
},
// التحليل من استعلام حرفي
parseLiteral(ast) {
if (ast.kind === Kind.STRING) {
const date = new Date(ast.value);
if (isNaN(date.getTime())) {
throw new Error('تنسيق DateTime غير صالح');
}
return date;
}
return null;
}
});
// تسجيل المحلل
const resolvers = {
DateTime: DateTimeScalar
};
شرح الطرق الثلاث:
- serialize: يحول القيمة الداخلية إلى تنسيق يواجه العميل (استجابة)
- parseValue: يحلل إدخال متغير من العميل (متغيرات في التحويرات)
- parseLiteral: يحلل الاستعلامات الحرفية المضمنة (قيم مشفرة في الاستعلامات)
تنفيذ مقياس Email
يتحقق مقياس Email من صحة عناوين البريد الإلكتروني باستخدام أنماط regex.
تنفيذ مقياس Email:
import { GraphQLScalarType, Kind } from 'graphql';
const EMAIL_REGEX = /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i;
const EmailScalar = new GraphQLScalarType({
name: 'Email',
description: 'عنوان بريد إلكتروني صالح',
serialize(value) {
if (typeof value !== 'string') {
throw new Error('يجب أن يكون Email سلسلة');
}
if (!EMAIL_REGEX.test(value)) {
throw new Error('تنسيق بريد إلكتروني غير صالح');
}
return value.toLowerCase();
},
parseValue(value) {
if (typeof value !== 'string') {
throw new Error('يجب أن يكون Email سلسلة');
}
if (!EMAIL_REGEX.test(value)) {
throw new Error('تنسيق بريد إلكتروني غير صالح');
}
return value.toLowerCase();
},
parseLiteral(ast) {
if (ast.kind === Kind.STRING) {
if (!EMAIL_REGEX.test(ast.value)) {
throw new Error('تنسيق بريد إلكتروني غير صالح');
}
return ast.value.toLowerCase();
}
return null;
}
});
const resolvers = {
Email: EmailScalar
};
تنفيذ مقياس URL
تنفيذ مقياس URL:
import { GraphQLScalarType, Kind } from 'graphql';
const URLScalar = new GraphQLScalarType({
name: 'URL',
description: 'عنوان URL صالح مع البروتوكول',
serialize(value) {
try {
const url = new URL(value);
return url.href;
} catch {
throw new Error('تنسيق URL غير صالح');
}
},
parseValue(value) {
try {
const url = new URL(value);
// التأكد من أن البروتوكول هو http أو https
if (!['http:', 'https:'].includes(url.protocol)) {
throw new Error('يجب أن يستخدم URL بروتوكول http أو https');
}
return url.href;
} catch {
throw new Error('تنسيق URL غير صالح');
}
},
parseLiteral(ast) {
if (ast.kind === Kind.STRING) {
try {
const url = new URL(ast.value);
if (!['http:', 'https:'].includes(url.protocol)) {
throw new Error('يجب أن يستخدم URL بروتوكول http أو https');
}
return url.href;
} catch {
throw new Error('تنسيق URL غير صالح');
}
}
return null;
}
});
تنفيذ مقياس JSON
يسمح مقياس JSON بكائنات JSON عشوائية كقيم حقول.
تنفيذ مقياس JSON:
import { GraphQLScalarType, Kind } from 'graphql';
const JSONScalar = new GraphQLScalarType({
name: 'JSON',
description: 'قيمة JSON عشوائية',
serialize(value) {
return value; // بالفعل كائن JS
},
parseValue(value) {
return value; // العميل يرسل JSON
},
parseLiteral(ast) {
switch (ast.kind) {
case Kind.STRING:
case Kind.BOOLEAN:
return ast.value;
case Kind.INT:
case Kind.FLOAT:
return parseFloat(ast.value);
case Kind.OBJECT:
return parseObject(ast);
case Kind.LIST:
return ast.values.map(n => JSONScalar.parseLiteral(n));
default:
return null;
}
}
});
function parseObject(ast) {
const value = Object.create(null);
ast.fields.forEach(field => {
value[field.name.value] = JSONScalar.parseLiteral(field.value);
});
return value;
}
استخدام مكتبة graphql-scalars
بدلاً من تنفيذ المقاييس من الصفر، استخدم مكتبة graphql-scalars التي توفر تطبيقات جاهزة للإنتاج.
التثبيت والاستخدام graphql-scalars:
# التثبيت
npm install graphql-scalars
# الاستخدام
import {
DateTimeResolver,
EmailAddressResolver,
URLResolver,
JSONResolver,
PhoneNumberResolver,
UUIDResolver
} from 'graphql-scalars';
const resolvers = {
DateTime: DateTimeResolver,
Email: EmailAddressResolver,
URL: URLResolver,
JSON: JSONResolver,
PhoneNumber: PhoneNumberResolver,
UUID: UUIDResolver
};
المقاييس المتاحة في graphql-scalars:
# المقاييس الشائعة DateTime, Date, Time, Duration EmailAddress, PhoneNumber, URL UUID, GUID, HexColorCode, RGB, RGBA JSON, JSONObject BigInt, Long, Byte PositiveInt, NonNegativeInt, NegativeInt PositiveFloat, NonNegativeFloat, NegativeFloat # متقدم MAC, IPv4, IPv6 Port, Latitude, Longitude PostalCode, Currency SafeInt, Void
أفضل ممارسات التحقق من الصحة
مثال شامل للتحقق من الصحة:
import { GraphQLScalarType, Kind } from 'graphql';
const PhoneNumberScalar = new GraphQLScalarType({
name: 'PhoneNumber',
description: 'رقم هاتف دولي (تنسيق E.164)',
serialize(value) {
return validateAndFormat(value);
},
parseValue(value) {
return validateAndFormat(value);
},
parseLiteral(ast) {
if (ast.kind === Kind.STRING) {
return validateAndFormat(ast.value);
}
return null;
}
});
function validateAndFormat(value) {
if (typeof value !== 'string') {
throw new Error('يجب أن يكون PhoneNumber سلسلة');
}
// إزالة المسافات والتنسيق
const cleaned = value.replace(/[\s()-]/g, '');
// تنسيق E.164: +[رمز البلد][الرقم]
const e164Regex = /^\+[1-9]\d{1,14}$/;
if (!e164Regex.test(cleaned)) {
throw new Error(
'يجب أن يكون PhoneNumber بتنسيق E.164 (مثل +14155552671)'
);
}
return cleaned;
}
تحذير: المقاييس المخصصة تتحقق من الصحة في كل إدخال/إخراج. منطق التحقق المعقد يمكن أن يؤثر على الأداء. احتفظ بالتحقق بكفاءة وفكر في تخزين نتائج التحقق مؤقتًا للقيم المستخدمة بشكل متكرر.
تمرين:
- أنشئ مقياس
HexColorيتحقق من صحة رموز الألوان السداسية (#RGB أو #RRGGBB) - نفذ مقياس
Currencyيخزن المبالغ بدقة عشرية 2 - قم ببناء مقياس
Markdownيتحقق من صحة بناء جملة Markdown - أنشئ مقياس
LatitudeوLongitudeمع التحقق من النطاق (-90 إلى 90، -180 إلى 180) - استخدم مكتبة
graphql-scalarsلإضافة مقاييس DateTime وEmail وURL إلى مخططك