action() işlem tanımı
gate.action() bir yazma isteğinin güvenlik ve dayanıklılık kararlarını tek nesnede tutar. Action tanımı immutable bir DefinedAction üretir; gerçek yürütme gate.execute() veya HTTP bağdaştırıcısı tarafından yapılır.
const updateSettings = gate.action({
name: "guild.settings.update",
authentication: "required",
csrf: "required",
timeoutMs: 5_000,
parse(value) {
return parseSettingsInput(value);
},
resource: (input) => ({
type: "guild",
id: input.guildId,
}),
authorize: async (context, input) => {
const allowed = await canManageGuild(context.userId!, input.guildId);
return allowed ? { allowed: true } : { allowed: false, code: "missing_permission" };
},
rateLimit: {
limit: 20,
windowMs: 60_000,
key: (context, input) => `${context.userId}:${input.guildId}`,
},
idempotency: {
required: true,
ttlMs: 10 * 60_000,
scope: (context, input) => `${context.userId}:${input.guildId}`,
},
optimistic: {
expected: (input) => input.expectedRevision,
current: (input) => readRevision(input.guildId),
resource: (input) => `guild:${input.guildId}`,
},
concurrency: {
key: (_context, input) => `guild-settings:${input.guildId}`,
ttlMs: 8_000,
renewEveryMs: 2_000,
waitMs: 250,
},
transaction: {
required: true,
isolation: "serializable",
},
async execute(context, input) {
context.signal.throwIfAborted();
return saveGuildSettings({
transaction: context.transaction,
fencingToken: context.fencingToken,
input,
});
},
cache: {
invalidateTags: (result) => [`guild:${result.guildId}`],
},
realtime: {
delivery: "outbox",
events: (result) => [{
event: "guild.settings.updated",
channel: `guild:${result.guildId}`,
data: result,
}],
},
audit: {
enabled: true,
changes: (result) => ({ revision: result.revision }),
},
});
Kimlik ve CSRF
authentication değeri required, optional veya none olabilir. Varsayılan korumalı davranışı bozacağın her action için sebebi route incelemesinde açıkça yaz.
Güvensiz HTTP yöntemlerinde csrf: "required" kullan. CSRF'yi yalnızca oturum kullanmayan, başka bir imza mekanizmasıyla doğrulanan endpoint'te kapat.
parse ve resource
parse, bilinmeyen girdiyi dar bir tipe çevirir. Uzunluk, enum, sayı aralığı ve izin verilen alanlar burada doğrulanmalıdır.
resource, aynı doğrulanmış kimliği yetkilendirme, optimistic revision, audit, kilit ve realtime kanalı için yeniden kullanmayı kolaylaştırır.
authorize
Callback hiçbir şey döndürmezse izin verilmiş kabul edilir. Açık karar üretmek daha okunaklıdır:
return {
allowed: false,
code: "missing_manage_guild",
details: { guildId: input.guildId },
};
Discord permission değerlerini JavaScript number tipine daraltma; BigInt veya discord.js permission yardımcılarını kullan.
Idempotency reservation
1.1.0 her inflight kayıt için reservationId üretir. Store şu kurala uymalıdır:
- aynı key ve aynı request hash tamamlandıysa saklanan yanıtı döndür
- aynı key farklı hash taşıyorsa conflict üret
- aktif reservation varsa ikinci yürütmeyi başlatma
renew,completevefailsırasında owner token'ı karşılaştır
Action timeout'tan sonra commit ederse çekirdek sonucu aynı reservation altında tamamlayıp sonraki isteğe replay edebilir.
Concurrency ve fencing
ttlMs, lease'in geçerlilik süresidir; renewEveryMs bundan kısa olmalıdır. waitMs, kilit alınamadığında ne kadar bekleneceğini belirler.
Lease store acquireLease() destekliyorsa context.fencingToken üretilir. Veritabanındaki güncelleme, önceki token'dan büyük olmayan bir değeri reddetmelidir; yalnızca kilit sahibine güvenme.
Transaction hook'ları
transaction: {
required: true,
isolation: "serializable",
hooks: {
before: async (scope, input) => {},
beforeCommit: async (scope, result, input) => {},
afterCommit: async (result, input) => {},
afterRollback: async (error, input) => {},
},
}
afterCommit domain yazımından sonra çalışır. Buradaki hata commit'i geri almaz ve action'ı ikinci kez yürütmek için gerekçe değildir.
Retry ve circuit breaker
Retry yalnızca güvenli biçimde tekrarlanabilen işlemde açılmalıdır. Harici yazma idempotent değilse action düzeyindeki idempotency tek başına üçüncü taraf yan etkisini güvenli yapmaz.
Circuit breaker, belirlenen hata eşiğinden sonra çağrıyı geçici olarak keser. Adı verilmezse action kimliğinden türetilir.
Cache, realtime ve audit
cache.invalidateTags commit edilmiş sonuçtan tag listesi üretir. realtime.delivery: "outbox", olayın transaction sınırında kuyruğa yazılmasını sağlar. Audit changes çıktısında secret, token ve gereksiz tam nesne bulundurma.
Sonuç metadata'sı
HTTP katmanı ActionExecutionResult.meta alanını işlemelidir. Özellikle yenilenmiş cookie, cookie temizleme ve postCommitIssues bilgilerini kaybetme.
Konsolda action test guild.settings.update komutuyla kontrol sırasını inceleyebilirsin.